From c53486a47c2f0e48b2a5e2ca7b4f1d5f18396411 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Mon, 15 May 2023 20:14:51 +0200 Subject: [PATCH 01/70] try to fix tests --- packages/backend/test/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/test/api.ts b/packages/backend/test/api.ts index ae46ae92d..222c8d4d4 100644 --- a/packages/backend/test/api.ts +++ b/packages/backend/test/api.ts @@ -10,7 +10,7 @@ describe('API', () => { let bob: any; let carol: any; - before(async () => { + before(async function() { this.timeout(0); p = await startServer(); alice = await signup({ username: 'alice' }); From 9675ced91551c9986aa198097a1aca5851c19a25 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 18 May 2023 02:08:07 +0200 Subject: [PATCH 02/70] translate japanese comment --- packages/backend/src/remote/activitypub/models/person.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 17d9858e4..80993a333 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -217,7 +217,7 @@ export async function createPerson(value: string | IObject, resolver: Resolver): } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { - // /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応 + // Fix an error when the input is an alias like /users/@a -> /users/:id const u = await Users.findOneBy({ uri: person.id, }); From 1516ddfc9bebe946cb94f8cbcabfc586db098823 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 18 May 2023 13:25:57 +0200 Subject: [PATCH 03/70] refactor: remove CacheableUser & co The CacheableUser, CacheableLocalUser and CacheableRemoteUser are identical types to User, ILocalUser and IRemoteUser so it seems nonsensical to have different types for them. --- packages/backend/src/models/entities/user.ts | 6 ------ .../backend/src/remote/activitypub/audience.ts | 16 ++++++++-------- .../src/remote/activitypub/db-resolver.ts | 4 ++-- .../remote/activitypub/kernel/accept/follow.ts | 6 +++--- .../remote/activitypub/kernel/accept/index.ts | 4 ++-- .../src/remote/activitypub/kernel/add/index.ts | 4 ++-- .../remote/activitypub/kernel/announce/index.ts | 4 ++-- .../remote/activitypub/kernel/announce/note.ts | 4 ++-- .../src/remote/activitypub/kernel/block/index.ts | 8 ++++---- .../remote/activitypub/kernel/create/index.ts | 4 ++-- .../src/remote/activitypub/kernel/create/note.ts | 4 ++-- .../remote/activitypub/kernel/delete/actor.ts | 4 ++-- .../remote/activitypub/kernel/delete/index.ts | 4 ++-- .../src/remote/activitypub/kernel/delete/note.ts | 4 ++-- .../src/remote/activitypub/kernel/flag/index.ts | 9 +++++---- .../src/remote/activitypub/kernel/follow.ts | 4 ++-- .../src/remote/activitypub/kernel/index.ts | 6 +++--- .../src/remote/activitypub/kernel/like.ts | 4 ++-- .../src/remote/activitypub/kernel/move/index.ts | 4 ++-- .../src/remote/activitypub/kernel/read.ts | 4 ++-- .../remote/activitypub/kernel/reject/follow.ts | 6 +++--- .../remote/activitypub/kernel/reject/index.ts | 4 ++-- .../remote/activitypub/kernel/remove/index.ts | 4 ++-- .../src/remote/activitypub/kernel/undo/accept.ts | 4 ++-- .../remote/activitypub/kernel/undo/announce.ts | 4 ++-- .../src/remote/activitypub/kernel/undo/block.ts | 4 ++-- .../src/remote/activitypub/kernel/undo/follow.ts | 4 ++-- .../src/remote/activitypub/kernel/undo/index.ts | 4 ++-- .../src/remote/activitypub/kernel/undo/like.ts | 4 ++-- .../remote/activitypub/kernel/update/index.ts | 4 ++-- .../src/remote/activitypub/misc/auth-user.ts | 4 ++-- .../src/remote/activitypub/models/image.ts | 6 +++--- .../src/remote/activitypub/models/mention.ts | 8 ++++---- .../src/remote/activitypub/models/note.ts | 4 ++-- .../src/remote/activitypub/models/person.ts | 6 +++--- .../backend/src/remote/activitypub/perform.ts | 4 ++-- packages/backend/src/server/api/authenticate.ts | 4 ++-- packages/backend/src/server/api/call.ts | 4 ++-- packages/backend/src/server/api/define.ts | 8 ++++---- .../backend/src/server/api/endpoints/ap/show.ts | 6 +++--- packages/backend/src/services/blocking/delete.ts | 4 ++-- packages/backend/src/services/messages/create.ts | 4 ++-- packages/backend/src/services/note/polls/vote.ts | 4 ++-- packages/backend/src/services/user-cache.ts | 4 ++-- 44 files changed, 107 insertions(+), 112 deletions(-) diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 6cfca187a..0c1e8092f 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -260,9 +260,3 @@ export interface IRemoteUser extends User { host: string; token: null; } - -export type CacheableLocalUser = ILocalUser; - -export type CacheableRemoteUser = IRemoteUser; - -export type CacheableUser = CacheableLocalUser | CacheableRemoteUser; diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts index 9c04ecb6d..9125660f3 100644 --- a/packages/backend/src/remote/activitypub/audience.ts +++ b/packages/backend/src/remote/activitypub/audience.ts @@ -1,5 +1,5 @@ import promiseLimit from 'promise-limit'; -import { CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; +import { IRemoteUser, User } from '@/models/entities/user.js'; import { unique, concat } from '@/prelude/array.js'; import { resolvePerson } from './models/person.js'; import { Resolver } from './resolver.js'; @@ -9,20 +9,20 @@ type Visibility = 'public' | 'home' | 'followers' | 'specified'; type AudienceInfo = { visibility: Visibility, - mentionedUsers: CacheableUser[], - visibleUsers: CacheableUser[], + mentionedUsers: User[], + visibleUsers: User[], }; -export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { +export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = groupingAudience(getApIds(to), actor); const ccGroups = groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); + )).filter((x): x is User => x != null); if (toGroups.public.length > 0) { return { @@ -55,7 +55,7 @@ export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, c }; } -function groupingAudience(ids: string[], actor: CacheableRemoteUser) { +function groupingAudience(ids: string[], actor: IRemoteUser) { const groups = { public: [] as string[], followers: [] as string[], @@ -85,7 +85,7 @@ function isPublic(id: string) { ].includes(id); } -function isFollowers(id: string, actor: CacheableRemoteUser) { +function isFollowers(id: string, actor: IRemoteUser) { return ( id === (actor.followersUri || `${actor.uri}/followers`) ); diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index dc1b47fef..4684b8931 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -1,7 +1,7 @@ import escapeRegexp from 'escape-regexp'; import config from '@/config/index.js'; import { Note } from '@/models/entities/note.js'; -import { CacheableUser } from '@/models/entities/user.js'; +import { User } from '@/models/entities/user.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Notes, MessagingMessages } from '@/models/index.js'; import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; @@ -89,7 +89,7 @@ export class DbResolver { /** * AP Person => FoundKey User in DB */ - public async getUserFromApId(value: string | IObject): Promise { + public async getUserFromApId(value: string | IObject): Promise { const parsed = parseUri(value); if (parsed.local) { diff --git a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts index 037f660c6..05c621e6d 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts @@ -1,11 +1,11 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { acceptFollowRequest } from '@/services/following/requests/accept.js'; import { relayAccepted } from '@/services/relay.js'; import { IFollow } from '@/remote/activitypub/type.js'; import { DbResolver } from '@/remote/activitypub/db-resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある +export default async (actor: IRemoteUser, activity: IFollow): Promise => { + // activity is a follow request started by this server, so activity.actor must be an existing local user. const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.actor); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index be9b80096..1a61011e6 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -1,10 +1,10 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { IAccept, isFollow, getApType } from '@/remote/activitypub/type.js'; import acceptFollow from './follow.js'; -export default async (actor: CacheableRemoteUser, activity: IAccept, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IAccept, resolver: Resolver): Promise => { const uri = activity.id || activity; apLogger.info(`Accept: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/add/index.ts b/packages/backend/src/remote/activitypub/kernel/add/index.ts index 3fd5f4723..3d685dae4 100644 --- a/packages/backend/src/remote/activitypub/kernel/add/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/add/index.ts @@ -1,10 +1,10 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { addPinned } from '@/services/i/pin.js'; import { resolveNote } from '@/remote/activitypub/models/note.js'; import { IAdd } from '@/remote/activitypub/type.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IAdd, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IAdd, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index e4d77e1c5..25bc8c73c 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -1,10 +1,10 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { IAnnounce, getApId } from '@/remote/activitypub/type.js'; import announceNote from './note.js'; -export default async (actor: CacheableRemoteUser, activity: IAnnounce, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IAnnounce, resolver: Resolver): Promise => { const uri = getApId(activity); apLogger.info(`Announce: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 254ef2727..5c6196588 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -1,5 +1,5 @@ import post from '@/services/note/create.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { extractDbHost } from '@/misc/convert-host.js'; import { getApLock } from '@/misc/app-lock.js'; import { StatusError } from '@/misc/fetch.js'; @@ -11,7 +11,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js'; import { IAnnounce, getApId } from '@/remote/activitypub/type.js'; import { shouldBlockInstance } from '@/misc/should-block-instance.js'; -export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { +export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); if (actor.isSuspended) { diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts index 7095a36a5..08b7ee517 100644 --- a/packages/backend/src/remote/activitypub/kernel/block/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts @@ -1,11 +1,11 @@ import block from '@/services/blocking/create.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { DbResolver } from '@/remote/activitypub/db-resolver.js'; import { IBlock } from '@/remote/activitypub/type.js'; -export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { - // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず +export default async (actor: IRemoteUser, activity: IBlock): Promise => { + // There is a block target in activity.object, which should be a local user that exists. const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); @@ -15,7 +15,7 @@ export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { +export default async (actor: IRemoteUser, activity: ICreate, resolver: Resolver): Promise => { const uri = getApId(activity); apLogger.info(`Create: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts index 892dbb26a..6fc7b6c2d 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts @@ -1,4 +1,4 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { getApLock } from '@/misc/app-lock.js'; import { extractDbHost } from '@/misc/convert-host.js'; import { StatusError } from '@/misc/fetch.js'; @@ -9,7 +9,7 @@ import { getApId, IObject } from '@/remote/activitypub/type.js'; /** * 投稿作成アクティビティを捌きます */ -export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false): Promise { +export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false): Promise { const uri = getApId(note); if (typeof note === 'object') { diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index ea75a9739..9513ea22f 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -1,9 +1,9 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { deleteAccount } from '@/services/delete-account.js'; -export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise { +export async function deleteActor(actor: IRemoteUser, uri: string): Promise { apLogger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index ee05e5327..9e05c9e48 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -1,4 +1,4 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { toSingle } from '@/prelude/array.js'; import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '@/remote/activitypub/type.js'; import { deleteActor } from './actor.js'; @@ -7,7 +7,7 @@ import deleteNote from './note.js'; /** * 削除アクティビティを捌きます */ -export default async (actor: CacheableRemoteUser, activity: IDelete): Promise => { +export default async (actor: IRemoteUser, activity: IDelete): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index 9f9a5cea6..d855f7f92 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -1,11 +1,11 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { deleteNotes } from '@/services/note/delete.js'; import { getApLock } from '@/misc/app-lock.js'; import { deleteMessage } from '@/services/messages/delete.js'; import { DbResolver } from '@/remote/activitypub/db-resolver.js'; import { apLogger } from '@/remote/activitypub/logger.js'; -export default async function(actor: CacheableRemoteUser, uri: string): Promise { +export default async function(actor: IRemoteUser, uri: string): Promise { apLogger.info(`Deleting the Note: ${uri}`); const unlock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index e50bcc2bd..cadb7436d 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -1,13 +1,14 @@ import { In } from 'typeorm'; import config from '@/config/index.js'; import { genId } from '@/misc/gen-id.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { AbuseUserReports, Users } from '@/models/index.js'; import { IFlag, getApIds } from '@/remote/activitypub/type.js'; -export default async (actor: CacheableRemoteUser, activity: IFlag): Promise => { - // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので - // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する +export default async (actor: IRemoteUser, activity: IFlag): Promise => { + // The object is `(User|Note) | (User|Note)[]`, but since the database schema + // cannot be made to handle every possible case, the target user is the first user + // and everything else is stored by URL. const uris = getApIds(activity.object); const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!); diff --git a/packages/backend/src/remote/activitypub/kernel/follow.ts b/packages/backend/src/remote/activitypub/kernel/follow.ts index 8125b4606..99dbb369c 100644 --- a/packages/backend/src/remote/activitypub/kernel/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/follow.ts @@ -1,9 +1,9 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import follow from '@/services/following/create.js'; import { IFollow } from '../type.js'; import { DbResolver } from '../db-resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async (actor: IRemoteUser, activity: IFollow): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index 46a972a7e..2a0918a4d 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -1,4 +1,4 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { toArray } from '@/prelude/array.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { extractDbHost } from '@/misc/convert-host.js'; @@ -21,7 +21,7 @@ import block from './block/index.js'; import flag from './flag/index.js'; import { move } from './move/index.js'; -export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise { +export async function performActivity(actor: IRemoteUser, activity: IObject, resolver: Resolver): Promise { if (isCollectionOrOrderedCollection(activity)) { for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { const act = await resolver.resolve(item); @@ -38,7 +38,7 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj } } -async function performOneActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise { +async function performOneActivity(actor: IRemoteUser, activity: IObject, resolver: Resolver): Promise { if (actor.isSuspended) return; if (typeof activity.id !== 'undefined') { diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 9650312b3..51270cc80 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -1,9 +1,9 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { createReaction } from '@/services/note/reaction/create.js'; import { ILike, getApId } from '../type.js'; import { fetchNote, extractEmojis } from '../models/note.js'; -export default async (actor: CacheableRemoteUser, activity: ILike) => { +export default async (actor: IRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts index e64656e09..8f233d869 100644 --- a/packages/backend/src/remote/activitypub/kernel/move/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/move/index.ts @@ -1,12 +1,12 @@ import { IsNull } from 'typeorm'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { resolvePerson } from '@/remote/activitypub/models/person.js'; import { Followings, Users } from '@/models/index.js'; import { createNotification } from '@/services/create-notification.js'; import Resolver from '../../resolver.js'; import { IMove, isActor, getApId } from '../../type.js'; -export async function move(actor: CacheableRemoteUser, activity: IMove, resolver: Resolver): Promise { +export async function move(actor: IRemoteUser, activity: IMove, resolver: Resolver): Promise { // actor is not move origin if (activity.object == null || getApId(activity.object) !== actor.uri) return; diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts index d367fb669..cb147f2af 100644 --- a/packages/backend/src/remote/activitypub/kernel/read.ts +++ b/packages/backend/src/remote/activitypub/kernel/read.ts @@ -1,10 +1,10 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { isSelfHost, extractDbHost } from '@/misc/convert-host.js'; import { MessagingMessages } from '@/models/index.js'; import { readUserMessagingMessage } from '@/server/api/common/read-messaging-message.js'; import { IRead, getApId } from '../type.js'; -export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise => { +export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise => { const id = await getApId(activity.object); if (!isSelfHost(extractDbHost(id))) { diff --git a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts index bd3ad1660..2606b8a5e 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts @@ -1,12 +1,12 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { remoteReject } from '@/services/following/reject.js'; import { relayRejected } from '@/services/relay.js'; import { Users } from '@/models/index.js'; import { IFollow } from '../../type.js'; import { DbResolver } from '../../db-resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある +export default async (actor: IRemoteUser, activity: IFollow): Promise => { + // activity is a follow request started by this server, so activity.actor must be an existing local user. const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.actor); diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index 3a91c8ec7..3eb748f6b 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -1,10 +1,10 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { apLogger } from '../../logger.js'; import { IReject, isFollow, getApType } from '../../type.js'; import rejectFollow from './follow.js'; -export default async (actor: CacheableRemoteUser, activity: IReject, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IReject, resolver: Resolver): Promise => { const uri = activity.id || activity; apLogger.info(`Reject: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/remove/index.ts b/packages/backend/src/remote/activitypub/kernel/remove/index.ts index 6591f82b1..3f7ad494e 100644 --- a/packages/backend/src/remote/activitypub/kernel/remove/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/remove/index.ts @@ -1,10 +1,10 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { removePinned } from '@/services/i/pin.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { IRemove } from '../../type.js'; import { resolveNote } from '../../models/note.js'; -export default async (actor: CacheableRemoteUser, activity: IRemove, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IRemove, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts index fa4eea44c..b14cec889 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts @@ -1,10 +1,10 @@ import unfollow from '@/services/following/delete.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { Followings } from '@/models/index.js'; import { DbResolver } from '@/remote/activitypub/db-resolver.js'; import { IAccept } from '@/remote/activitypub/type.js'; -export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { +export default async (actor: IRemoteUser, activity: IAccept): Promise => { const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts index 78981b542..05e0edbb6 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts @@ -1,9 +1,9 @@ import { Notes } from '@/models/index.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { deleteNotes } from '@/services/note/delete.js'; import { IAnnounce, getApId } from '@/remote/activitypub/type.js'; -export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { +export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise => { const uri = getApId(activity); const note = await Notes.findOneBy({ diff --git a/packages/backend/src/remote/activitypub/kernel/undo/block.ts b/packages/backend/src/remote/activitypub/kernel/undo/block.ts index ae1c9c0b6..f4e0513fb 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/block.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/block.ts @@ -1,10 +1,10 @@ import unblock from '@/services/blocking/delete.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { IBlock } from '@/remote/activitypub/type.js'; import { DbResolver } from '@/remote/activitypub/db-resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { +export default async (actor: IRemoteUser, activity: IBlock): Promise => { const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts index c7f99bcf2..172ee8460 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts @@ -1,11 +1,11 @@ import unfollow from '@/services/following/delete.js'; import { cancelFollowRequest } from '@/services/following/requests/cancel.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { FollowRequests, Followings } from '@/models/index.js'; import { IFollow } from '@/remote/activitypub/type.js'; import { DbResolver } from '@/remote/activitypub/db-resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async (actor: IRemoteUser, activity: IFollow): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index 05382f0f5..139711129 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -1,4 +1,4 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '@/remote/activitypub/type.js'; @@ -8,7 +8,7 @@ import undoLike from './like.js'; import undoAccept from './accept.js'; import { undoAnnounce } from './announce.js'; -export default async (actor: CacheableRemoteUser, activity: IUndo, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IUndo, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts index 6c7b8d18b..717c8aa2a 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts @@ -1,4 +1,4 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { deleteReaction } from '@/services/note/reaction/delete.js'; import { ILike, getApId } from '@/remote/activitypub/type.js'; import { fetchNote } from '@/remote/activitypub/models/note.js'; @@ -6,7 +6,7 @@ import { fetchNote } from '@/remote/activitypub/models/note.js'; /** * Process Undo.Like activity */ -export default async (actor: CacheableRemoteUser, activity: ILike) => { +export default async (actor: IRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); const note = await fetchNote(targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 73085b181..d34965db2 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,4 +1,4 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { getApId, getApType, IUpdate, isActor } from '@/remote/activitypub/type.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { updateQuestion } from '@/remote/activitypub/models/question.js'; @@ -8,7 +8,7 @@ import { updatePerson } from '@/remote/activitypub/models/person.js'; /** * Updateアクティビティを捌きます */ -export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: Resolver): Promise => { +export default async (actor: IRemoteUser, activity: IUpdate, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; } diff --git a/packages/backend/src/remote/activitypub/misc/auth-user.ts b/packages/backend/src/remote/activitypub/misc/auth-user.ts index 4705bb791..e140d2fa1 100644 --- a/packages/backend/src/remote/activitypub/misc/auth-user.ts +++ b/packages/backend/src/remote/activitypub/misc/auth-user.ts @@ -1,12 +1,12 @@ import { Cache } from '@/misc/cache.js'; import { UserPublickeys } from '@/models/index.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { UserPublickey } from '@/models/entities/user-publickey.js'; import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; import { createPerson } from '@/remote/activitypub/models/person.js'; export type AuthUser = { - user: CacheableRemoteUser; + user: IRemoteUser; key: UserPublickey; }; diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index 281cbdf9a..aaf4b90d7 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,5 +1,5 @@ import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles } from '@/models/index.js'; @@ -11,7 +11,7 @@ import { apLogger } from '../logger.js'; /** * Imageを作成します。 */ -export async function createImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise { +export async function createImage(actor: IRemoteUser, value: any, resolver: Resolver): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); @@ -58,7 +58,7 @@ export async function createImage(actor: CacheableRemoteUser, value: any, resolv * If the target Image is registered in FoundKey, return it; otherwise, fetch it from the remote server and return it. * Fetch the image from the remote server, register it in FoundKey and return it. */ -export async function resolveImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise { +export async function resolveImage(actor: IRemoteUser, value: any, resolver: Resolver): Promise { // TODO // Fetch from remote server and register it. diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index 183ab841a..c42fd197a 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -1,17 +1,17 @@ import promiseLimit from 'promise-limit'; import { toArray, unique } from '@/prelude/array.js'; -import { CacheableUser } from '@/models/entities/user.js'; +import { User } from '@/models/entities/user.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { IObject, isMention, IApMention } from '../type.js'; import { resolvePerson } from './person.js'; -export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise { +export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); + )).filter((x): x is User => x != null); return mentionedUsers; } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 30245a67f..52de0d49c 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -2,7 +2,7 @@ import promiseLimit from 'promise-limit'; import config from '@/config/index.js'; import post from '@/services/note/create.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; import { vote } from '@/services/note/polls/vote.js'; import { DriveFile } from '@/models/entities/drive-file.js'; @@ -91,7 +91,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si apLogger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; + const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 80993a333..a5e268005 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -6,7 +6,7 @@ import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instanc import { Note } from '@/models/entities/note.js'; import { updateUsertags } from '@/services/update-hashtag.js'; import { Users, Instances, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; -import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js'; +import { User, IRemoteUser, User } from '@/models/entities/user.js'; import { Emoji } from '@/models/entities/emoji.js'; import { UserNotePining } from '@/models/entities/user-note-pining.js'; import { genId } from '@/misc/gen-id.js'; @@ -121,7 +121,7 @@ async function validateActor(x: IObject, resolver: Resolver): Promise { * * If the target Person is registered in FoundKey, it is returned. */ -export async function fetchPerson(uri: string): Promise { +export async function fetchPerson(uri: string): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); const cached = uriPersonCache.get(uri); @@ -394,7 +394,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver): * If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it. * Fetch the person from the remote server, register it in FoundKey, and return it. */ -export async function resolvePerson(uri: string, resolver: Resolver, hint?: IObject): Promise { +export async function resolvePerson(uri: string, resolver: Resolver, hint?: IObject): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index 8622d43df..23999213c 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -1,11 +1,11 @@ import { DAY } from '@/const.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { IRemoteUser } from '@/models/entities/user.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { IObject } from './type.js'; import { performActivity } from './kernel/index.js'; import { updatePerson } from './models/person.js'; -export async function perform(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise { +export async function perform(actor: IRemoteUser, activity: IObject, resolver: Resolver): Promise { await performActivity(actor, activity, resolver); // And while I'm at it, I'll update the remote user information if it's out of date. diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 25e87b75e..9da253526 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -1,4 +1,4 @@ -import { CacheableLocalUser } from '@/models/entities/user.js'; +import { ILocalUser } from '@/models/entities/user.js'; import { Users, AccessTokens } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { userByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; @@ -11,7 +11,7 @@ export class AuthenticationError extends Error { } } -export default async (authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { +export default async (authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[ILocalUser | null | undefined, AccessToken | null | undefined]> => { let maybeToken: string | null = null; // check if there is an authorization header set diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index dc0e790bd..89c80f367 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,6 +1,6 @@ import { performance } from 'perf_hooks'; import Koa from 'koa'; -import { CacheableLocalUser } from '@/models/entities/user.js'; +import { ILocalUser } from '@/models/entities/user.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { limiter } from './limiter.js'; @@ -8,7 +8,7 @@ import endpoints, { IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; -export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { +export default async (endpoint: string, user: ILocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; const isModerator = user != null && (user.isModerator || user.isAdmin); diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts index 243b105ae..811a10ae3 100644 --- a/packages/backend/src/server/api/define.ts +++ b/packages/backend/src/server/api/define.ts @@ -1,6 +1,6 @@ import * as fs from 'node:fs'; import Ajv from 'ajv'; -import { CacheableLocalUser } from '@/models/entities/user.js'; +import { ILocalUser } from '@/models/entities/user.js'; import { Schema, SchemaType } from '@/misc/schema.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { IEndpointMeta } from './endpoints.js'; @@ -10,7 +10,7 @@ export type Response = Record | void; // TODO: paramsの型をT['params']のスキーマ定義から推論する type executor = - (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) => + (params: SchemaType, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) => Promise>>; const ajv = new Ajv({ @@ -20,10 +20,10 @@ const ajv = new Ajv({ ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); export default function (meta: T, paramDef: Ps, cb: executor) - : (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise { + : (params: any, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any) => Promise { const validate = ajv.compile(paramDef); - return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => { + return (params: any, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any) => { function cleanup() { fs.unlink(file.path, () => {}); } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 9492cc60a..31b0f1266 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -5,7 +5,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js'; import { extractDbHost } from '@/misc/convert-host.js'; import { Users, Notes } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; +import { ILocalUser, User } from '@/models/entities/user.js'; import { isActor, isPost } from '@/remote/activitypub/type.js'; import { SchemaType } from '@/misc/schema.js'; import { HOUR } from '@/const.js'; @@ -85,7 +85,7 @@ export default define(meta, paramDef, async (ps, me) => { /*** * URIからUserかNoteを解決する */ -async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { +async function fetchAny(uri: string, me: ILocalUser | null | undefined): Promise | null> { // Stop if the host is blocked. const host = extractDbHost(uri); if (await shouldBlockInstance(host)) { @@ -122,7 +122,7 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): ); } -async function mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { +async function mergePack(me: ILocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts index 82f92f05a..c26f1ac54 100644 --- a/packages/backend/src/services/blocking/delete.ts +++ b/packages/backend/src/services/blocking/delete.ts @@ -2,13 +2,13 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { renderBlock } from '@/remote/activitypub/renderer/block.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { deliver } from '@/queue/index.js'; -import { CacheableUser } from '@/models/entities/user.js'; +import { User } from '@/models/entities/user.js'; import { Blockings, Users } from '@/models/index.js'; import Logger from '../logger.js'; const logger = new Logger('blocking/delete'); -export default async function(blocker: CacheableUser, blockee: CacheableUser) { +export default async function(blocker: User, blockee: User) { const blocking = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index 4a0ea53a8..dd31d8794 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -1,5 +1,5 @@ import { Not } from 'typeorm'; -import { CacheableUser, User } from '@/models/entities/user.js'; +import { User } from '@/models/entities/user.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js'; @@ -13,7 +13,7 @@ import renderCreate from '@/remote/activitypub/renderer/create.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { deliver } from '@/queue/index.js'; -export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { +export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { const message = { id: genId(), createdAt: new Date(), diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts index b86e7107d..cdd27da50 100644 --- a/packages/backend/src/services/note/polls/vote.ts +++ b/packages/backend/src/services/note/polls/vote.ts @@ -1,12 +1,12 @@ import { ArrayOverlap, Not } from 'typeorm'; import { publishNoteStream } from '@/services/stream.js'; -import { CacheableUser } from '@/models/entities/user.js'; +import { User } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { PollVotes, NoteWatchings, Polls, Blockings, NoteThreadMutings } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { createNotification } from '@/services/create-notification.js'; -export async function vote(user: CacheableUser, note: Note, choice: number): Promise { +export async function vote(user: User, note: Note, choice: number): Promise { const poll = await Polls.findOneBy({ noteId: note.id }); if (poll == null) throw new Error('poll not found'); diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts index b4bbb6f1a..77c5d0541 100644 --- a/packages/backend/src/services/user-cache.ts +++ b/packages/backend/src/services/user-cache.ts @@ -1,5 +1,5 @@ import { IsNull } from 'typeorm'; -import { CacheableLocalUser, ILocalUser, User } from '@/models/entities/user.js'; +import { ILocalUser, User } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { Cache } from '@/misc/cache.js'; import { subscriber } from '@/db/redis.js'; @@ -8,7 +8,7 @@ export const userByIdCache = new Cache( Infinity, async (id) => await Users.findOneBy({ id, isDeleted: false }) ?? undefined, ); -export const localUserByNativeTokenCache = new Cache( +export const localUserByNativeTokenCache = new Cache( Infinity, async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: false }) as ILocalUser | null ?? undefined, ); From 54d69ed49ee4c0d0db57a94a6019b556b46a1ddd Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 3 Oct 2021 12:20:48 +0200 Subject: [PATCH 04/70] reduce opacity of small tag only once fixes https://github.com/misskey-dev/misskey/issues/7852 --- .../src/components/global/misskey-flavored-markdown.vue | 8 ++++++++ packages/client/src/components/mfm.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index 72ab8b9ce..eb0c5f34b 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -56,6 +56,14 @@ withDefaults(defineProps<{ } } +._mfm_small_ { + opacity: 0.7; +} + +._mfm_small_ ._mfm_small_{ + opacity: initial; +} + @keyframes mfm-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 1107edcd6..64fe5bdb5 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -197,7 +197,7 @@ export default defineComponent({ case 'small': { return h('small', { - style: 'opacity: 0.7;', + class: '_mfm_small_' }, genEl(token.children)); } From c5327f74d42a811e4a924b0f72b8cd8aaa0d0b71 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 18 May 2023 21:41:32 +0200 Subject: [PATCH 05/70] refactor to check log levels This will eventually allow the log level to be configured. For now, the explicit debug flag does not work. --- packages/backend/src/services/logger.ts | 112 ++++++++++++++++-------- 1 file changed, 77 insertions(+), 35 deletions(-) diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index a3bfc8a0a..ea5d321fa 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -12,7 +12,14 @@ type Domain = { color?: KEYWORD; }; -type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; +export const LEVELS = { + error: 0, + warning: 1, + success: 2, + info: 3, + debug: 4, +}; +export type Level = LEVELS[keyof LEVELS]; /** * Class that facilitates recording log messages to the console and optionally a syslog server. @@ -22,6 +29,10 @@ export default class Logger { private parentLogger: Logger | null = null; private store: boolean; private syslogClient: SyslogPro.RFC5424 | null = null; + /** + * Messages below this level will be discarded. + */ + private minLevel: Level; /** * Create a logger instance. @@ -29,12 +40,13 @@ export default class Logger { * @param color Log message color * @param store Whether to store messages */ - constructor(domain: string, color?: KEYWORD, store = true) { + constructor(domain: string, color?: KEYWORD, store = true, minLevel: Level = LEVELS.info) { this.domain = { name: domain, color, }; this.store = store; + this.minLevel = minLevel; if (config.syslog) { this.syslogClient = new SyslogPro.RFC5424({ @@ -58,16 +70,29 @@ export default class Logger { * @param store Whether to store messages * @returns A Logger instance whose parent logger is this instance. */ - public createSubLogger(domain: string, color?: KEYWORD, store = true): Logger { - const logger = new Logger(domain, color, store); + public createSubLogger(domain: string, color?: KEYWORD, store = true, minLevel: Level = LEVELS.info): Logger { + const logger = new Logger(domain, color, store, minLevel); logger.parentLogger = this; return logger; } + /** + * Log a message. + * @param level Indicates the level of this particular message. If it is + * less than the minimum level configured, the message will be discarded. + * @param message The message to be logged. + * @param important Whether to highlight this message as especially important. + * @param subDomains Names of sub-loggers to be added. + */ private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], _store = true): void { if (envOption.quiet) return; - const store = _store && this.store && (level !== 'debug'); + const store = _store && this.store; + // Check against the configured log level. + if (level < this.minLevel) return; + + // If this logger has a parent logger, delegate the actual logging to it, + // so the parent domain(s) will be logged properly. if (this.parentLogger) { this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store); return; @@ -75,34 +100,53 @@ export default class Logger { const time = dateFormat(new Date(), 'HH:mm:ss'); const worker = cluster.isPrimary ? '*' : cluster.worker?.id; - const l = - level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') : - level === 'warning' ? chalk.yellow('WARN') : - level === 'success' ? important ? chalk.bgGreen.white('DONE') : chalk.green('DONE') : - level === 'debug' ? chalk.gray('VERB') : - chalk.blue('INFO'); const domains = [this.domain].concat(subDomains).map(d => d.color ? chalk.rgb(...convertColor.keyword.rgb(d.color))(d.name) : chalk.white(d.name)); - const m = - level === 'error' ? chalk.red(message) : - level === 'warning' ? chalk.yellow(message) : - level === 'success' ? chalk.green(message) : - level === 'debug' ? chalk.gray(message) : - message; - let log = `${l} ${worker}\t[${domains.join(' ')}]\t${m}`; + let levelDisplay; + let messageDisplay; + switch (level) { + case LEVELS.error: + if (important) { + levelDisplay = chalk.bgRed.white('ERR '); + } else { + levelDisplay = chalk.red('ERR '); + } + messageDisplay = chalk.red(message); + break; + case LEVELS.warning: + levelDisplay = chalk.yellow('WARN'); + messageDisplay = chalk.yellow(message); + break; + case LEVELS.success: + if (important) { + levelDisplay = chalk.bgGreen.white('DONE'); + } else { + levelDisplay = chalk.green('DONE'); + } + messageDisplay = chalk.green(message); + break; + case LEVELS.info: + levelDisplay = chalk.blue('INFO'); + messageDisplay = message; + break; + case LEVELS.debug: default: + levelDisplay = chalk.gray('VERB'); + messageDisplay = chalk.gray(message); + break; + } + + let log = `${levelDisplay} ${worker}\t[${domains.join(' ')}]\t${messageDisplay}`; if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log; console.log(important ? chalk.bold(log) : log); - if (store) { - if (this.syslogClient) { - const send = - level === 'error' ? this.syslogClient.error : - level === 'warning' ? this.syslogClient.warning : - this.syslogClient.info; + if (store && this.syslogClient) { + const send = + level === LEVELS.error ? this.syslogClient.error : + level === LEVELS.warning ? this.syslogClient.warning : + this.syslogClient.info; - send.bind(this.syslogClient)(message).catch(() => {}); - } + send.bind(this.syslogClient)(message).catch(() => {}); } } @@ -116,11 +160,11 @@ export default class Logger { public error(err: string | Error, data: Record = {}, important = false): void { if (err instanceof Error) { data.e = err; - this.log('error', err.toString(), data, important); + this.log(LEVELS.error, err.toString(), data, important); } else if (typeof err === 'object') { - this.log('error', `${(err as any).message || (err as any).name || err}`, data, important); + this.log(LEVELS.error, `${(err as any).message || (err as any).name || err}`, data, important); } else { - this.log('error', `${err}`, data, important); + this.log(LEVELS.error, `${err}`, data, important); } } @@ -132,7 +176,7 @@ export default class Logger { * @param important Whether this warning is important */ public warn(message: string, data?: Record | null, important = false): void { - this.log('warning', message, data, important); + this.log(LEVELS.warning, message, data, important); } /** @@ -143,7 +187,7 @@ export default class Logger { * @param important Whether this success message is important */ public succ(message: string, data?: Record | null, important = false): void { - this.log('success', message, data, important); + this.log(LEVELS.success, message, important); } /** @@ -154,9 +198,7 @@ export default class Logger { * @param important Whether this debug message is important */ public debug(message: string, data?: Record | null, important = false): void { - if (process.env.NODE_ENV !== 'production' || envOption.verbose) { - this.log('debug', message, data, important); - } + this.log(LEVELS.debug, message, data, important); } /** @@ -167,6 +209,6 @@ export default class Logger { * @param important Whether this info message is important */ public info(message: string, data?: Record | null, important = false): void { - this.log('info', message, data, important); + this.log(LEVELS.info, message, data, important); } } From 85a392ee335c941fd2d27caf5086efaa77c3b34d Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 18 May 2023 22:42:30 +0200 Subject: [PATCH 06/70] logger: remove unused structured data The `data` field is not used anywhere in the logger. While it would be possible to send structured data through syslog, it seems unnecessary at present and also the way in which this structured data would have to be provided sounds too cumbersome to implement for no real value. --- packages/backend/src/boot/master.ts | 12 +++---- packages/backend/src/queue/index.ts | 20 ++++++------ .../src/remote/activitypub/models/note.ts | 8 +---- packages/backend/src/server/api/call.ts | 10 +----- .../src/services/drive/upload-from-url.ts | 5 +-- packages/backend/src/services/logger.ts | 32 ++++++++----------- 6 files changed, 32 insertions(+), 55 deletions(-) diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 6f64715f8..f68a2d310 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -41,7 +41,7 @@ function greet(): void { } bootLogger.info('Welcome to FoundKey!'); - bootLogger.info(`FoundKey v${meta.version}`, null, true); + bootLogger.info(`FoundKey v${meta.version}`, true); } /** @@ -59,7 +59,7 @@ export async function masterMain(): Promise { config = loadConfigBoot(); await connectDb(); } catch (e) { - bootLogger.error('Fatal error occurred during initialization', {}, true); + bootLogger.error('Fatal error occurred during initialization', true); process.exit(1); } @@ -69,7 +69,7 @@ export async function masterMain(): Promise { await spawnWorkers(config.clusterLimits); } - bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); + bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, true); if (!envOption.noDaemons) { import('../daemons/server-stats.js').then(x => x.serverStats()); @@ -84,7 +84,7 @@ function showEnvironment(): void { if (env !== 'production') { logger.warn('The environment is not in production mode.'); - logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', {}, true); + logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', true); } } @@ -109,7 +109,7 @@ function loadConfigBoot(): Config { } catch (exception) { const e = exception as Partial | Error; if ('code' in e && e.code === 'ENOENT') { - configLogger.error('Configuration file not found', {}, true); + configLogger.error('Configuration file not found', true); process.exit(1); } else if (e instanceof Error) { configLogger.error(e.message); @@ -133,7 +133,7 @@ async function connectDb(): Promise { const v = await db.query('SHOW server_version').then(x => x[0].server_version); dbLogger.succ(`Connected: v${v}`); } catch (e) { - dbLogger.error('Cannot connect', {}, true); + dbLogger.error('Cannot connect', true); dbLogger.error(e as Error | string); process.exit(1); } diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index cc125a3de..558f54ff0 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -39,8 +39,8 @@ systemQueue .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`)) .on('active', (job) => systemLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`)) + .on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`)) .on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`)); deliverQueue @@ -48,31 +48,31 @@ deliverQueue .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) - .on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`)) .on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); inboxQueue .on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`)) .on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`)) .on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) - .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`)) + .on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`)) .on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`)); dbQueue .on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`)) .on('active', (job) => dbLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`)) + .on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`)) .on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`)); objectStorageQueue .on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`)) .on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`)) + .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`)) .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); webhookDeliverQueue @@ -80,7 +80,7 @@ webhookDeliverQueue .on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) - .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) })) + .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`)) .on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); export function deliver(user: ThinUser, content: unknown, to: string | null) { diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 52de0d49c..3607ad62b 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -74,13 +74,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si const err = validateNote(object); if (err) { - apLogger.error(`${err.message}`, { - resolver: { - history: resolver.getHistory(), - }, - value, - object, - }); + apLogger.error(`${err.message}`); throw new Error('invalid note'); } diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 89c80f367..7ecb0676b 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -82,15 +82,7 @@ export default async (endpoint: string, user: ILocalUser | null | undefined, tok if (e instanceof ApiError) { throw e; } else { - apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, { - ep: ep.name, - ps: data, - e: { - message: e.message, - code: e.name, - stack: e.stack, - }, - }); + apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`); throw new ApiError('INTERNAL_ERROR', { e: { message: e.message, diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index e8f3793b7..912ac25f6 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -60,10 +60,7 @@ export async function uploadFromUrl({ logger.succ(`Got: ${driveFile.id}`); return driveFile; } catch (e) { - logger.error(`Failed to create drive file: ${e}`, { - url, - e, - }); + logger.error(`Failed to create drive file: ${e}`); throw e; } finally { cleanup(); diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index ea5d321fa..7b0d1ce2b 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -84,7 +84,7 @@ export default class Logger { * @param important Whether to highlight this message as especially important. * @param subDomains Names of sub-loggers to be added. */ - private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], _store = true): void { + private log(level: Level, message: string, important = false, subDomains: Domain[] = [], _store = true): void { if (envOption.quiet) return; const store = _store && this.store; @@ -94,7 +94,7 @@ export default class Logger { // If this logger has a parent logger, delegate the actual logging to it, // so the parent domain(s) will be logged properly. if (this.parentLogger) { - this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store); + this.parentLogger.log(level, message, important, [this.domain].concat(subDomains), store); return; } @@ -154,17 +154,15 @@ export default class Logger { * Log an error message. * Use in situations where execution cannot be continued. * @param err Error or string containing an error message - * @param data Data relating to the error * @param important Whether this error is important */ - public error(err: string | Error, data: Record = {}, important = false): void { + public error(err: string | Error, important = false): void { if (err instanceof Error) { - data.e = err; - this.log(LEVELS.error, err.toString(), data, important); + this.log(LEVELS.error, err.toString(), important); } else if (typeof err === 'object') { - this.log(LEVELS.error, `${(err as any).message || (err as any).name || err}`, data, important); + this.log(LEVELS.error, `${(err as any).message || (err as any).name || err}`, important); } else { - this.log(LEVELS.error, `${err}`, data, important); + this.log(LEVELS.error, `${err}`, important); } } @@ -172,21 +170,19 @@ export default class Logger { * Log a warning message. * Use in situations where execution can continue but needs to be improved. * @param message Warning message - * @param data Data relating to the warning * @param important Whether this warning is important */ - public warn(message: string, data?: Record | null, important = false): void { - this.log(LEVELS.warning, message, data, important); + public warn(message: string, important = false): void { + this.log(LEVELS.warning, message, important); } /** * Log a success message. * Use in situations where something has been successfully done. * @param message Success message - * @param data Data relating to the success * @param important Whether this success message is important */ - public succ(message: string, data?: Record | null, important = false): void { + public succ(message: string, important = false): void { this.log(LEVELS.success, message, important); } @@ -194,21 +190,19 @@ export default class Logger { * Log a debug message. * Use for debugging (information needed by developers but not required by users). * @param message Debug message - * @param data Data relating to the debug message * @param important Whether this debug message is important */ - public debug(message: string, data?: Record | null, important = false): void { - this.log(LEVELS.debug, message, data, important); + public debug(message: string, important = false): void { + this.log(LEVELS.debug, message, important); } /** * Log an informational message. * Use when something needs to be logged but doesn't fit into other levels. * @param message Info message - * @param data Data relating to the info message * @param important Whether this info message is important */ - public info(message: string, data?: Record | null, important = false): void { - this.log(LEVELS.info, message, data, important); + public info(message: string, important = false): void { + this.log(LEVELS.info, message, important); } } From d6452795b0de2766a1f77208d538233c30f748a1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 19 May 2023 00:21:47 +0200 Subject: [PATCH 07/70] translate more comments --- .../backend/src/queue/processors/db/export-custom-emojis.ts | 2 +- packages/backend/src/server/file/send-drive-file.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index f31531db4..9aa503e43 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -71,7 +71,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi try { await downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; - } catch (e) { // TODO: 何度か再試行 + } catch (e) { // TODO: retry logger.error(e instanceof Error ? e : new Error(e as string)); } diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 0ae16d94c..f5934540a 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -49,7 +49,7 @@ export default async function(ctx: Koa.Context) { const isWebpublic = file.webpublicAccessKey === key; if (!file.storedInternal) { - if (file.isLink && file.uri) { // 期限切れリモートファイル + if (file.isLink && file.uri) { // expired remote file const [path, cleanup] = await createTemp(); try { From 410c519953d662e51806d4111bc71914511bc3d5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 19 May 2023 00:32:11 +0200 Subject: [PATCH 08/70] remove some default exports --- packages/backend/src/boot/master.ts | 2 +- packages/backend/src/config/load.ts | 2 +- packages/backend/src/server/file/index.ts | 2 +- packages/backend/src/server/file/send-drive-file.ts | 3 +-- packages/backend/src/server/web/feed.ts | 2 +- packages/backend/src/server/web/index.ts | 2 +- packages/backend/src/services/stream.ts | 4 +--- 7 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index f68a2d310..7a27deb9a 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -8,7 +8,7 @@ import chalkTemplate from 'chalk-template'; import semver from 'semver'; import Logger from '@/services/logger.js'; -import loadConfig from '@/config/load.js'; +import { loadConfig } from '@/config/load.js'; import { Config } from '@/config/types.js'; import { showMachineInfo } from '@/misc/show-machine-info.js'; import { envOption } from '@/env.js'; diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts index a6431162d..f16fb850b 100644 --- a/packages/backend/src/config/load.ts +++ b/packages/backend/src/config/load.ts @@ -23,7 +23,7 @@ const path = process.env.NODE_ENV === 'test' ? `${dir}/test.yml` : `${dir}/default.yml`; -export default function load(): Config { +export function loadConfig(): Config { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8')); const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8')); let config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; diff --git a/packages/backend/src/server/file/index.ts b/packages/backend/src/server/file/index.ts index 4c4707e61..56bf14f9a 100644 --- a/packages/backend/src/server/file/index.ts +++ b/packages/backend/src/server/file/index.ts @@ -8,7 +8,7 @@ import { dirname } from 'node:path'; import Koa from 'koa'; import cors from '@koa/cors'; import Router from '@koa/router'; -import sendDriveFile from './send-drive-file.js'; +import { sendDriveFile } from './send-drive-file.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index f5934540a..990dd98fe 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -27,8 +27,7 @@ const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => ctx.set('Cache-Control', 'max-age=300'); }; -// eslint-disable-next-line import/no-default-export -export default async function(ctx: Koa.Context) { +export async function sendDriveFile(ctx: Koa.Context) { const key = ctx.params.key; // Fetch drive file diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts index b83ccf188..497d677c6 100644 --- a/packages/backend/src/server/web/feed.ts +++ b/packages/backend/src/server/web/feed.ts @@ -4,7 +4,7 @@ import config from '@/config/index.js'; import { User } from '@/models/entities/user.js'; import { Notes, DriveFiles, UserProfiles, Users } from '@/models/index.js'; -export default async function(user: User) { +export async function packFeed(user: User) { const author = { link: `${config.url}/@${user.username}`, name: user.name || user.username, diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 7fe462d43..7c04077d4 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -26,7 +26,7 @@ import { MINUTE, DAY } from '@/const.js'; import { genOpenapiSpec } from '../api/openapi/gen-spec.js'; import { urlPreviewHandler } from './url-preview.js'; import { manifestHandler } from './manifest.js'; -import packFeed from './feed.js'; +import { packFeed } from './feed.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index 0119d7fdf..87ddee19d 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -95,9 +95,7 @@ class Publisher { }; } -const publisher = new Publisher(); - -export default publisher; +export const publisher = new Publisher(); export const publishInternalEvent = publisher.publishInternalEvent; export const publishUserEvent = publisher.publishUserEvent; From b0d8e15796bcbe463e170e98e5d52f2337802d58 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 19 May 2023 20:07:26 +0200 Subject: [PATCH 09/70] fixup: remove some default exports This is a fixup for commit 410c519953d662e51806d4111bc71914511bc3d5. --- packages/backend/src/config/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/config/index.ts b/packages/backend/src/config/index.ts index 3e53b0003..6b407f269 100644 --- a/packages/backend/src/config/index.ts +++ b/packages/backend/src/config/index.ts @@ -1,3 +1,3 @@ -import load from './load.js'; +import { loadConfig } from './load.js'; -export default load(); +export default loadConfig(); From 2c3f731ae245eef97f1893f904b103da005c877e Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 19 May 2023 23:08:06 +0200 Subject: [PATCH 10/70] remove more default exports --- packages/backend/src/server/api/api-handler.ts | 2 +- packages/backend/src/server/api/authenticate.ts | 4 ++-- packages/backend/src/server/api/call.ts | 2 +- packages/backend/src/server/api/endpoints.ts | 4 +--- packages/backend/src/server/api/endpoints/endpoint.ts | 2 +- packages/backend/src/server/api/endpoints/endpoints.ts | 2 +- packages/backend/src/server/api/index.ts | 2 +- packages/backend/src/server/api/openapi/gen-spec.ts | 2 +- packages/backend/src/server/api/streaming.ts | 2 +- 9 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts index de9af0890..3bde09602 100644 --- a/packages/backend/src/server/api/api-handler.ts +++ b/packages/backend/src/server/api/api-handler.ts @@ -1,7 +1,7 @@ import Koa from 'koa'; import { IEndpoint } from './endpoints.js'; -import authenticate, { AuthenticationError } from './authenticate.js'; +import { authenticate, AuthenticationError } from './authenticate.js'; import call from './call.js'; import { ApiError } from './error.js'; diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 9da253526..7daa91de4 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -11,7 +11,7 @@ export class AuthenticationError extends Error { } } -export default async (authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[ILocalUser | null | undefined, AccessToken | null | undefined]> => { +export async function authenticate(authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[ILocalUser | null | undefined, AccessToken | null | undefined]> { let maybeToken: string | null = null; // check if there is an authorization header set @@ -66,4 +66,4 @@ export default async (authorization: string | null | undefined, bodyToken: strin return [user, accessToken]; } -}; +} diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 7ecb0676b..ea8c6086e 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -4,7 +4,7 @@ import { ILocalUser } from '@/models/entities/user.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { limiter } from './limiter.js'; -import endpoints, { IEndpointMeta } from './endpoints.js'; +import { endpoints, IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 55763d875..a7757c2b5 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -713,7 +713,7 @@ export interface IEndpoint { params: Schema; } -const endpoints: IEndpoint[] = eps.map(([name, ep]) => { +export const endpoints: IEndpoint[] = eps.map(([name, ep]) => { return { name, exec: ep.default, @@ -721,5 +721,3 @@ const endpoints: IEndpoint[] = eps.map(([name, ep]) => { params: ep.paramDef, }; }); - -export default endpoints; diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index f380a5287..b5894c10d 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -1,5 +1,5 @@ import define from '@/server/api/define.js'; -import endpoints from '@/server/api/endpoints.js'; +import { endpoints } from '@/server/api/endpoints.js'; export const meta = { requireCredential: false, diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index 184f74e79..976109887 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -1,5 +1,5 @@ import define from '@/server/api/define.js'; -import endpoints from '@/server/api/endpoints.js'; +import { endpoints } from '@/server/api/endpoints.js'; export const meta = { requireCredential: false, diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index fe0c4450a..554596e3a 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -10,7 +10,7 @@ import cors from '@koa/cors'; import { Instances, AccessTokens, Users } from '@/models/index.js'; import config from '@/config/index.js'; -import endpoints from './endpoints.js'; +import { endpoints } from './endpoints.js'; import { handler } from './api-handler.js'; import signup from './private/signup.js'; import signin from './private/signin.js'; diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 0a7fcf667..9dc1c89cf 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -2,7 +2,7 @@ import config from '@/config/index.js'; import { kinds } from '@/misc/api-permissions.js'; import { I18n } from '@/misc/i18n.js'; import { errors as errorDefinitions } from '@/server/api/error.js'; -import endpoints from '@/server/api/endpoints.js'; +import { endpoints } from '@/server/api/endpoints.js'; import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; import { httpCodes } from './http-codes.js'; diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index 797443afb..eefbcc216 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -6,7 +6,7 @@ import { SECOND, MINUTE } from '@/const.js'; import { subscriber as redisClient } from '@/db/redis.js'; import { Users } from '@/models/index.js'; import { Connection } from './stream/index.js'; -import authenticate from './authenticate.js'; +import { authenticate } from './authenticate.js'; export const initializeStreamingServer = (server: http.Server): void => { // Init websocket server From ff66f48ea2ddea59550341ed854640b7d5b80b3f Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 19 May 2023 00:59:53 +0200 Subject: [PATCH 11/70] remove syslog closes https://akkoma.dev/FoundKeyGang/FoundKey/issues/19 Changelog: Removed --- .config/example.yml | 5 ----- packages/backend/package.json | 2 -- packages/backend/src/config/types.ts | 5 ----- packages/backend/src/services/logger.ts | 27 +------------------------ yarn.lock | 25 ----------------------- 5 files changed, 1 insertion(+), 63 deletions(-) diff --git a/.config/example.yml b/.config/example.yml index 4146881b1..8d4a162cb 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -108,11 +108,6 @@ redis: #deliverJobMaxAttempts: 12 #inboxJobMaxAttempts: 8 -# Syslog option -#syslog: -# host: localhost -# port: 514 - # Proxy for HTTP/HTTPS outgoing connections #proxy: http://127.0.0.1:3128 diff --git a/packages/backend/package.json b/packages/backend/package.json index 5d7df7cfa..d9c830baf 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -100,7 +100,6 @@ "stringz": "2.1.0", "style-loader": "3.3.1", "summaly": "2.7.0", - "syslog-pro": "1.0.0", "systeminformation": "5.11.22", "tinycolor2": "1.4.2", "tmp": "0.2.1", @@ -158,7 +157,6 @@ "@types/sinon": "^10.0.13", "@types/sinonjs__fake-timers": "8.1.2", "@types/speakeasy": "2.0.7", - "@types/syslog-pro": "^1.0.0", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", "@types/uuid": "8.3.4", diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 55226ca47..686a8c242 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -59,11 +59,6 @@ export type Source = { deliverJobMaxAttempts?: number; inboxJobMaxAttempts?: number; - syslog?: { - host: string; - port: number; - }; - mediaProxy?: string; proxyRemoteFiles?: boolean; internalStoragePath?: string; diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index 7b0d1ce2b..5c24186b6 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -2,7 +2,6 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; import convertColor from 'color-convert'; import { format as dateFormat } from 'date-fns'; -import * as SyslogPro from 'syslog-pro'; import config from '@/config/index.js'; import { envOption } from '@/env.js'; import type { KEYWORD } from 'color-convert/conversions.js'; @@ -22,13 +21,12 @@ export const LEVELS = { export type Level = LEVELS[keyof LEVELS]; /** - * Class that facilitates recording log messages to the console and optionally a syslog server. + * Class that facilitates recording log messages to the console. */ export default class Logger { private domain: Domain; private parentLogger: Logger | null = null; private store: boolean; - private syslogClient: SyslogPro.RFC5424 | null = null; /** * Messages below this level will be discarded. */ @@ -47,20 +45,6 @@ export default class Logger { }; this.store = store; this.minLevel = minLevel; - - if (config.syslog) { - this.syslogClient = new SyslogPro.RFC5424({ - applicationName: 'FoundKey', - timestamp: true, - includeStructuredData: true, - color: true, - extendedColor: true, - server: { - target: config.syslog.host, - port: config.syslog.port, - }, - }); - } } /** @@ -139,15 +123,6 @@ export default class Logger { if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log; console.log(important ? chalk.bold(log) : log); - - if (store && this.syslogClient) { - const send = - level === LEVELS.error ? this.syslogClient.error : - level === LEVELS.warning ? this.syslogClient.warning : - this.syslogClient.info; - - send.bind(this.syslogClient)(message).catch(() => {}); - } } /** diff --git a/yarn.lock b/yarn.lock index 8d225b0f4..1786f0c51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2443,13 +2443,6 @@ __metadata: languageName: node linkType: hard -"@types/syslog-pro@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/syslog-pro@npm:1.0.0" - checksum: d0dcd87efad8a629bba449f86a617605a3fbffa5c18a8b309c82e7b85036ac21cfd34711fd522f50528dd0f0d07bdb66261a6f9ef20f2a9133e847b2e717c1bc - languageName: node - linkType: hard - "@types/throttle-debounce@npm:5.0.0": version: 5.0.0 resolution: "@types/throttle-debounce@npm:5.0.0" @@ -3702,7 +3695,6 @@ __metadata: "@types/sinon": ^10.0.13 "@types/sinonjs__fake-timers": 8.1.2 "@types/speakeasy": 2.0.7 - "@types/syslog-pro": ^1.0.0 "@types/tinycolor2": 1.4.3 "@types/tmp": 0.2.3 "@types/uuid": 8.3.4 @@ -3792,7 +3784,6 @@ __metadata: stringz: 2.1.0 style-loader: 3.3.1 summaly: 2.7.0 - syslog-pro: 1.0.0 systeminformation: 5.11.22 tinycolor2: 1.4.2 tmp: 0.2.1 @@ -12044,13 +12035,6 @@ __metadata: languageName: node linkType: hard -"moment@npm:^2.22.2": - version: 2.29.4 - resolution: "moment@npm:2.29.4" - checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e - languageName: node - linkType: hard - "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -16141,15 +16125,6 @@ __metadata: languageName: node linkType: hard -"syslog-pro@npm:1.0.0": - version: 1.0.0 - resolution: "syslog-pro@npm:1.0.0" - dependencies: - moment: ^2.22.2 - checksum: 7d6399e4ca3a9305758f77b3e720469b39c156b5a8219ed4ce27b4ad8f960f8e395aebb0ccc84e4438b50a6b2cda2e20251e278307833ed7ac1045ae9516a33c - languageName: node - linkType: hard - "systeminformation@npm:5.11.22": version: 5.11.22 resolution: "systeminformation@npm:5.11.22" From eafacbba995daef6043125f215cf47263675e099 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sat, 20 May 2023 06:41:39 -0400 Subject: [PATCH 12/70] docs: fix typo in migration revert script --- docs/migrating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrating.md b/docs/migrating.md index 6308f667b..a44742508 100644 --- a/docs/migrating.md +++ b/docs/migrating.md @@ -20,7 +20,7 @@ cd packages/backend LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n nsfwDetection1655368940105 | cut -d ':' -f 1)" NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)" -for i in $(seq 1 $NUM_MIGRAIONS); do +for i in $(seq 1 $NUM_MIGRATIONS); do npx typeorm migration:revert -d ormconfig.js done ``` From ed9f2f49005450655b5caf9f745420fe0154e4e4 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 20 May 2023 00:22:38 +0200 Subject: [PATCH 13/70] server: refactor ffVisibility checks into function --- .../backend/src/models/repositories/user.ts | 42 +++++++++++++++---- .../src/server/activitypub/followers.ts | 13 ++---- .../src/server/activitypub/following.ts | 13 ++---- .../server/api/endpoints/users/followers.ts | 23 ++-------- .../server/api/endpoints/users/following.ts | 23 ++-------- 5 files changed, 46 insertions(+), 68 deletions(-) diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 10969a034..4387ed922 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -230,6 +230,34 @@ export const UserRepository = db.getRepository(User).extend({ return `${config.url}/identicon/${userId}`; }, + /** + * Determines whether the followers/following of user `user` are visibile to user `me`. + */ + async areFollowersVisibleTo(user: User, me: { id: User['id'] } | null | undefined): Promise { + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + + switch (profile.ffVisibility) { + case 'public': + return true; + case 'followers': + if (me == null) { + return false; + } else if (me.id === user.id) { + return true; + } else { + return await Followings.count({ + where: { + followerId: me.id, + followeeId: user.id, + }, + take: 1, + }).then(n => n > 0); + } + case 'private': + return me?.id === user.id; + } + } + async pack( src: User['id'] | User, me?: { id: User['id'] } | null | undefined, @@ -270,15 +298,13 @@ export const UserRepository = db.getRepository(User).extend({ .getMany() : []; const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null; - const followingCount = profile == null ? null : - (profile.ffVisibility === 'public') || isMe ? user.followingCount : - (profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followingCount : - null; + const ffVisible = await this.areFollowersVisibleTo(user, me); - const followersCount = profile == null ? null : - (profile.ffVisibility === 'public') || isMe ? user.followersCount : - (profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followersCount : - null; + const followingCount = opts.detail ? null : + ffVisible ? user.followingCount : null; + + const followersCount = opts.detail ? null : + ffVisible ? user.followersCount : null; const packed = { id: user.id, diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index beb48713a..2c2b6cfb4 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; +import { Users, Followings } from '@/models/index.js'; import { Following } from '@/models/entities/following.js'; import { setResponseType } from '../activitypub.js'; @@ -31,19 +31,12 @@ export default async (ctx: Router.RouterContext) => { return; } - //#region Check ff visibility - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); - return; - } else if (profile.ffVisibility === 'followers') { + const ffVisible = await Users.areFollowersVisibleTo(user, null); + if (!ffVisible) { ctx.status = 403; ctx.set('Cache-Control', 'public, max-age=30'); return; } - //#endregion const limit = 10; const partOf = `${config.url}/users/${userId}/followers`; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index 3a25a6316..4e156a19f 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; +import { Users, Followings } from '@/models/index.js'; import { Following } from '@/models/entities/following.js'; import { setResponseType } from '../activitypub.js'; @@ -31,19 +31,12 @@ export default async (ctx: Router.RouterContext) => { return; } - //#region Check ff visibility - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); - return; - } else if (profile.ffVisibility === 'followers') { + const ffVisible = await Users.areFollowersVisibleTo(user, null); + if (!ffVisible) { ctx.status = 403; ctx.set('Cache-Control', 'public, max-age=30'); return; } - //#endregion const limit = 10; const partOf = `${config.url}/users/${userId}/following`; diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 2595fbff5..e93851cd8 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,5 +1,5 @@ import { IsNull } from 'typeorm'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; +import { Users, Followings } from '@/models/index.js'; import { toPunyNullable } from '@/misc/convert-host.js'; import define from '@/server/api/define.js'; import { ApiError } from '@/server/api/error.js'; @@ -61,25 +61,8 @@ export default define(meta, paramDef, async (ps, me) => { if (user == null) throw new ApiError('NO_SUCH_USER'); - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError('ACCESS_DENIED'); - } - } else if (profile.ffVisibility === 'followers') { - if (me == null) { - throw new ApiError('ACCESS_DENIED'); - } else if (me.id !== user.id) { - const following = await Followings.countBy({ - followeeId: user.id, - followerId: me.id, - }); - if (!following) { - throw new ApiError('ACCESS_DENIED'); - } - } - } + const ffVisible = await Users.areFollowersVisibleTo(user, me); + if (!ffVisible) throw new ApiError('ACCESS_DENIED'); const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) .andWhere('following.followeeId = :userId', { userId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 0bf60c079..406853423 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,5 +1,5 @@ import { IsNull } from 'typeorm'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; +import { Users, Followings } from '@/models/index.js'; import { toPunyNullable } from '@/misc/convert-host.js'; import define from '@/server/api/define.js'; import { ApiError } from '@/server/api/error.js'; @@ -61,25 +61,8 @@ export default define(meta, paramDef, async (ps, me) => { if (user == null) throw new ApiError('NO_SUCH_USER'); - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { - throw new ApiError('ACCESS_DENIED'); - } - } else if (profile.ffVisibility === 'followers') { - if (me == null) { - throw new ApiError('ACCESS_DENIED'); - } else if (me.id !== user.id) { - const following = await Followings.countBy({ - followeeId: user.id, - followerId: me.id, - }); - if (!following) { - throw new ApiError('ACCESS_DENIED'); - } - } - } + const ffVisible = await Users.areFollowersVisibleTo(user, me); + if (!ffVisible) throw new ApiError('ACCESS_DENIED'); const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) .andWhere('following.followerId = :userId', { userId: user.id }) From ded48c96d83391805c5eb8bac368b2b149d46174 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 20 May 2023 00:23:18 +0200 Subject: [PATCH 14/70] server: use foundkey-js definition of ffVisibilities --- packages/backend/src/server/api/endpoints/i/update.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index c7f7d50c8..1a73e31df 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -1,6 +1,6 @@ import RE2 from 're2'; import * as mfm from 'mfm-js'; -import { notificationTypes } from 'foundkey-js'; +import { ffVisibility, notificationTypes } from 'foundkey-js'; import { publishMainStream, publishUserEvent } from '@/services/stream.js'; import { acceptAllFollowRequests } from '@/services/following/requests/accept-all.js'; import { publishToFollowers } from '@/services/i/update.js'; @@ -67,7 +67,7 @@ export const paramDef = { injectFeaturedNote: { type: 'boolean' }, receiveAnnouncementEmail: { type: 'boolean' }, alwaysMarkNsfw: { type: 'boolean' }, - ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] }, + ffVisibility: { type: 'string', enum: ffVisibility }, pinnedPageId: { type: 'array', items: { type: 'string', format: 'misskey:id', } }, From ef4055840b5b69472b2a6f8ab2ed478c7c976a3a Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 20 May 2023 00:25:41 +0200 Subject: [PATCH 15/70] BREAKING server: respect ffVisibility on stats endpoint This makes the returned values `localFollowingCount`, `remoteFollowingCount`, `followingCount`, `localFollowersCount`, `remotefollowersCount`, `followersCount` optional on the API endpoint `users/stats`. Changelog: Fixed --- .../src/server/api/endpoints/users/stats.ts | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 970308f78..7197219fb 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -46,27 +46,27 @@ export const meta = { }, localFollowingCount: { type: 'integer', - optional: false, nullable: false, + optional: true, nullable: false, }, remoteFollowingCount: { type: 'integer', - optional: false, nullable: false, + optional: true, nullable: false, }, localFollowersCount: { type: 'integer', - optional: false, nullable: false, + optional: true, nullable: false, }, remoteFollowersCount: { type: 'integer', - optional: false, nullable: false, + optional: true, nullable: false, }, followingCount: { type: 'integer', - optional: false, nullable: false, + optional: true, nullable: false, }, followersCount: { type: 'integer', - optional: false, nullable: false, + optional: true, nullable: false, }, sentReactionsCount: { type: 'integer', @@ -110,7 +110,7 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps) => { +export default define(meta, paramDef, async (ps, me) => { const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { throw new ApiError('NO_SUCH_USER'); @@ -141,22 +141,6 @@ export default define(meta, paramDef, async (ps) => { .innerJoin('vote.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - localFollowingCount: Followings.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NULL') - .getCount(), - remoteFollowingCount: Followings.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NOT NULL') - .getCount(), - localFollowersCount: Followings.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NULL') - .getCount(), - remoteFollowersCount: Followings.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NOT NULL') - .getCount(), sentReactionsCount: NoteReactions.createQueryBuilder('reaction') .where('reaction.userId = :userId', { userId: user.id }) .getCount(), @@ -180,8 +164,32 @@ export default define(meta, paramDef, async (ps) => { driveUsage: DriveFiles.calcDriveUsageOf(user.id), }); - result.followingCount = result.localFollowingCount + result.remoteFollowingCount; - result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + const ffVisible = await Users.areFollowersVisibleTo(user, me); + if (ffVisible) { + const follows = await awaitAll({ + localFollowingCount: Followings.createQueryBuilder('following') + .where('following.followerId = :userId', { userId: user.id }) + .andWhere('following.followeeHost IS NULL') + .getCount(), + remoteFollowingCount: Followings.createQueryBuilder('following') + .where('following.followerId = :userId', { userId: user.id }) + .andWhere('following.followeeHost IS NOT NULL') + .getCount(), + localFollowersCount: Followings.createQueryBuilder('following') + .where('following.followeeId = :userId', { userId: user.id }) + .andWhere('following.followerHost IS NULL') + .getCount(), + remoteFollowersCount: Followings.createQueryBuilder('following') + .where('following.followeeId = :userId', { userId: user.id }) + .andWhere('following.followerHost IS NOT NULL') + .getCount(), + }); + + Object.assign(result, follows); + + result.followingCount = result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + } return result; }); From fe65cba9bec2c9cf87a51e767c22ba23ccb9e747 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 20 May 2023 00:32:18 +0200 Subject: [PATCH 16/70] add "nobody" follower visibility Adds a new follower/following visibility that hides followers even from yourself. Changelog: Added --- locales/en-US.yml | 1 + .../1684536337602-ffVisibilityNobody.js | 21 +++++++++++++++++++ .../backend/src/models/repositories/user.ts | 2 ++ .../client/src/pages/settings/privacy.vue | 1 + packages/foundkey-js/src/consts.ts | 2 +- packages/foundkey-js/src/entities.ts | 2 +- 6 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 packages/backend/migration/1684536337602-ffVisibilityNobody.js diff --git a/locales/en-US.yml b/locales/en-US.yml index fa0d9d28e..fe1dc137e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -843,6 +843,7 @@ _ffVisibility: public: "Public" followers: "Visible to followers only" private: "Private" + nobody: "Nobody (not even you)" _signup: almostThere: "Almost there" emailAddressInfo: "Please enter your email address. It will not be made public." diff --git a/packages/backend/migration/1684536337602-ffVisibilityNobody.js b/packages/backend/migration/1684536337602-ffVisibilityNobody.js new file mode 100644 index 000000000..8998e7d24 --- /dev/null +++ b/packages/backend/migration/1684536337602-ffVisibilityNobody.js @@ -0,0 +1,21 @@ +export class ffVisibilityNobody1684536337602 { + name = 'ffVisibilityNobody1684536337602'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TYPE "public"."user_profile_ffvisibility_enum" RENAME TO "user_profile_ffvisibility_enum_old"`); + await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private', 'nobody')`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" TYPE "public"."user_profile_ffvisibility_enum" USING "ffVisibility"::"text"::"public"."user_profile_ffvisibility_enum"`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" SET DEFAULT 'public'`); + await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum_old"`); + } + + async down(queryRunner) { + await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum_old" AS ENUM('public', 'followers', 'private')`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" TYPE "public"."user_profile_ffvisibility_enum_old" USING "ffVisibility"::"text"::"public"."user_profile_ffvisibility_enum_old"`); + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" SET DEFAULT 'public'`); + await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum"`); + await queryRunner.query(`ALTER TYPE "public"."user_profile_ffvisibility_enum_old" RENAME TO "user_profile_ffvisibility_enum"`); + } +} diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 4387ed922..aea591229 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -255,6 +255,8 @@ export const UserRepository = db.getRepository(User).extend({ } case 'private': return me?.id === user.id; + case 'nobody': + return false; } } diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index f8b02efb6..54ee1cf8e 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -16,6 +16,7 @@ + diff --git a/packages/foundkey-js/src/consts.ts b/packages/foundkey-js/src/consts.ts index a7ea18d25..ed9fd8e53 100644 --- a/packages/foundkey-js/src/consts.ts +++ b/packages/foundkey-js/src/consts.ts @@ -4,7 +4,7 @@ export const noteNotificationTypes = ['mention', 'reply', 'renote', 'quote', 're export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; -export const ffVisibility = ['public', 'followers', 'private'] as const; +export const ffVisibility = ['public', 'followers', 'private', 'nobody'] as const; export const permissions = [ 'read:account', diff --git a/packages/foundkey-js/src/entities.ts b/packages/foundkey-js/src/entities.ts index 5cec05c99..a83a8c123 100644 --- a/packages/foundkey-js/src/entities.ts +++ b/packages/foundkey-js/src/entities.ts @@ -38,7 +38,7 @@ export type UserDetailed = UserLite & { birthday: string | null; createdAt: DateString; description: string | null; - ffVisibility: 'public' | 'followers' | 'private'; + ffVisibility: 'public' | 'followers' | 'private' | 'nobody'; fields: {name: string; value: string}[]; followersCount: number; followingCount: number; From 6886ddb689a43af267f2676bc86ca5fdd8da74e6 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sat, 20 May 2023 00:16:46 -0400 Subject: [PATCH 17/70] docs: reformat warnings and update Misskey references in emoji docs --- docs/emoji.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/emoji.md b/docs/emoji.md index 257cbc39d..abfd13632 100644 --- a/docs/emoji.md +++ b/docs/emoji.md @@ -18,12 +18,11 @@ Please note that Emoji may be subject to copyright and you are responsible for c If you have an image file that you would like to turn into a custom emoji you can import the image as an emoji. This works just like attaching files to a note: -You can choose to upload a new file, pick a file from your Misskey drive or upload a file from another URL. +You can choose to upload a new file, pick a file from your Foundkey drive or upload a file from another URL. -::: danger +**Warning:** When you import emoji from your drive, the file will remain inside your drive. -Misskey does not make a copy of this file so if you delete it, the emoji will be broken. -::: +Foundkey does not make a copy of this file so if you delete it, the emoji will be broken. The emoji will be added to the instance and you will then be able to edit or delete it as usual. @@ -32,10 +31,9 @@ The emoji will be added to the instance and you will then be able to edit or del Emojis can be imported in bulk as packed ZIP files with a special format. This ability can be found in the three dots menu in the top right corner of the custom emoji menu. -::: warning +**Warning:** Bulk emoji import may overwrite existing emoji or otherwise mess up your instance. Be sure to only import emoji from trusted sources, ideally only ones you exported yourself. -::: ### Packed emoji format @@ -89,10 +87,9 @@ The properties of an emoji can be edited by clicking it in the list of local emo When you click on a custom emoji, a dialog for editing the properties will open. This dialog will also allow you to delete an emoji. -::: danger +**Warning:** When you delete a custom emoji, old notes that contain it will still have the text name of the emoji in it. The emoji will no longer be rendered correctly. -::: Note that remote emoji can not be edited or deleted. From 38193cd3e5cdc73a1dcef808887393f22d46115b Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 20 May 2023 23:38:23 +0200 Subject: [PATCH 18/70] fixup: typo in ffVisibility setting This is a fixup for commit fe65cba9bec2c9cf87a51e767c22ba23ccb9e747. --- packages/client/src/pages/settings/privacy.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index 54ee1cf8e..7c99bf83a 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -16,7 +16,7 @@ - + From 7a94e9f2d5804b6331272cc5d9db2ff12d8fde05 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 21 May 2023 00:13:09 +0200 Subject: [PATCH 19/70] remove overstriking from korean translation --- locales/ko-KR.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 69a2ad1af..a729d0a3a 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -842,8 +842,8 @@ _ffVisibility: private: "비공개" _signup: almostThere: "거의 다 끝났습니다" - emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다." - emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요." + emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다." + emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요." _accountDelete: accountDelete: "계정 삭제" mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다." From 296c40c5b43b7a0f796b2076911b418b9e65530f Mon Sep 17 00:00:00 2001 From: Ignas Kiela Date: Tue, 23 May 2023 10:39:42 +0300 Subject: [PATCH 20/70] fix: stop sending pings on every pong This resulted in endless ping-pong traffic on the websocket, happening every interval of network latency to the server (e.g. for me, with 40ms latency to my server, it was about every 40ms). On my server this ended up taking about 20% of foundkey's CPU usage. Now, just send pings every 30s, and check if we have received any pong's in last 60 seconds to check that the connection is still alive. Changelog: Fixed --- packages/backend/src/server/api/streaming.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index eefbcc216..5a1c79831 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -44,18 +44,21 @@ export const initializeStreamingServer = (server: http.Server): void => { const main = new Connection(socket, ev, user, app); // ping/pong mechanism - let pingTimeout = null; - function startHeartbeat() { - if (pingTimeout) clearTimeout(pingTimeout); - + let pingTimeout: NodeJS.Timeout | null = null; + let disconnectTimeout = setTimeout(() => { + socket.terminate(); + }, 60 * SECOND);; + function sendPing() { socket.ping(); pingTimeout = setTimeout(() => { - socket.terminate(); + sendPing(); }, 30 * SECOND); } - startHeartbeat(); - socket.on('ping', () => { startHeartbeat(); }); - socket.on('pong', () => { startHeartbeat(); }); + function onPong() { + disconnectTimeout.refresh() + } + sendPing(); + socket.on('pong', onPong); // keep user "online" while a stream is connected const intervalId = user ? setInterval(() => { @@ -75,6 +78,7 @@ export const initializeStreamingServer = (server: http.Server): void => { redisClient.off('message', onRedisMessage); if (intervalId) clearInterval(intervalId); if (pingTimeout) clearTimeout(pingTimeout); + if (disconnectTimeout) clearTimeout(disconnectTimeout); }); }); }); From 6b9a3259f5b4da2066131ca3ae49fc991b129350 Mon Sep 17 00:00:00 2001 From: Ignas Kiela Date: Tue, 23 May 2023 12:09:15 +0300 Subject: [PATCH 21/70] fix: log levels order Most important messages are the ones with the highest value This is a fixup for commit c5327f74d42a811e4a924b0f72b8cd8aaa0d0b71. --- packages/backend/src/services/logger.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index 5c24186b6..9d4190ca3 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -12,11 +12,11 @@ type Domain = { }; export const LEVELS = { - error: 0, - warning: 1, - success: 2, - info: 3, - debug: 4, + error: 5, + warning: 4, + success: 3, + info: 2, + debug: 1, }; export type Level = LEVELS[keyof LEVELS]; From 38c2d86983876241f5605978605cb11941bec07f Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 19:43:04 +0200 Subject: [PATCH 22/70] add log level environment variable To avoid a circular dependency this requires moving the log level definitions. Also to avoid a circular dependency the env.ts file cannot use a logger and instead uses plain `console.log`. --- packages/backend/src/env.ts | 25 +++++++++++++--- packages/backend/src/services/logger.ts | 39 ++++++++++--------------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts index 1b678edc4..f4aae6161 100644 --- a/packages/backend/src/env.ts +++ b/packages/backend/src/env.ts @@ -1,4 +1,12 @@ -const envOption = { +export const LOG_LEVELS = { + error: 5, + warning: 4, + success: 3, + info: 2, + debug: 1, +}; + +export const envOption = { onlyQueue: false, onlyServer: false, noDaemons: false, @@ -7,14 +15,23 @@ const envOption = { withLogTime: false, quiet: false, slow: false, + logLevel: LOG_LEVELS.info, }; for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { - if (process.env['MK_' + key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()]) envOption[key] = true; + const value = process.env['MK_' + key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()]; + if (value) { + if (key === 'logLevel') { + if (value.toLowerCase() in LOG_LEVELS) { + envOption.logLevel = LOG_LEVELS[value.toLowerCase()]; + } + console.log('Unknown log level ' + JSON.stringify(value.toLowerCase()) + ', defaulting to "info"'); + } else { + envOption[key] = true; + } + } } if (process.env.NODE_ENV === 'test') envOption.disableClustering = true; if (process.env.NODE_ENV === 'test') envOption.quiet = true; if (process.env.NODE_ENV === 'test') envOption.noDaemons = true; - -export { envOption }; diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index 9d4190ca3..070716286 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import convertColor from 'color-convert'; import { format as dateFormat } from 'date-fns'; import config from '@/config/index.js'; -import { envOption } from '@/env.js'; +import { envOption, LOG_LEVELS } from '@/env.js'; import type { KEYWORD } from 'color-convert/conversions.js'; type Domain = { @@ -11,14 +11,7 @@ type Domain = { color?: KEYWORD; }; -export const LEVELS = { - error: 5, - warning: 4, - success: 3, - info: 2, - debug: 1, -}; -export type Level = LEVELS[keyof LEVELS]; +export type Level = LOG_LEVELS[keyof LOG_LEVELS]; /** * Class that facilitates recording log messages to the console. @@ -38,7 +31,7 @@ export default class Logger { * @param color Log message color * @param store Whether to store messages */ - constructor(domain: string, color?: KEYWORD, store = true, minLevel: Level = LEVELS.info) { + constructor(domain: string, color?: KEYWORD, store = true, minLevel: Level = LOG_LEVELS.info) { this.domain = { name: domain, color, @@ -54,7 +47,7 @@ export default class Logger { * @param store Whether to store messages * @returns A Logger instance whose parent logger is this instance. */ - public createSubLogger(domain: string, color?: KEYWORD, store = true, minLevel: Level = LEVELS.info): Logger { + public createSubLogger(domain: string, color?: KEYWORD, store = true, minLevel: Level = LOG_LEVELS.info): Logger { const logger = new Logger(domain, color, store, minLevel); logger.parentLogger = this; return logger; @@ -89,7 +82,7 @@ export default class Logger { let levelDisplay; let messageDisplay; switch (level) { - case LEVELS.error: + case LOG_LEVELS.error: if (important) { levelDisplay = chalk.bgRed.white('ERR '); } else { @@ -97,11 +90,11 @@ export default class Logger { } messageDisplay = chalk.red(message); break; - case LEVELS.warning: + case LOG_LEVELS.warning: levelDisplay = chalk.yellow('WARN'); messageDisplay = chalk.yellow(message); break; - case LEVELS.success: + case LOG_LEVELS.success: if (important) { levelDisplay = chalk.bgGreen.white('DONE'); } else { @@ -109,11 +102,11 @@ export default class Logger { } messageDisplay = chalk.green(message); break; - case LEVELS.info: + case LOG_LEVELS.info: levelDisplay = chalk.blue('INFO'); messageDisplay = message; break; - case LEVELS.debug: default: + case LOG_LEVELS.debug: default: levelDisplay = chalk.gray('VERB'); messageDisplay = chalk.gray(message); break; @@ -133,11 +126,11 @@ export default class Logger { */ public error(err: string | Error, important = false): void { if (err instanceof Error) { - this.log(LEVELS.error, err.toString(), important); + this.log(LOG_LEVELS.error, err.toString(), important); } else if (typeof err === 'object') { - this.log(LEVELS.error, `${(err as any).message || (err as any).name || err}`, important); + this.log(LOG_LEVELS.error, `${(err as any).message || (err as any).name || err}`, important); } else { - this.log(LEVELS.error, `${err}`, important); + this.log(LOG_LEVELS.error, `${err}`, important); } } @@ -148,7 +141,7 @@ export default class Logger { * @param important Whether this warning is important */ public warn(message: string, important = false): void { - this.log(LEVELS.warning, message, important); + this.log(LOG_LEVELS.warning, message, important); } /** @@ -158,7 +151,7 @@ export default class Logger { * @param important Whether this success message is important */ public succ(message: string, important = false): void { - this.log(LEVELS.success, message, important); + this.log(LOG_LEVELS.success, message, important); } /** @@ -168,7 +161,7 @@ export default class Logger { * @param important Whether this debug message is important */ public debug(message: string, important = false): void { - this.log(LEVELS.debug, message, important); + this.log(LOG_LEVELS.debug, message, important); } /** @@ -178,6 +171,6 @@ export default class Logger { * @param important Whether this info message is important */ public info(message: string, important = false): void { - this.log(LEVELS.info, message, important); + this.log(LOG_LEVELS.info, message, important); } } From 239a52eb995caf8b6d60a1742209b49fab918f1e Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 19:45:33 +0200 Subject: [PATCH 23/70] add "quiet" log level This log level replaces the "MK_QUIET" environment variable to unify the interface in a sensible way. This also removes the "MK_VERBOSE" environment variable which was unused. --- packages/backend/src/boot/index.ts | 4 ++-- packages/backend/src/boot/master.ts | 4 ++-- packages/backend/src/env.ts | 11 ++++++----- packages/backend/src/services/logger.ts | 1 - 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index 46dc2d258..2580920f1 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import Xev from 'xev'; import Logger from '@/services/logger.js'; -import { envOption } from '@/env.js'; +import { envOption, LOG_LEVELS } from '@/env.js'; // for typeorm import 'reflect-metadata'; @@ -66,7 +66,7 @@ cluster.on('exit', worker => { }); // Display detail of unhandled promise rejection -if (!envOption.quiet) { +if (envOption.logLevel !== LOG_LEVELS.quiet) { process.on('unhandledRejection', console.dir); } diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 7a27deb9a..26303d140 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -11,7 +11,7 @@ import Logger from '@/services/logger.js'; import { loadConfig } from '@/config/load.js'; import { Config } from '@/config/types.js'; import { showMachineInfo } from '@/misc/show-machine-info.js'; -import { envOption } from '@/env.js'; +import { envOption, LOG_LEVELS } from '@/env.js'; import { db, initDb } from '@/db/postgre.js'; const _filename = fileURLToPath(import.meta.url); @@ -25,7 +25,7 @@ const bootLogger = logger.createSubLogger('boot', 'magenta', false); const themeColor = chalk.hex('#86b300'); function greet(): void { - if (!envOption.quiet) { + if (envOption.logLevel !== LOG_LEVELS.quiet) { //#region FoundKey logo console.log(themeColor(' ___ _ _ __ ')); console.log(themeColor(' | __|__ _ _ _ _ __| | |/ /___ _ _ ')); diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts index f4aae6161..586a2fb62 100644 --- a/packages/backend/src/env.ts +++ b/packages/backend/src/env.ts @@ -1,4 +1,5 @@ export const LOG_LEVELS = { + quiet: 6, error: 5, warning: 4, success: 3, @@ -11,9 +12,7 @@ export const envOption = { onlyServer: false, noDaemons: false, disableClustering: false, - verbose: false, withLogTime: false, - quiet: false, slow: false, logLevel: LOG_LEVELS.info, }; @@ -32,6 +31,8 @@ for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { } } -if (process.env.NODE_ENV === 'test') envOption.disableClustering = true; -if (process.env.NODE_ENV === 'test') envOption.quiet = true; -if (process.env.NODE_ENV === 'test') envOption.noDaemons = true; +if (process.env.NODE_ENV === 'test') { + envOption.disableClustering = true; + envOption.logLevel = LOG_LEVELS.quiet; + envOption.noDaemons = true; +} diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index 070716286..67fb8407c 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -62,7 +62,6 @@ export default class Logger { * @param subDomains Names of sub-loggers to be added. */ private log(level: Level, message: string, important = false, subDomains: Domain[] = [], _store = true): void { - if (envOption.quiet) return; const store = _store && this.store; // Check against the configured log level. From e6a8173378bb60ff34e3ab2112e55a205500dee3 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 19:48:48 +0200 Subject: [PATCH 24/70] refactor onlyQueue and onlyServer configuration Instead of checking this configuration in the respective component (queue) or not at all (server), the configuration can be checked when starting the respective workers. --- packages/backend/src/boot/master.ts | 17 +++++++++++++---- packages/backend/src/queue/index.ts | 3 --- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 26303d140..c53dc12b1 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -141,15 +141,24 @@ async function connectDb(): Promise { async function spawnWorkers(clusterLimits: Required): Promise { const modes = ['web' as const, 'queue' as const]; + + const clusters = structuredClone(clusterLimits); + + if (envOption.onlyQueue) { + clusters.web = 0; + } else if (envOption.onlyServer) { + clusters.queue = 0; + } + const cpus = os.cpus().length; - for (const mode of modes.filter(mode => clusterLimits[mode] > cpus)) { + for (const mode of modes.filter(mode => clusters[mode] > cpus)) { bootLogger.warn(`configuration warning: cluster limit for ${mode} exceeds number of cores (${cpus})`); } - const total = modes.reduce((acc, mode) => acc + clusterLimits[mode], 0); + const total = modes.reduce((acc, mode) => acc + clusters[mode], 0); const workers = new Array(total); - workers.fill('web', 0, clusterLimits.web); - workers.fill('queue', clusterLimits.web); + workers.fill('web', 0, clusters.web); + workers.fill('queue', clusters.web); bootLogger.info(`Starting ${total} workers...`); await Promise.all(workers.map(mode => spawnWorker(mode))); diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 558f54ff0..0d0a89f14 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -5,7 +5,6 @@ import config from '@/config/index.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js'; import { IActivity } from '@/remote/activitypub/type.js'; -import { envOption } from '@/env.js'; import { MINUTE } from '@/const.js'; import processDeliver from './processors/deliver.js'; @@ -289,8 +288,6 @@ export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[ } export default function() { - if (envOption.onlyServer) return; - deliverQueue.process(config.deliverJobConcurrency, processDeliver); inboxQueue.process(config.inboxJobConcurrency, processInbox); endedPollNotificationQueue.process(endedPollNotification); From efa8305e0b6d05f146dee5acdbbedb1a92f604ca Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 19:49:10 +0200 Subject: [PATCH 25/70] BREAKING rename environment variables from MK_... to FK_... Changelog: Changed --- packages/backend/src/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts index 586a2fb62..e89b83567 100644 --- a/packages/backend/src/env.ts +++ b/packages/backend/src/env.ts @@ -18,7 +18,7 @@ export const envOption = { }; for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { - const value = process.env['MK_' + key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()]; + const value = process.env['FK_' + key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()]; if (value) { if (key === 'logLevel') { if (value.toLowerCase() in LOG_LEVELS) { From 4a77e93dfd41d378213444fe555ddca573f62c4c Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 19:51:55 +0200 Subject: [PATCH 26/70] document environment variables --- docs/install-docker.md | 3 ++- docs/install.md | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/install-docker.md b/docs/install-docker.md index 9444c5ace..435b09bd6 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -38,10 +38,11 @@ cp .config/docker_example.env .config/docker.env Edit `default.yml` and `docker.env` according to the instructions in the files. You will need to set the database host to `db` and Redis host to `redis` in order to use the internal container network for these services. - Edit `docker-compose.yml` if necessary. (e.g. if you want to change the port). If you are using SELinux (eg. you're on Fedora or a RHEL derivative), you'll want to add the `Z` mount flag to the volume mounts to allow the containers to access the contents of those volumes. +Also check out the [Configure Foundkey](./install.md#configure-foundkey) section in the ordinary installation instructions. + ## Build and initialize The following command will build FoundKey and initialize the database. This will take some time. diff --git a/docs/install.md b/docs/install.md index 055c5a590..ce5452a1d 100644 --- a/docs/install.md +++ b/docs/install.md @@ -78,6 +78,21 @@ There are instructions for setting up [nginx](./nginx.md) for this purpose. ### Changing the default Reaction You can change the default reaction that is used when an ActivityPub "Like" is received from '👍' to '⭐' by changing the boolean value `meta.useStarForReactionFallback` in the databse respectively. +### Environment variables +There are some behaviour changes which can be accomplished using environment variables. + +|variable name|meaning| +|---|---| +|`FK_ONLY_QUEUE`|If set, only the queue processing will be run. The frontend will not be available. Cannot be combined with `FK_ONLY_SERVER` or `FK_DISABLE_CLUSTERING`.| +|`FK_ONLY_SERVER`|If set, only the frontend will be run. Queues will not be processed. Cannot be combined with `FK_ONLY_QUEUE` or `FK_DISABLE_CLUSTERING`.| +|`FK_NO_DAEMONS`|If set, the server statistics and queue statistics will not be run.| +|`FK_DISABLE_CLUSTERING`|If set, all work will be done in a single thread instead of different threads for frontend and queue. (not recommended)| +|`FK_WITH_LOG_TIME`|If set, a timestamp will be appended to all log messages.| +|`FK_SLOW`|If set, all requests will be delayed by 3s. (not recommended, useful for testing)| +|`FK_LOG_LEVEL`|Sets the log level. Messages below the set log level will be suppressed. Available log levels are `quiet` (suppress all), `error`, `warning`, `success`, `info`, `debug`.| + +If the `NODE_ENV` environment variable is set to `testing`, then the flags `FK_DISABLE_CLUSTERING` and `FK_NO_DAEMONS` will always be set, and the log level will always be `quiet`. + ## Build FoundKey Build foundkey with the following: From 2fde652b4a77061a9e6e85673e694234f0747212 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Mon, 9 Jan 2023 21:27:05 +0100 Subject: [PATCH 27/70] server: fix drive quota for remote users This deletes as many files as necessary to ensure the drive quota for remote users is kept. Previously only one file would have been deleted for each file added. Changelog: Fixed Co-authored-by: CGsama Co-authored-by: tamaina --- .../backend/src/services/drive/add-file.ts | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 9cdade177..25ca7103c 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -2,9 +2,10 @@ import * as fs from 'node:fs'; import { v4 as uuid } from 'uuid'; import S3 from 'aws-sdk/clients/s3.js'; -import { IsNull } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import sharp from 'sharp'; +import { db } from '@/db/postgre.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import { publishMainStream, publishDriveStream } from '@/services/stream.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; @@ -290,25 +291,36 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, _type: string if (result) logger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); } -async function deleteOldFile(user: IRemoteUser): Promise { - const q = DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId', { userId: user.id }) - .andWhere('NOT file.isLink'); +async function expireOldFiles(user: IRemoteUser, driveCapacity: number): Promise { + // Delete as many files as necessary so the total usage is below driveCapacity, + // oldest files first, and exclude avatar and banner. + // + // Using a window function, i.e. `OVER (ORDER BY "createdAt" DESC)` means that + // the `SUM` will be a running total. + const exceededFileIds = await db.query('SELECT "id" FROM (' + + 'SELECT "id", SUM("size") OVER (ORDER BY "createdAt" DESC) AS "total" FROM "drive_file" WHERE "userId" = $1 AND NOT "isLink"' + + (user.avatarId ? ' AND "id" != $2' : '') + + (user.bannerId ? ' AND "id" != $3' : '') + + ') AS "totals" WHERE "total" > $4', + [ + user.id, + user.avatarId ?? '', + user.bannerId ?? '', + driveCapacity, + ] + ); - if (user.avatarId) { - q.andWhere('file.id != :avatarId', { avatarId: user.avatarId }); + if (exceededFileIds.length === 0) { + // no files to expire, avatar and banner if present are already the only files + throw new Error('remote user drive quota met by avatar and banner'); } - if (user.bannerId) { - q.andWhere('file.id != :bannerId', { bannerId: user.bannerId }); - } + const files = await DriveFiles.findBy({ + id: In(exceededFileIds.map(x => x.id)), + }); - q.orderBy('file.id', 'ASC'); - - const oldFile = await q.getOne(); - - if (oldFile) { - deleteFile(oldFile, true); + for (const file of files) { + deleteFile(file, true); } } @@ -373,19 +385,20 @@ export async function addFile({ //#region Check drive usage if (user && !isLink) { const usage = await DriveFiles.calcDriveUsageOf(user.id); + const isLocalUser = Users.isLocalUser(user); const instance = await fetchMeta(); - const driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb); + const driveCapacity = 1024 * 1024 * (isLocalUser ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb); logger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); // If usage limit exceeded if (usage + info.size > driveCapacity) { - if (Users.isLocalUser(user)) { + if (isLocalUser) { throw new Error('no-free-space'); } else { - // delete oldest file (excluding banner and avatar) - deleteOldFile(await Users.findOneByOrFail({ id: user.id }) as IRemoteUser); + // delete older files to make space for new file + expireOldFiles(await Users.findOneByOrFail({ id: user.id }) as IRemoteUser, driveCapacity - info.size); } } } From 431239316983ced7ffde03cacd7b7a87371b5f92 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 1 Feb 2023 23:22:26 +0100 Subject: [PATCH 28/70] Revert 'Revert "server: fix user deletion race condition"' This reverts commit bb3ec8bafe5d16eea061929271f72032c8aeb6f3. --- .../1673201544000-deletion-progress.js | 18 +++++++++++++ packages/backend/src/models/entities/user.ts | 8 +++--- .../backend/src/models/repositories/user.ts | 2 +- packages/backend/src/queue/index.ts | 26 +++++++++++++++---- .../queue/processors/system/check-expired.ts | 7 ++++- packages/backend/src/queue/types.ts | 2 ++ .../src/remote/activitypub/deliver-manager.ts | 18 ++++++++----- .../remote/activitypub/kernel/delete/actor.ts | 2 +- .../api/endpoints/admin/users/delete.ts | 1 + .../server/api/endpoints/i/delete-account.ts | 2 +- .../backend/src/services/delete-account.ts | 2 +- packages/backend/src/services/suspend-user.ts | 5 +++- 12 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 packages/backend/migration/1673201544000-deletion-progress.js diff --git a/packages/backend/migration/1673201544000-deletion-progress.js b/packages/backend/migration/1673201544000-deletion-progress.js new file mode 100644 index 000000000..90aa5cbf8 --- /dev/null +++ b/packages/backend/migration/1673201544000-deletion-progress.js @@ -0,0 +1,18 @@ +export class deletionProgress1673201544000 { + name = 'deletionProgress1673201544000'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" RENAME COLUMN "isDeleted" TO "isDeletedOld"`); + await queryRunner.query(`ALTER TABLE "user" ADD "isDeleted" integer`); + await queryRunner.query(`UPDATE "user" SET "isDeleted" = CASE WHEN "host" IS NULL THEN -1 ELSE 0 END WHERE "isDeletedOld"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isDeletedOld"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" RENAME COLUMN "isDeleted" TO "isDeletedOld"`); + await queryRunner.query(`ALTER TABLE "user" ADD "isDeleted" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`UPDATE "user" SET "isDeleted" = "isDeletedOld" IS NOT NULL`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isDeletedOld"`); + } +} + diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 0c1e8092f..f2d53a984 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -163,11 +163,11 @@ export class User { // Indicates the user was deleted by an admin. // The users' data is not deleted from the database to keep them from reappearing. // A hard delete of the record may follow if we receive a matching Delete activity. - @Column('boolean', { - default: false, - comment: 'Whether the User is deleted.', + @Column('integer', { + nullable: true, + comment: 'How many delivery jobs are outstanding before the deletion is completed.', }) - public isDeleted: boolean; + public isDeleted: number | null; @Column('varchar', { length: 128, array: true, default: '{}', diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index aea591229..6549c24c6 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -381,7 +381,7 @@ export const UserRepository = db.getRepository(User).extend({ autoAcceptFollowed: profile!.autoAcceptFollowed, noCrawle: profile!.noCrawle, isExplorable: user.isExplorable, - isDeleted: user.isDeleted, + isDeleted: user.isDeleted != null, hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: NoteUnreads.count({ where: { userId: user.id, isSpecified: true }, diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 0d0a89f14..4d4d9257d 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -1,7 +1,9 @@ import httpSignature from '@peertube/http-signature'; import { v4 as uuid } from 'uuid'; +import Bull from 'bull'; import config from '@/config/index.js'; +import { Users } from '@/models/index.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js'; import { IActivity } from '@/remote/activitypub/type.js'; @@ -17,7 +19,7 @@ import { endedPollNotification } from './processors/ended-poll-notification.js'; import { queueLogger } from './logger.js'; import { getJobInfo } from './get-job-info.js'; import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js'; -import { ThinUser } from './types.js'; +import { DeliverJobData, ThinUser } from './types.js'; function renderError(e: Error): any { return { @@ -34,6 +36,12 @@ const inboxLogger = queueLogger.createSubLogger('inbox'); const dbLogger = queueLogger.createSubLogger('db'); const objectStorageLogger = queueLogger.createSubLogger('objectStorage'); +async function deletionRefCount(job: Bull.Job): Promise { + if (job.data.deletingUserId) { + await Users.decrement({ id: job.data.deletingUserId }, 'isDeleted', 1); + } +} + systemQueue .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`)) .on('active', (job) => systemLogger.debug(`active id=${job.id}`)) @@ -45,8 +53,14 @@ systemQueue deliverQueue .on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`)) .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) + .on('completed', async (job, result) => { + deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`); + await deletionRefCount(job); + }) + .on('failed', async (job, err) => { + deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`); + await deletionRefCount(job); + }) .on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`)) .on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); @@ -82,7 +96,7 @@ webhookDeliverQueue .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`)) .on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); -export function deliver(user: ThinUser, content: unknown, to: string | null) { +export function deliver(user: ThinUser, content: unknown, to: string | null, deletingUserId?: string) { if (content == null) return null; if (to == null) return null; @@ -92,6 +106,7 @@ export function deliver(user: ThinUser, content: unknown, to: string | null) { }, content, to, + deletingUserId, }; return deliverQueue.add(data, { @@ -323,8 +338,9 @@ export default function() { } export function destroy() { - deliverQueue.once('cleaned', (jobs, status) => { + deliverQueue.once('cleaned', async (jobs, status) => { deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); + await Promise.all(jobs.map(job => deletionRefCount(job)); }); deliverQueue.clean(0, 'delayed'); diff --git a/packages/backend/src/queue/processors/system/check-expired.ts b/packages/backend/src/queue/processors/system/check-expired.ts index eeb6149bb..71bd498e9 100644 --- a/packages/backend/src/queue/processors/system/check-expired.ts +++ b/packages/backend/src/queue/processors/system/check-expired.ts @@ -1,6 +1,6 @@ import Bull from 'bull'; import { In, LessThan } from 'typeorm'; -import { AttestationChallenges, AuthSessions, Mutings, Notifications, PasswordResetRequests, Signins } from '@/models/index.js'; +import { AttestationChallenges, AuthSessions, Mutings, Notifications, PasswordResetRequests, Signins, Users } from '@/models/index.js'; import { publishUserEvent } from '@/services/stream.js'; import { MINUTE, MONTH } from '@/const.js'; import { queueLogger } from '@/queue/logger.js'; @@ -52,6 +52,11 @@ export async function checkExpired(job: Bull.Job>, done: createdAt: OlderThan(3 * MONTH), }); + await Users.delete({ + // delete users where the deletion status reference count has come down to zero + isDeleted: 0, + }); + logger.succ('Deleted expired data.'); done(); diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 82bd28703..d745a1fc7 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -12,6 +12,8 @@ export type DeliverJobData = { content: unknown; /** inbox URL to deliver */ to: string; + /** set if this job is part of a user deletion, on completion or failure the isDeleted field needs to be decremented */ + deletingUserId?: string; }; export type InboxJobData = { diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index 4bc651c98..dfadef150 100644 --- a/packages/backend/src/remote/activitypub/deliver-manager.ts +++ b/packages/backend/src/remote/activitypub/deliver-manager.ts @@ -88,10 +88,10 @@ export class DeliverManager { /** * Execute delivers */ - public async execute() { + public async execute(deletingUserId?: string) { if (!Users.isLocalUser(this.actor)) return; - const inboxes = new Set(); + let inboxes = new Set(); /* build inbox list @@ -150,13 +150,17 @@ export class DeliverManager { )), ); - // deliver - for (const inbox of inboxes) { - // skip instances as indicated - if (instancesToSkip.includes(new URL(inbox).host)) continue; + inboxes = inboxes.entries() + .filter(inbox => !instancesToSkip.includes(new URL(inbox).host)); - deliver(this.actor, this.activity, inbox); + if (deletingUserId) { + await Users.update(deletingUserId, { + // set deletion job count for reference counting before queueing jobs + isDeleted: inboxes.length, + }); } + + inboxes.forEach(inbox => deliver(this.actor, this.activity, inbox, deletingUserId)); } } diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 9513ea22f..a191c626a 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -16,7 +16,7 @@ export async function deleteActor(actor: IRemoteUser, uri: string): Promise { Users.findOneByOrFail({ id: user.id }), ]); - if (userDetailed.isDeleted) { + if (userDetailed.isDeleted != null) { return; } diff --git a/packages/backend/src/services/delete-account.ts b/packages/backend/src/services/delete-account.ts index 2fa6e004b..a52b5b5d4 100644 --- a/packages/backend/src/services/delete-account.ts +++ b/packages/backend/src/services/delete-account.ts @@ -9,7 +9,7 @@ export async function deleteAccount(user: { }): Promise { await Promise.all([ Users.update(user.id, { - isDeleted: true, + isDeleted: -1, }), // revoke all of the users access tokens to block API access AccessTokens.delete({ diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts index 11e6266a0..7ed6bff80 100644 --- a/packages/backend/src/services/suspend-user.ts +++ b/packages/backend/src/services/suspend-user.ts @@ -6,6 +6,9 @@ import { User } from '@/models/entities/user.js'; import { Users } from '@/models/index.js'; import { publishInternalEvent } from '@/services/stream.js'; +/** + * Sends an internal event and for local users queues the delete activites. + */ export async function doPostSuspend(user: { id: User['id']; host: User['host'] }): Promise { publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); @@ -15,6 +18,6 @@ export async function doPostSuspend(user: { id: User['id']; host: User['host'] } // deliver to all of known network const dm = new DeliverManager(user, content); dm.addEveryone(); - await dm.execute(); + await dm.execute(user.id); } } From e584937b4f7dcd8f5a9a35d68c690ac1efacbac9 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 1 Feb 2023 23:35:18 +0100 Subject: [PATCH 29/70] fix: missing paren, type error --- packages/backend/src/queue/index.ts | 2 +- packages/backend/src/remote/activitypub/deliver-manager.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 4d4d9257d..182202260 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -340,7 +340,7 @@ export default function() { export function destroy() { deliverQueue.once('cleaned', async (jobs, status) => { deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); - await Promise.all(jobs.map(job => deletionRefCount(job)); + await Promise.all(jobs.map(job => deletionRefCount(job))); }); deliverQueue.clean(0, 'delayed'); diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index dfadef150..469464cec 100644 --- a/packages/backend/src/remote/activitypub/deliver-manager.ts +++ b/packages/backend/src/remote/activitypub/deliver-manager.ts @@ -150,17 +150,17 @@ export class DeliverManager { )), ); - inboxes = inboxes.entries() + const filteredInboxes = Array.from(inboxes) .filter(inbox => !instancesToSkip.includes(new URL(inbox).host)); if (deletingUserId) { await Users.update(deletingUserId, { // set deletion job count for reference counting before queueing jobs - isDeleted: inboxes.length, + isDeleted: filteredInboxes.length, }); } - inboxes.forEach(inbox => deliver(this.actor, this.activity, inbox, deletingUserId)); + filteredInboxes.forEach(inbox => deliver(this.actor, this.activity, inbox, deletingUserId)); } } From 4307010a9fe6ba347ea7795710548c22adaceda5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Mon, 1 May 2023 12:50:39 +0200 Subject: [PATCH 30/70] fix new occurences of isDeleted --- packages/backend/src/server/api/common/getters.ts | 6 +++--- packages/backend/src/server/api/private/signin.ts | 2 +- packages/backend/src/server/web/index.ts | 8 ++++---- packages/backend/src/services/user-cache.ts | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts index 97059f987..f15ae39f8 100644 --- a/packages/backend/src/server/api/common/getters.ts +++ b/packages/backend/src/server/api/common/getters.ts @@ -32,7 +32,7 @@ export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null) export async function getUser(userId: User['id'], includeSuspended = false) { const user = await Users.findOneBy({ id: userId, - isDeleted: false, + isDeleted: IsNull(), ...(includeSuspended ? {} : {isSuspended: false}), }); @@ -50,7 +50,7 @@ export async function getRemoteUser(userId: User['id'], includeSuspended = false const user = await Users.findOneBy({ id: userId, host: Not(IsNull()), - isDeleted: false, + isDeleted: IsNull(), ...(includeSuspended ? {} : {isSuspended: false}), }); @@ -68,7 +68,7 @@ export async function getLocalUser(userId: User['id'], includeSuspended = false) const user = await Users.findOneBy({ id: userId, host: IsNull(), - isDeleted: false, + isDeleted: IsNull(), ...(includeSuspended ? {} : {isSuspended: false}), }); diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index b09262bb1..75d2a56de 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -47,7 +47,7 @@ export default async (ctx: Koa.Context) => { const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull(), - isDeleted: false, + isDeleted: IsNull(), }) as ILocalUser; if (user == null) { diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 7c04077d4..2f9a76c84 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -223,7 +223,7 @@ const getFeed = async (acct: string) => { usernameLower: username.toLowerCase(), host: host ?? IsNull(), isSuspended: false, - isDeleted: false, + isDeleted: IsNull(), }); return user && await packFeed(user); @@ -273,7 +273,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { usernameLower: username.toLowerCase(), host: host ?? IsNull(), isSuspended: false, - isDeleted: false, + isDeleted: IsNull(), }); if (user != null) { @@ -306,7 +306,7 @@ router.get('/users/:user', async ctx => { id: ctx.params.user, host: IsNull(), isSuspended: false, - isDeleted: false, + isDeleted: IsNull(), }); if (user == null) { @@ -423,7 +423,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => { usernameLower: username.toLowerCase(), host: host ?? IsNull(), isSuspended: false, - isDeleted: false, + isDeleted: IsNull(), }); if (user == null) return; diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts index 77c5d0541..a886e398d 100644 --- a/packages/backend/src/services/user-cache.ts +++ b/packages/backend/src/services/user-cache.ts @@ -6,15 +6,15 @@ import { subscriber } from '@/db/redis.js'; export const userByIdCache = new Cache( Infinity, - async (id) => await Users.findOneBy({ id, isDeleted: false }) ?? undefined, + async (id) => await Users.findOneBy({ id, isDeleted: IsNull() }) ?? undefined, ); export const localUserByNativeTokenCache = new Cache( Infinity, - async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: false }) as ILocalUser | null ?? undefined, + async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: isNull() }) as ILocalUser | null ?? undefined, ); export const uriPersonCache = new Cache( Infinity, - async (uri) => await Users.findOneBy({ uri, isDeleted: false }) ?? undefined, + async (uri) => await Users.findOneBy({ uri, isDeleted: IsNull() }) ?? undefined, ); subscriber.on('message', async (_, data) => { From c81368f61747a8d03b8ab98082278057ee864ef5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 21:59:14 +0200 Subject: [PATCH 31/70] update changelog and version number --- CHANGELOG.md | 66 +++++++++++++++++++++++++++++++ package.json | 2 +- packages/backend/package.json | 2 +- packages/client/package.json | 2 +- packages/foundkey-js/package.json | 2 +- packages/sw/package.json | 2 +- 6 files changed, 71 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85229a4c9..1e1099f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,72 @@ Unreleased changes should not be listed in this file. Instead, run `git shortlog --format='%h %s' --group=trailer:changelog ..` to see unreleased changes; replace `` with the tag you wish to compare from. If you are a contributor, please read [CONTRIBUTING.md, section "Changelog Trailer"](./CONTRIBUTING.md#changelog-trailer) on what to do instead. +## 13.0.0-preview5 - 2023-05-23 +This release contains 6 breaking changes and 1 security update. + +### Security +- client: check input for aiscript +- server: validate filenames and emoji names on emoji import +- server: check URL schema of ActivityPub URIs +- server: check schema for URL previews +- server: update summaly dependency + +### Added +- client: impolement filtering and sorting in drive +- client: add "nobody" follower/following visibility +- client: re-add flag to require approval for bot follows +- client: show waveform on audio player +- client: add new deepl languages +- client: add instructions on remote interaction (when signed out) +- client: show follow button when not logged in +- server: show worker mode in process names +- server: drive endpoint to fetch files and folders combined +- activitypub: implement receiving account moves + +### Changed +- **BREAKING** server: restructure endpoints related to user administration +- **BREAKING** server: refactor streaming API data structures +- **BREAKING** server: rename configuration environment variables + The environment variables that could be used for configuration which were previously prefixed with `MK_` + are now prefixed with `FK_` instead. +- server: improve error message for invalidating follows +- server: add pagination to file attachment timeline + +### Fixed +- **BREAKING** server: properly respect follower/following visibility setting on statistics endpoint + This affects the endpoint `/api/users/stats`. +- improve documentation for `fetch-rss` endpoint +- client: fix authentication error in RSS widget +- client: fix attached files and account switcher combination in new note form +- client: improved module tracker file detection +- client: fix follow requests pagination +- client: Theme creator breaks after creating a theme +- client: replace error UUIDs with error codes +- client: allow opening links in new tab + The usual 3rd button click (usually mouse wheel) or Ctrl+Click should now work to open a link in a new tab. +- client: fix drive item updates inserting duplicate entries +- client: improve error messages for failed uploads +- client: stop unnecessary network congestion by websocket ping mechanism +- server: don't fail if a system user was already created +- server: better matching for MFM mentions +- server: fix rate limit for adding reactions +- server: check instance description length limit +- server: dont error on generating RSS feeds for profiles without public posts +- server: group delivering `Delete` activities to improve performance +- server: fix drive quota for remote users +- server: user deletion race condition (again) + +### Removed +- **BREAKING** server: remove unused API parameters `sinceId` and `untilId` from `/api/notes/reactions`. +- **BREAKING** server: remove syslog integration + If you used syslog before, the syslog protocoll will no longer be connected to. + The configuration entries for `syslog` will be ignored, you should remove them if they are set. +- client: remove `driveFolderBg` theme colour +- activitypub: remove `_misskey_content` attribute +- activitypub: remove `_misskey_reaction` attribute +- activitypub: remove `_misskey_votes` attribute +- foundkey-js: remove unused definitions for Ads and detailed instance metadata + ## 13.0.0-preview4 - 2023-02-05 This release contains 6 breaking changes, including changes to the configuration file format. diff --git a/package.json b/package.json index f83572c9a..831964a0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foundkey", - "version": "13.0.0-preview4", + "version": "13.0.0-preview5", "repository": { "type": "git", "url": "https://akkoma.dev/FoundKeyGang/FoundKey.git" diff --git a/packages/backend/package.json b/packages/backend/package.json index d9c830baf..6c62d9231 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "13.0.0-preview4", + "version": "13.0.0-preview5", "main": "./index.js", "private": true, "type": "module", diff --git a/packages/client/package.json b/packages/client/package.json index d9a994d4a..e8ce60b35 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "13.0.0-preview4", + "version": "13.0.0-preview5", "private": true, "scripts": { "watch": "vite build --watch --mode development", diff --git a/packages/foundkey-js/package.json b/packages/foundkey-js/package.json index de0862919..b01ff78e5 100644 --- a/packages/foundkey-js/package.json +++ b/packages/foundkey-js/package.json @@ -1,6 +1,6 @@ { "name": "foundkey-js", - "version": "13.0.0-preview4", + "version": "13.0.0-preview5", "description": "Fork of misskey-js for Foundkey", "type": "module", "main": "./built/index.js", diff --git a/packages/sw/package.json b/packages/sw/package.json index 41bf30605..c4023cb30 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -1,6 +1,6 @@ { "name": "sw", - "version": "13.0.0-preview4", + "version": "13.0.0-preview5", "private": true, "scripts": { "watch": "node build.js watch", From e75123602f0b9ceaa98c8ea09312bf4d2c4739d6 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 22:44:15 +0200 Subject: [PATCH 32/70] fix reference error from isNull --- packages/backend/src/services/user-cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts index a886e398d..9638f8101 100644 --- a/packages/backend/src/services/user-cache.ts +++ b/packages/backend/src/services/user-cache.ts @@ -10,7 +10,7 @@ export const userByIdCache = new Cache( ); export const localUserByNativeTokenCache = new Cache( Infinity, - async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: isNull() }) as ILocalUser | null ?? undefined, + async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: IsNull() }) as ILocalUser | null ?? undefined, ); export const uriPersonCache = new Cache( Infinity, From dfe12cba756342aeb039e263f6787ca8fc62fb0b Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 22:44:56 +0200 Subject: [PATCH 33/70] show stack trace from API handler --- packages/backend/src/server/api/api-handler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts index 3bde09602..e5a8dbcf6 100644 --- a/packages/backend/src/server/api/api-handler.ts +++ b/packages/backend/src/server/api/api-handler.ts @@ -45,7 +45,10 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise Date: Tue, 23 May 2023 22:56:27 +0200 Subject: [PATCH 34/70] server: remove unnecessary complex loop --- .../src/queue/processors/db/delete-account.ts | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts index 84e28f25d..0bdf8c5af 100644 --- a/packages/backend/src/queue/processors/db/delete-account.ts +++ b/packages/backend/src/queue/processors/db/delete-account.ts @@ -46,29 +46,17 @@ export async function deleteAccount(job: Bull.Job): Promise } { // Delete files - let cursor: DriveFile['id'] | null = null; + const files = await DriveFiles.find({ + where: { + userId: user.id, + }, + order: { + id: 1, + }, + }) as DriveFile[]; - while (true) { - const files = await DriveFiles.find({ - where: { - userId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 10, - order: { - id: 1, - }, - }) as DriveFile[]; - - if (files.length === 0) { - break; - } - - cursor = files[files.length - 1].id; - - for (const file of files) { - await deleteFileSync(file); - } + for (const file of files) { + await deleteFileSync(file); } logger.succ('All of files deleted'); From 79acdd7652d0a1f293ed59f539ed68ae4e454a42 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 22:56:51 +0200 Subject: [PATCH 35/70] fix: properly await file deletion --- packages/backend/src/services/drive/delete-file.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/services/drive/delete-file.ts b/packages/backend/src/services/drive/delete-file.ts index 2fe8993a8..3938c7116 100644 --- a/packages/backend/src/services/drive/delete-file.ts +++ b/packages/backend/src/services/drive/delete-file.ts @@ -60,14 +60,14 @@ export async function deleteFileSync(file: DriveFile, isExpired = false): Promis await Promise.all(promises); } - postProcess(file, isExpired); + await postProcess(file, isExpired); } async function postProcess(file: DriveFile, isExpired = false): Promise { // Turn into a direct link after expiring a remote file. if (isExpired && file.userHost != null && file.uri != null) { const id = uuid(); - DriveFiles.update(file.id, { + await DriveFiles.update(file.id, { isLink: true, url: file.uri, thumbnailUrl: null, @@ -78,14 +78,14 @@ async function postProcess(file: DriveFile, isExpired = false): Promise { webpublicAccessKey: 'webpublic-' + id, }); } else { - DriveFiles.delete(file.id); + await DriveFiles.delete(file.id); } // update statistics - driveChart.update(file, false); - perUserDriveChart.update(file, false); + await driveChart.update(file, false); + await perUserDriveChart.update(file, false); if (file.userHost != null) { - instanceChart.updateDrive(file, false); + await instanceChart.updateDrive(file, false); } } From a3c7571670077a5baaf872eb4c014b3e0ff049db Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 23 May 2023 23:56:30 +0200 Subject: [PATCH 36/70] add missing comma --- packages/backend/src/models/repositories/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 6549c24c6..ddcb97f90 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -258,7 +258,7 @@ export const UserRepository = db.getRepository(User).extend({ case 'nobody': return false; } - } + }, async pack( src: User['id'] | User, From ffdb112867c13d137e2dc6f9843e0778500784f7 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 26 May 2023 20:53:47 +0200 Subject: [PATCH 37/70] foundkey-js: remove favorites --- packages/foundkey-js/src/api.types.ts | 5 +---- packages/foundkey-js/src/consts.ts | 2 -- packages/foundkey-js/src/entities.ts | 7 ------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/foundkey-js/src/api.types.ts b/packages/foundkey-js/src/api.types.ts index 4c6b098f5..73dcf32da 100644 --- a/packages/foundkey-js/src/api.types.ts +++ b/packages/foundkey-js/src/api.types.ts @@ -1,7 +1,7 @@ import { Announcement, Antenna, App, AuthSession, Blocking, Channel, Clip, DateString, InstanceMetadata, DriveFile, DriveFolder, Following, FollowingFolloweePopulated, FollowingFollowerPopulated, FollowRequest, Instance, MeDetailed, - Note, NoteFavorite, OriginType, Page, ServerInfo, Stats, User, UserDetailed, UserGroup, UserList, UserSorting, Notification, NoteReaction, Signin, MessagingMessage, + Note, OriginType, Page, ServerInfo, Stats, User, UserDetailed, UserGroup, UserList, UserSorting, Notification, NoteReaction, Signin, MessagingMessage, } from './entities.js'; type TODO = Record | null; @@ -304,7 +304,6 @@ export type Endpoints = { 'i/export-mute': { req: TODO; res: TODO; }; 'i/export-notes': { req: TODO; res: TODO; }; 'i/export-user-lists': { req: TODO; res: TODO; }; - 'i/favorites': { req: { limit?: number; sinceId?: NoteFavorite['id']; untilId?: NoteFavorite['id']; }; res: NoteFavorite[]; }; 'i/get-word-muted-notes-count': { req: TODO; res: TODO; }; 'i/import-blocking': { req: TODO; res: TODO; }; 'i/import-following': { req: TODO; res: TODO; }; @@ -411,8 +410,6 @@ export type Endpoints = { }; }; res: { createdNote: Note }; }; 'notes/delete': { req: { noteId: Note['id']; }; res: null; }; - 'notes/favorites/create': { req: { noteId: Note['id']; }; res: null; }; - 'notes/favorites/delete': { req: { noteId: Note['id']; }; res: null; }; 'notes/featured': { req: TODO; res: Note[]; }; 'notes/global-timeline': { req: { limit?: number; sinceId?: Note['id']; untilId?: Note['id']; sinceDate?: number; untilDate?: number; }; res: Note[]; }; 'notes/hybrid-timeline': { req: { limit?: number; sinceId?: Note['id']; untilId?: Note['id']; sinceDate?: number; untilDate?: number; }; res: Note[]; }; diff --git a/packages/foundkey-js/src/consts.ts b/packages/foundkey-js/src/consts.ts index ed9fd8e53..1c941d3d5 100644 --- a/packages/foundkey-js/src/consts.ts +++ b/packages/foundkey-js/src/consts.ts @@ -13,8 +13,6 @@ export const permissions = [ 'write:blocks', 'read:drive', 'write:drive', - 'read:favorites', - 'write:favorites', 'read:following', 'write:following', 'read:messaging', diff --git a/packages/foundkey-js/src/entities.ts b/packages/foundkey-js/src/entities.ts index a83a8c123..173f0f28f 100644 --- a/packages/foundkey-js/src/entities.ts +++ b/packages/foundkey-js/src/entities.ts @@ -385,13 +385,6 @@ export type AuthSession = { export type Clip = TODO; -export type NoteFavorite = { - id: ID; - createdAt: DateString; - noteId: Note['id']; - note: Note; -}; - export type FollowRequest = { id: ID; follower: User; From 6695171b6aa28843f1cb49ebfbdaa1b0950e66f9 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 26 May 2023 20:57:45 +0200 Subject: [PATCH 38/70] remove outdated misskey-js api report --- packages/foundkey-js/etc/misskey-js.api.md | 2659 -------------------- 1 file changed, 2659 deletions(-) delete mode 100644 packages/foundkey-js/etc/misskey-js.api.md diff --git a/packages/foundkey-js/etc/misskey-js.api.md b/packages/foundkey-js/etc/misskey-js.api.md deleted file mode 100644 index 706f0216f..000000000 --- a/packages/foundkey-js/etc/misskey-js.api.md +++ /dev/null @@ -1,2659 +0,0 @@ -## API Report File for "misskey-js" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { EventEmitter } from 'eventemitter3'; - -// @public (undocumented) -export type Acct = { - username: string; - host: string | null; -}; - -// Warning: (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -type Ad = TODO_2; - -// @public (undocumented) -type Announcement = { - id: ID; - createdAt: DateString; - updatedAt: DateString | null; - text: string; - title: string; - imageUrl: string | null; - isRead?: boolean; -}; - -// @public (undocumented) -type Antenna = { - id: ID; - createdAt: DateString; - name: string; - keywords: string[][]; - excludeKeywords: string[][]; - src: 'home' | 'all' | 'users' | 'list' | 'group'; - userListId: ID | null; - userGroupId: ID | null; - users: string[]; - caseSensitive: boolean; - notify: boolean; - withReplies: boolean; - withFile: boolean; - hasUnreadNote: boolean; -}; - -declare namespace api { - export { - isAPIError, - APIError, - FetchLike, - APIClient - } -} -export { api } - -// @public (undocumented) -class APIClient { - constructor(opts: { - origin: APIClient['origin']; - credential?: APIClient['credential']; - fetch?: APIClient['fetch'] | null | undefined; - }); - // (undocumented) - credential: string | null | undefined; - // (undocumented) - fetch: FetchLike; - // (undocumented) - origin: string; - // Warning: (ae-forgotten-export) The symbol "IsCaseMatched" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "GetCaseResult" needs to be exported by the entry point index.d.ts - // - // (undocumented) - request(endpoint: E, params?: P, credential?: string | null | undefined): Promise extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : IsCaseMatched extends true ? GetCaseResult : Endpoints[E]['res']['$switch']['$default'] : Endpoints[E]['res']>; -} - -// @public (undocumented) -type APIError = { - id: string; - code: string; - message: string; - kind: 'client' | 'server'; - info: Record; -}; - -// @public (undocumented) -type App = TODO_2; - -// @public (undocumented) -type AuthSession = { - id: ID; - app: App; - token: string; -}; - -// @public (undocumented) -type Blocking = { - id: ID; - createdAt: DateString; - blockeeId: User['id']; - blockee: UserDetailed; -}; - -// @public (undocumented) -type Channel = { - id: ID; -}; - -// Warning: (ae-forgotten-export) The symbol "AnyOf" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export abstract class ChannelConnection = any> extends EventEmitter { - constructor(stream: Stream, channel: string, name?: string); - // (undocumented) - channel: string; - // (undocumented) - abstract dispose(): void; - // (undocumented) - abstract id: string; - // (undocumented) - inCount: number; - // (undocumented) - name?: string; - // (undocumented) - outCount: number; - // (undocumented) - send(type: T, body: Channel['receives'][T]): void; - // (undocumented) - protected stream: Stream; -} - -// @public (undocumented) -export type Channels = { - main: { - params: null; - events: { - notification: (payload: Notification_2) => void; - mention: (payload: Note) => void; - reply: (payload: Note) => void; - renote: (payload: Note) => void; - follow: (payload: User) => void; - followed: (payload: User) => void; - unfollow: (payload: User) => void; - meUpdated: (payload: MeDetailed) => void; - pageEvent: (payload: PageEvent) => void; - urlUploadFinished: (payload: { - marker: string; - file: DriveFile; - }) => void; - readAllNotifications: () => void; - unreadNotification: (payload: Notification_2) => void; - unreadMention: (payload: Note['id']) => void; - readAllUnreadMentions: () => void; - unreadSpecifiedNote: (payload: Note['id']) => void; - readAllUnreadSpecifiedNotes: () => void; - readAllMessagingMessages: () => void; - messagingMessage: (payload: MessagingMessage) => void; - unreadMessagingMessage: (payload: MessagingMessage) => void; - readAllAntennas: () => void; - unreadAntenna: (payload: Antenna) => void; - readAllAnnouncements: () => void; - readAllChannels: () => void; - unreadChannel: (payload: Note['id']) => void; - myTokenRegenerated: () => void; - reversiNoInvites: () => void; - reversiInvited: (payload: FIXME) => void; - signin: (payload: FIXME) => void; - registryUpdated: (payload: { - scope?: string[]; - key: string; - value: any | null; - }) => void; - driveFileCreated: (payload: DriveFile) => void; - readAntenna: (payload: Antenna) => void; - }; - receives: null; - }; - homeTimeline: { - params: null; - events: { - note: (payload: Note) => void; - }; - receives: null; - }; - localTimeline: { - params: null; - events: { - note: (payload: Note) => void; - }; - receives: null; - }; - hybridTimeline: { - params: null; - events: { - note: (payload: Note) => void; - }; - receives: null; - }; - globalTimeline: { - params: null; - events: { - note: (payload: Note) => void; - }; - receives: null; - }; - messaging: { - params: { - otherparty?: User['id'] | null; - group?: UserGroup['id'] | null; - }; - events: { - message: (payload: MessagingMessage) => void; - deleted: (payload: MessagingMessage['id']) => void; - read: (payload: MessagingMessage['id'][]) => void; - typers: (payload: User[]) => void; - }; - receives: { - read: { - id: MessagingMessage['id']; - }; - }; - }; - serverStats: { - params: null; - events: { - stats: (payload: FIXME) => void; - }; - receives: { - requestLog: { - id: string | number; - length: number; - }; - }; - }; - queueStats: { - params: null; - events: { - stats: (payload: FIXME) => void; - }; - receives: { - requestLog: { - id: string | number; - length: number; - }; - }; - }; -}; - -// @public (undocumented) -type Clip = TODO_2; - -// @public (undocumented) -type CustomEmoji = { - id: string; - name: string; - url: string; - category: string; - aliases: string[]; -}; - -// @public (undocumented) -type DateString = string; - -// @public (undocumented) -type DetailedInstanceMetadata = LiteInstanceMetadata & { - features: Record; -}; - -// @public (undocumented) -type DriveFile = { - id: ID; - createdAt: DateString; - isSensitive: boolean; - name: string; - thumbnailUrl: string; - url: string; - type: string; - size: number; - md5: string; - blurhash: string; - properties: Record; -}; - -// @public (undocumented) -type DriveFolder = TODO_2; - -// @public (undocumented) -export type Endpoints = { - 'admin/abuse-user-reports': { - req: TODO; - res: TODO; - }; - 'admin/delete-all-files-of-a-user': { - req: { - userId: User['id']; - }; - res: null; - }; - 'admin/delete-logs': { - req: NoParams; - res: null; - }; - 'admin/get-index-stats': { - req: TODO; - res: TODO; - }; - 'admin/get-table-stats': { - req: TODO; - res: TODO; - }; - 'admin/invite': { - req: TODO; - res: TODO; - }; - 'admin/logs': { - req: TODO; - res: TODO; - }; - 'admin/reset-password': { - req: TODO; - res: TODO; - }; - 'admin/resolve-abuse-user-report': { - req: TODO; - res: TODO; - }; - 'admin/resync-chart': { - req: TODO; - res: TODO; - }; - 'admin/send-email': { - req: TODO; - res: TODO; - }; - 'admin/server-info': { - req: TODO; - res: TODO; - }; - 'admin/show-moderation-logs': { - req: TODO; - res: TODO; - }; - 'admin/show-user': { - req: TODO; - res: TODO; - }; - 'admin/show-users': { - req: TODO; - res: TODO; - }; - 'admin/silence-user': { - req: TODO; - res: TODO; - }; - 'admin/suspend-user': { - req: TODO; - res: TODO; - }; - 'admin/unsilence-user': { - req: TODO; - res: TODO; - }; - 'admin/unsuspend-user': { - req: TODO; - res: TODO; - }; - 'admin/update-meta': { - req: TODO; - res: TODO; - }; - 'admin/vacuum': { - req: TODO; - res: TODO; - }; - 'admin/accounts/create': { - req: TODO; - res: TODO; - }; - 'admin/ad/create': { - req: TODO; - res: TODO; - }; - 'admin/ad/delete': { - req: { - id: Ad['id']; - }; - res: null; - }; - 'admin/ad/list': { - req: TODO; - res: TODO; - }; - 'admin/ad/update': { - req: TODO; - res: TODO; - }; - 'admin/announcements/create': { - req: TODO; - res: TODO; - }; - 'admin/announcements/delete': { - req: { - id: Announcement['id']; - }; - res: null; - }; - 'admin/announcements/list': { - req: TODO; - res: TODO; - }; - 'admin/announcements/update': { - req: TODO; - res: TODO; - }; - 'admin/drive/clean-remote-files': { - req: TODO; - res: TODO; - }; - 'admin/drive/cleanup': { - req: TODO; - res: TODO; - }; - 'admin/drive/files': { - req: TODO; - res: TODO; - }; - 'admin/drive/show-file': { - req: TODO; - res: TODO; - }; - 'admin/emoji/add': { - req: TODO; - res: TODO; - }; - 'admin/emoji/copy': { - req: TODO; - res: TODO; - }; - 'admin/emoji/list-remote': { - req: TODO; - res: TODO; - }; - 'admin/emoji/list': { - req: TODO; - res: TODO; - }; - 'admin/emoji/remove': { - req: TODO; - res: TODO; - }; - 'admin/emoji/update': { - req: TODO; - res: TODO; - }; - 'admin/federation/delete-all-files': { - req: { - host: string; - }; - res: null; - }; - 'admin/federation/refresh-remote-instance-metadata': { - req: TODO; - res: TODO; - }; - 'admin/federation/remove-all-following': { - req: TODO; - res: TODO; - }; - 'admin/federation/update-instance': { - req: TODO; - res: TODO; - }; - 'admin/moderators/add': { - req: TODO; - res: TODO; - }; - 'admin/moderators/remove': { - req: TODO; - res: TODO; - }; - 'admin/promo/create': { - req: TODO; - res: TODO; - }; - 'admin/queue/clear': { - req: TODO; - res: TODO; - }; - 'admin/queue/deliver-delayed': { - req: TODO; - res: TODO; - }; - 'admin/queue/inbox-delayed': { - req: TODO; - res: TODO; - }; - 'admin/queue/jobs': { - req: TODO; - res: TODO; - }; - 'admin/queue/stats': { - req: TODO; - res: TODO; - }; - 'admin/relays/add': { - req: TODO; - res: TODO; - }; - 'admin/relays/list': { - req: TODO; - res: TODO; - }; - 'admin/relays/remove': { - req: TODO; - res: TODO; - }; - 'announcements': { - req: { - limit?: number; - withUnreads?: boolean; - sinceId?: Announcement['id']; - untilId?: Announcement['id']; - }; - res: Announcement[]; - }; - 'antennas/create': { - req: TODO; - res: Antenna; - }; - 'antennas/delete': { - req: { - antennaId: Antenna['id']; - }; - res: null; - }; - 'antennas/list': { - req: NoParams; - res: Antenna[]; - }; - 'antennas/notes': { - req: { - antennaId: Antenna['id']; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - }; - res: Note[]; - }; - 'antennas/show': { - req: { - antennaId: Antenna['id']; - }; - res: Antenna; - }; - 'antennas/update': { - req: TODO; - res: Antenna; - }; - 'ap/get': { - req: { - uri: string; - }; - res: Record; - }; - 'ap/show': { - req: { - uri: string; - }; - res: { - type: 'Note'; - object: Note; - } | { - type: 'User'; - object: UserDetailed; - }; - }; - 'app/create': { - req: TODO; - res: App; - }; - 'app/show': { - req: { - appId: App['id']; - }; - res: App; - }; - 'auth/accept': { - req: { - token: string; - }; - res: null; - }; - 'auth/session/generate': { - req: { - appSecret: string; - }; - res: { - token: string; - url: string; - }; - }; - 'auth/session/show': { - req: { - token: string; - }; - res: AuthSession; - }; - 'auth/session/userkey': { - req: { - appSecret: string; - token: string; - }; - res: { - accessToken: string; - user: User; - }; - }; - 'blocking/create': { - req: { - userId: User['id']; - }; - res: UserDetailed; - }; - 'blocking/delete': { - req: { - userId: User['id']; - }; - res: UserDetailed; - }; - 'blocking/list': { - req: { - limit?: number; - sinceId?: Blocking['id']; - untilId?: Blocking['id']; - }; - res: Blocking[]; - }; - 'channels/create': { - req: TODO; - res: TODO; - }; - 'channels/featured': { - req: TODO; - res: TODO; - }; - 'channels/follow': { - req: TODO; - res: TODO; - }; - 'channels/followed': { - req: TODO; - res: TODO; - }; - 'channels/owned': { - req: TODO; - res: TODO; - }; - 'channels/pin-note': { - req: TODO; - res: TODO; - }; - 'channels/show': { - req: TODO; - res: TODO; - }; - 'channels/timeline': { - req: TODO; - res: TODO; - }; - 'channels/unfollow': { - req: TODO; - res: TODO; - }; - 'channels/update': { - req: TODO; - res: TODO; - }; - 'charts/active-users': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: { - local: { - users: number[]; - }; - remote: { - users: number[]; - }; - }; - }; - 'charts/drive': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: { - local: { - decCount: number[]; - decSize: number[]; - incCount: number[]; - incSize: number[]; - totalCount: number[]; - totalSize: number[]; - }; - remote: { - decCount: number[]; - decSize: number[]; - incCount: number[]; - incSize: number[]; - totalCount: number[]; - totalSize: number[]; - }; - }; - }; - 'charts/federation': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: { - instance: { - dec: number[]; - inc: number[]; - total: number[]; - }; - }; - }; - 'charts/hashtag': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: TODO; - }; - 'charts/instance': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - host: string; - }; - res: { - drive: { - decFiles: number[]; - decUsage: number[]; - incFiles: number[]; - incUsage: number[]; - totalFiles: number[]; - totalUsage: number[]; - }; - followers: { - dec: number[]; - inc: number[]; - total: number[]; - }; - following: { - dec: number[]; - inc: number[]; - total: number[]; - }; - notes: { - dec: number[]; - inc: number[]; - total: number[]; - diffs: { - normal: number[]; - renote: number[]; - reply: number[]; - }; - }; - requests: { - failed: number[]; - received: number[]; - succeeded: number[]; - }; - users: { - dec: number[]; - inc: number[]; - total: number[]; - }; - }; - }; - 'charts/network': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: TODO; - }; - 'charts/notes': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: { - local: { - dec: number[]; - inc: number[]; - total: number[]; - diffs: { - normal: number[]; - renote: number[]; - reply: number[]; - }; - }; - remote: { - dec: number[]; - inc: number[]; - total: number[]; - diffs: { - normal: number[]; - renote: number[]; - reply: number[]; - }; - }; - }; - }; - 'charts/user/drive': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - userId: User['id']; - }; - res: { - decCount: number[]; - decSize: number[]; - incCount: number[]; - incSize: number[]; - totalCount: number[]; - totalSize: number[]; - }; - }; - 'charts/user/following': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - userId: User['id']; - }; - res: TODO; - }; - 'charts/user/notes': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - userId: User['id']; - }; - res: { - dec: number[]; - inc: number[]; - total: number[]; - diffs: { - normal: number[]; - renote: number[]; - reply: number[]; - }; - }; - }; - 'charts/user/reactions': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - userId: User['id']; - }; - res: TODO; - }; - 'charts/users': { - req: { - span: 'day' | 'hour'; - limit?: number; - offset?: number | null; - }; - res: { - local: { - dec: number[]; - inc: number[]; - total: number[]; - }; - remote: { - dec: number[]; - inc: number[]; - total: number[]; - }; - }; - }; - 'clips/add-note': { - req: TODO; - res: TODO; - }; - 'clips/create': { - req: TODO; - res: TODO; - }; - 'clips/delete': { - req: { - clipId: Clip['id']; - }; - res: null; - }; - 'clips/list': { - req: TODO; - res: TODO; - }; - 'clips/notes': { - req: TODO; - res: TODO; - }; - 'clips/show': { - req: TODO; - res: TODO; - }; - 'clips/update': { - req: TODO; - res: TODO; - }; - 'drive': { - req: NoParams; - res: { - capacity: number; - usage: number; - }; - }; - 'drive/files': { - req: { - folderId?: DriveFolder['id'] | null; - type?: DriveFile['type'] | null; - limit?: number; - sinceId?: DriveFile['id']; - untilId?: DriveFile['id']; - }; - res: DriveFile[]; - }; - 'drive/files/attached-notes': { - req: TODO; - res: TODO; - }; - 'drive/files/check-existence': { - req: TODO; - res: TODO; - }; - 'drive/files/create': { - req: TODO; - res: TODO; - }; - 'drive/files/delete': { - req: { - fileId: DriveFile['id']; - }; - res: null; - }; - 'drive/files/find-by-hash': { - req: TODO; - res: TODO; - }; - 'drive/files/find': { - req: { - name: string; - folderId?: DriveFolder['id'] | null; - }; - res: DriveFile[]; - }; - 'drive/files/show': { - req: { - fileId?: DriveFile['id']; - url?: string; - }; - res: DriveFile; - }; - 'drive/files/update': { - req: { - fileId: DriveFile['id']; - folderId?: DriveFolder['id'] | null; - name?: string; - isSensitive?: boolean; - comment?: string | null; - }; - res: DriveFile; - }; - 'drive/files/upload-from-url': { - req: { - url: string; - folderId?: DriveFolder['id'] | null; - isSensitive?: boolean; - comment?: string | null; - marker?: string | null; - force?: boolean; - }; - res: null; - }; - 'drive/folders': { - req: { - folderId?: DriveFolder['id'] | null; - limit?: number; - sinceId?: DriveFile['id']; - untilId?: DriveFile['id']; - }; - res: DriveFolder[]; - }; - 'drive/folders/create': { - req: { - name?: string; - parentId?: DriveFolder['id'] | null; - }; - res: DriveFolder; - }; - 'drive/folders/delete': { - req: { - folderId: DriveFolder['id']; - }; - res: null; - }; - 'drive/folders/find': { - req: { - name: string; - parentId?: DriveFolder['id'] | null; - }; - res: DriveFolder[]; - }; - 'drive/folders/show': { - req: { - folderId: DriveFolder['id']; - }; - res: DriveFolder; - }; - 'drive/folders/update': { - req: { - folderId: DriveFolder['id']; - name?: string; - parentId?: DriveFolder['id'] | null; - }; - res: DriveFolder; - }; - 'drive/stream': { - req: { - type?: DriveFile['type'] | null; - limit?: number; - sinceId?: DriveFile['id']; - untilId?: DriveFile['id']; - }; - res: DriveFile[]; - }; - 'endpoint': { - req: { - endpoint: string; - }; - res: { - params: { - name: string; - type: string; - }[]; - }; - }; - 'endpoints': { - req: NoParams; - res: string[]; - }; - 'federation/dns': { - req: { - host: string; - }; - res: { - a: string[]; - aaaa: string[]; - cname: string[]; - txt: string[]; - }; - }; - 'federation/followers': { - req: { - host: string; - limit?: number; - sinceId?: Following['id']; - untilId?: Following['id']; - }; - res: FollowingFolloweePopulated[]; - }; - 'federation/following': { - req: { - host: string; - limit?: number; - sinceId?: Following['id']; - untilId?: Following['id']; - }; - res: FollowingFolloweePopulated[]; - }; - 'federation/instances': { - req: { - host?: string | null; - blocked?: boolean | null; - notResponding?: boolean | null; - suspended?: boolean | null; - federating?: boolean | null; - subscribing?: boolean | null; - publishing?: boolean | null; - limit?: number; - offset?: number; - sort?: '+pubSub' | '-pubSub' | '+notes' | '-notes' | '+users' | '-users' | '+following' | '-following' | '+followers' | '-followers' | '+caughtAt' | '-caughtAt' | '+lastCommunicatedAt' | '-lastCommunicatedAt' | '+driveUsage' | '-driveUsage' | '+driveFiles' | '-driveFiles'; - }; - res: Instance[]; - }; - 'federation/show-instance': { - req: { - host: string; - }; - res: Instance; - }; - 'federation/update-remote-user': { - req: { - userId: User['id']; - }; - res: null; - }; - 'federation/users': { - req: { - host: string; - limit?: number; - sinceId?: User['id']; - untilId?: User['id']; - }; - res: UserDetailed[]; - }; - 'following/create': { - req: { - userId: User['id']; - }; - res: User; - }; - 'following/delete': { - req: { - userId: User['id']; - }; - res: User; - }; - 'following/requests/accept': { - req: { - userId: User['id']; - }; - res: null; - }; - 'following/requests/cancel': { - req: { - userId: User['id']; - }; - res: User; - }; - 'following/requests/list': { - req: NoParams; - res: FollowRequest[]; - }; - 'following/requests/reject': { - req: { - userId: User['id']; - }; - res: null; - }; - 'gallery/featured': { - req: TODO; - res: TODO; - }; - 'gallery/popular': { - req: TODO; - res: TODO; - }; - 'gallery/posts': { - req: TODO; - res: TODO; - }; - 'gallery/posts/create': { - req: TODO; - res: TODO; - }; - 'gallery/posts/delete': { - req: { - postId: GalleryPost['id']; - }; - res: null; - }; - 'gallery/posts/like': { - req: TODO; - res: TODO; - }; - 'gallery/posts/show': { - req: TODO; - res: TODO; - }; - 'gallery/posts/unlike': { - req: TODO; - res: TODO; - }; - 'gallery/posts/update': { - req: TODO; - res: TODO; - }; - 'games/reversi/games': { - req: TODO; - res: TODO; - }; - 'games/reversi/games/show': { - req: TODO; - res: TODO; - }; - 'games/reversi/games/surrender': { - req: TODO; - res: TODO; - }; - 'games/reversi/invitations': { - req: TODO; - res: TODO; - }; - 'games/reversi/match': { - req: TODO; - res: TODO; - }; - 'games/reversi/match/cancel': { - req: TODO; - res: TODO; - }; - 'get-online-users-count': { - req: NoParams; - res: { - count: number; - }; - }; - 'hashtags/list': { - req: TODO; - res: TODO; - }; - 'hashtags/search': { - req: TODO; - res: TODO; - }; - 'hashtags/show': { - req: TODO; - res: TODO; - }; - 'hashtags/trend': { - req: TODO; - res: TODO; - }; - 'hashtags/users': { - req: TODO; - res: TODO; - }; - 'i': { - req: NoParams; - res: User; - }; - 'i/apps': { - req: TODO; - res: TODO; - }; - 'i/authorized-apps': { - req: TODO; - res: TODO; - }; - 'i/change-password': { - req: TODO; - res: TODO; - }; - 'i/delete-account': { - req: { - password: string; - }; - res: null; - }; - 'i/export-blocking': { - req: TODO; - res: TODO; - }; - 'i/export-following': { - req: TODO; - res: TODO; - }; - 'i/export-mute': { - req: TODO; - res: TODO; - }; - 'i/export-notes': { - req: TODO; - res: TODO; - }; - 'i/export-user-lists': { - req: TODO; - res: TODO; - }; - 'i/favorites': { - req: { - limit?: number; - sinceId?: NoteFavorite['id']; - untilId?: NoteFavorite['id']; - }; - res: NoteFavorite[]; - }; - 'i/gallery/likes': { - req: TODO; - res: TODO; - }; - 'i/gallery/posts': { - req: TODO; - res: TODO; - }; - 'i/get-word-muted-notes-count': { - req: TODO; - res: TODO; - }; - 'i/import-following': { - req: TODO; - res: TODO; - }; - 'i/import-user-lists': { - req: TODO; - res: TODO; - }; - 'i/notifications': { - req: { - limit?: number; - sinceId?: Notification_2['id']; - untilId?: Notification_2['id']; - following?: boolean; - markAsRead?: boolean; - includeTypes?: Notification_2['type'][]; - excludeTypes?: Notification_2['type'][]; - }; - res: Notification_2[]; - }; - 'i/page-likes': { - req: TODO; - res: TODO; - }; - 'i/pages': { - req: TODO; - res: TODO; - }; - 'i/pin': { - req: { - noteId: Note['id']; - }; - res: MeDetailed; - }; - 'i/read-all-messaging-messages': { - req: TODO; - res: TODO; - }; - 'i/read-all-unread-notes': { - req: TODO; - res: TODO; - }; - 'i/read-announcement': { - req: TODO; - res: TODO; - }; - 'i/regenerate-token': { - req: { - password: string; - }; - res: null; - }; - 'i/registry/get-all': { - req: { - scope?: string[]; - }; - res: Record; - }; - 'i/registry/get-detail': { - req: { - key: string; - scope?: string[]; - }; - res: { - updatedAt: DateString; - value: any; - }; - }; - 'i/registry/get': { - req: { - key: string; - scope?: string[]; - }; - res: any; - }; - 'i/registry/keys-with-type': { - req: { - scope?: string[]; - }; - res: Record; - }; - 'i/registry/keys': { - req: { - scope?: string[]; - }; - res: string[]; - }; - 'i/registry/remove': { - req: { - key: string; - scope?: string[]; - }; - res: null; - }; - 'i/registry/scopes': { - req: NoParams; - res: string[][]; - }; - 'i/registry/set': { - req: { - key: string; - value: any; - scope?: string[]; - }; - res: null; - }; - 'i/revoke-token': { - req: TODO; - res: TODO; - }; - 'i/signin-history': { - req: { - limit?: number; - sinceId?: Signin['id']; - untilId?: Signin['id']; - }; - res: Signin[]; - }; - 'i/unpin': { - req: { - noteId: Note['id']; - }; - res: MeDetailed; - }; - 'i/update-email': { - req: { - password: string; - email?: string | null; - }; - res: MeDetailed; - }; - 'i/update': { - req: { - name?: string | null; - description?: string | null; - lang?: string | null; - location?: string | null; - birthday?: string | null; - avatarId?: DriveFile['id'] | null; - bannerId?: DriveFile['id'] | null; - fields?: { - name: string; - value: string; - }[]; - isLocked?: boolean; - isExplorable?: boolean; - hideOnlineStatus?: boolean; - carefulBot?: boolean; - autoAcceptFollowed?: boolean; - noCrawle?: boolean; - isBot?: boolean; - isCat?: boolean; - injectFeaturedNote?: boolean; - receiveAnnouncementEmail?: boolean; - alwaysMarkNsfw?: boolean; - mutedWords?: string[][]; - mutingNotificationTypes?: Notification_2['type'][]; - emailNotificationTypes?: string[]; - }; - res: MeDetailed; - }; - 'i/user-group-invites': { - req: TODO; - res: TODO; - }; - 'i/2fa/done': { - req: TODO; - res: TODO; - }; - 'i/2fa/key-done': { - req: TODO; - res: TODO; - }; - 'i/2fa/password-less': { - req: TODO; - res: TODO; - }; - 'i/2fa/register-key': { - req: TODO; - res: TODO; - }; - 'i/2fa/register': { - req: TODO; - res: TODO; - }; - 'i/2fa/remove-key': { - req: TODO; - res: TODO; - }; - 'i/2fa/unregister': { - req: TODO; - res: TODO; - }; - 'messaging/history': { - req: { - limit?: number; - group?: boolean; - }; - res: MessagingMessage[]; - }; - 'messaging/messages': { - req: { - userId?: User['id']; - groupId?: UserGroup['id']; - limit?: number; - sinceId?: MessagingMessage['id']; - untilId?: MessagingMessage['id']; - markAsRead?: boolean; - }; - res: MessagingMessage[]; - }; - 'messaging/messages/create': { - req: { - userId?: User['id']; - groupId?: UserGroup['id']; - text?: string; - fileId?: DriveFile['id']; - }; - res: MessagingMessage; - }; - 'messaging/messages/delete': { - req: { - messageId: MessagingMessage['id']; - }; - res: null; - }; - 'messaging/messages/read': { - req: { - messageId: MessagingMessage['id']; - }; - res: null; - }; - 'meta': { - req: { - detail?: boolean; - }; - res: { - $switch: { - $cases: [ - [ - { - detail: true; - }, - DetailedInstanceMetadata - ], - [ - { - detail: false; - }, - LiteInstanceMetadata - ], - [ - { - detail: boolean; - }, - LiteInstanceMetadata | DetailedInstanceMetadata - ] - ]; - $default: LiteInstanceMetadata; - }; - }; - }; - 'miauth/gen-token': { - req: TODO; - res: TODO; - }; - 'mute/create': { - req: TODO; - res: TODO; - }; - 'mute/delete': { - req: { - userId: User['id']; - }; - res: null; - }; - 'mute/list': { - req: TODO; - res: TODO; - }; - 'my/apps': { - req: TODO; - res: TODO; - }; - 'notes': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - }; - res: Note[]; - }; - 'notes/children': { - req: { - noteId: Note['id']; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - }; - res: Note[]; - }; - 'notes/clips': { - req: TODO; - res: TODO; - }; - 'notes/conversation': { - req: TODO; - res: TODO; - }; - 'notes/create': { - req: { - visibility?: 'public' | 'home' | 'followers' | 'specified'; - visibleUserIds?: User['id'][]; - text?: null | string; - cw?: null | string; - viaMobile?: boolean; - localOnly?: boolean; - fileIds?: DriveFile['id'][]; - replyId?: null | Note['id']; - renoteId?: null | Note['id']; - channelId?: null | Channel['id']; - poll?: null | { - choices: string[]; - multiple?: boolean; - expiresAt?: null | number; - expiredAfter?: null | number; - }; - }; - res: { - createdNote: Note; - }; - }; - 'notes/delete': { - req: { - noteId: Note['id']; - }; - res: null; - }; - 'notes/favorites/create': { - req: { - noteId: Note['id']; - }; - res: null; - }; - 'notes/favorites/delete': { - req: { - noteId: Note['id']; - }; - res: null; - }; - 'notes/featured': { - req: TODO; - res: Note[]; - }; - 'notes/global-timeline': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; - }; - res: Note[]; - }; - 'notes/hybrid-timeline': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; - }; - res: Note[]; - }; - 'notes/local-timeline': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; - }; - res: Note[]; - }; - 'notes/mentions': { - req: { - following?: boolean; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - }; - res: Note[]; - }; - 'notes/polls/recommendation': { - req: TODO; - res: TODO; - }; - 'notes/polls/vote': { - req: { - noteId: Note['id']; - choice: number; - }; - res: null; - }; - 'notes/reactions': { - req: { - noteId: Note['id']; - type?: string | null; - limit?: number; - }; - res: NoteReaction[]; - }; - 'notes/reactions/create': { - req: { - noteId: Note['id']; - reaction: string; - }; - res: null; - }; - 'notes/reactions/delete': { - req: { - noteId: Note['id']; - }; - res: null; - }; - 'notes/renotes': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - noteId: Note['id']; - }; - res: Note[]; - }; - 'notes/replies': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - noteId: Note['id']; - }; - res: Note[]; - }; - 'notes/search-by-tag': { - req: TODO; - res: TODO; - }; - 'notes/search': { - req: TODO; - res: TODO; - }; - 'notes/show': { - req: { - noteId: Note['id']; - }; - res: Note; - }; - 'notes/state': { - req: TODO; - res: TODO; - }; - 'notes/timeline': { - req: { - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; - }; - res: Note[]; - }; - 'notes/unrenote': { - req: { - noteId: Note['id']; - }; - res: null; - }; - 'notes/user-list-timeline': { - req: { - listId: UserList['id']; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; - }; - res: Note[]; - }; - 'notes/watching/create': { - req: TODO; - res: TODO; - }; - 'notes/watching/delete': { - req: { - noteId: Note['id']; - }; - res: null; - }; - 'notifications/create': { - req: { - body: string; - header?: string | null; - icon?: string | null; - }; - res: null; - }; - 'notifications/mark-all-as-read': { - req: NoParams; - res: null; - }; - 'notifications/read': { - req: { - notificationId: Notification_2['id']; - }; - res: null; - }; - 'page-push': { - req: { - pageId: Page['id']; - event: string; - var?: any; - }; - res: null; - }; - 'pages/create': { - req: TODO; - res: Page; - }; - 'pages/delete': { - req: { - pageId: Page['id']; - }; - res: null; - }; - 'pages/featured': { - req: NoParams; - res: Page[]; - }; - 'pages/like': { - req: { - pageId: Page['id']; - }; - res: null; - }; - 'pages/show': { - req: { - pageId?: Page['id']; - name?: string; - username?: string; - }; - res: Page; - }; - 'pages/unlike': { - req: { - pageId: Page['id']; - }; - res: null; - }; - 'pages/update': { - req: TODO; - res: null; - }; - 'ping': { - req: NoParams; - res: { - pong: number; - }; - }; - 'pinned-users': { - req: TODO; - res: TODO; - }; - 'promo/read': { - req: TODO; - res: TODO; - }; - 'request-reset-password': { - req: { - username: string; - email: string; - }; - res: null; - }; - 'reset-password': { - req: { - token: string; - password: string; - }; - res: null; - }; - 'room/show': { - req: TODO; - res: TODO; - }; - 'room/update': { - req: TODO; - res: TODO; - }; - 'stats': { - req: NoParams; - res: Stats; - }; - 'server-info': { - req: NoParams; - res: ServerInfo; - }; - 'sw/register': { - req: TODO; - res: TODO; - }; - 'username/available': { - req: { - username: string; - }; - res: { - available: boolean; - }; - }; - 'users': { - req: { - limit?: number; - offset?: number; - sort?: UserSorting; - origin?: OriginType; - }; - res: User[]; - }; - 'users/clips': { - req: TODO; - res: TODO; - }; - 'users/followers': { - req: { - userId?: User['id']; - username?: User['username']; - host?: User['host'] | null; - limit?: number; - sinceId?: Following['id']; - untilId?: Following['id']; - }; - res: FollowingFollowerPopulated[]; - }; - 'users/following': { - req: { - userId?: User['id']; - username?: User['username']; - host?: User['host'] | null; - limit?: number; - sinceId?: Following['id']; - untilId?: Following['id']; - }; - res: FollowingFolloweePopulated[]; - }; - 'users/gallery/posts': { - req: TODO; - res: TODO; - }; - 'users/groups/create': { - req: TODO; - res: TODO; - }; - 'users/groups/delete': { - req: { - groupId: UserGroup['id']; - }; - res: null; - }; - 'users/groups/invitations/accept': { - req: TODO; - res: TODO; - }; - 'users/groups/invitations/reject': { - req: TODO; - res: TODO; - }; - 'users/groups/invite': { - req: TODO; - res: TODO; - }; - 'users/groups/joined': { - req: TODO; - res: TODO; - }; - 'users/groups/owned': { - req: TODO; - res: TODO; - }; - 'users/groups/pull': { - req: TODO; - res: TODO; - }; - 'users/groups/show': { - req: TODO; - res: TODO; - }; - 'users/groups/transfer': { - req: TODO; - res: TODO; - }; - 'users/groups/update': { - req: TODO; - res: TODO; - }; - 'users/lists/create': { - req: { - name: string; - }; - res: UserList; - }; - 'users/lists/delete': { - req: { - listId: UserList['id']; - }; - res: null; - }; - 'users/lists/list': { - req: NoParams; - res: UserList[]; - }; - 'users/lists/pull': { - req: { - listId: UserList['id']; - userId: User['id']; - }; - res: null; - }; - 'users/lists/push': { - req: { - listId: UserList['id']; - userId: User['id']; - }; - res: null; - }; - 'users/lists/show': { - req: { - listId: UserList['id']; - }; - res: UserList; - }; - 'users/lists/update': { - req: { - listId: UserList['id']; - name: string; - }; - res: UserList; - }; - 'users/notes': { - req: { - userId: User['id']; - limit?: number; - sinceId?: Note['id']; - untilId?: Note['id']; - sinceDate?: number; - untilDate?: number; - }; - res: Note[]; - }; - 'users/pages': { - req: TODO; - res: TODO; - }; - 'users/recommendation': { - req: TODO; - res: TODO; - }; - 'users/relation': { - req: TODO; - res: TODO; - }; - 'users/report-abuse': { - req: TODO; - res: TODO; - }; - 'users/search-by-username-and-host': { - req: TODO; - res: TODO; - }; - 'users/search': { - req: TODO; - res: TODO; - }; - 'users/show': { - req: ShowUserReq | { - userIds: User['id'][]; - }; - res: { - $switch: { - $cases: [ - [ - { - userIds: User['id'][]; - }, - UserDetailed[] - ] - ]; - $default: UserDetailed; - }; - }; - }; - 'users/stats': { - req: TODO; - res: TODO; - }; -}; - -declare namespace entities { - export { - ID, - DateString, - User, - UserLite, - UserDetailed, - UserGroup, - UserList, - MeDetailed, - DriveFile, - DriveFolder, - GalleryPost, - Note, - NoteReaction, - Notification_2 as Notification, - MessagingMessage, - CustomEmoji, - LiteInstanceMetadata, - DetailedInstanceMetadata, - InstanceMetadata, - ServerInfo, - Stats, - Page, - PageEvent, - Announcement, - Antenna, - App, - AuthSession, - Ad, - Clip, - NoteFavorite, - FollowRequest, - Channel, - Following, - FollowingFolloweePopulated, - FollowingFollowerPopulated, - Blocking, - Instance, - Signin, - UserSorting, - OriginType - } -} -export { entities } - -// @public (undocumented) -type FetchLike = (input: string, init?: { - method?: string; - body?: string; - credentials?: RequestCredentials; - cache?: RequestCache; -}) => Promise<{ - status: number; - json(): Promise; -}>; - -// @public (undocumented) -export const ffVisibility: readonly ["public", "followers", "private"]; - -// @public (undocumented) -type Following = { - id: ID; - createdAt: DateString; - followerId: User['id']; - followeeId: User['id']; -}; - -// @public (undocumented) -type FollowingFolloweePopulated = Following & { - followee: UserDetailed; -}; - -// @public (undocumented) -type FollowingFollowerPopulated = Following & { - follower: UserDetailed; -}; - -// @public (undocumented) -type FollowRequest = { - id: ID; - follower: User; - followee: User; -}; - -// @public (undocumented) -type GalleryPost = TODO_2; - -// @public (undocumented) -type ID = string; - -// @public (undocumented) -type Instance = { - id: ID; - caughtAt: DateString; - host: string; - usersCount: number; - notesCount: number; - followingCount: number; - followersCount: number; - driveUsage: number; - driveFiles: number; - latestRequestSentAt: DateString | null; - latestStatus: number | null; - latestRequestReceivedAt: DateString | null; - lastCommunicatedAt: DateString; - isNotResponding: boolean; - isSuspended: boolean; - softwareName: string | null; - softwareVersion: string | null; - openRegistrations: boolean | null; - name: string | null; - description: string | null; - maintainerName: string | null; - maintainerEmail: string | null; - iconUrl: string | null; - faviconUrl: string | null; - themeColor: string | null; - infoUpdatedAt: DateString | null; -}; - -// @public (undocumented) -type InstanceMetadata = LiteInstanceMetadata | DetailedInstanceMetadata; - -// @public (undocumented) -function isAPIError(reason: any): reason is APIError; - -// @public (undocumented) -type LiteInstanceMetadata = { - maintainerName: string | null; - maintainerEmail: string | null; - version: string; - name: string | null; - uri: string; - description: string | null; - tosUrl: string | null; - disableRegistration: boolean; - disableLocalTimeline: boolean; - disableGlobalTimeline: boolean; - driveCapacityPerLocalUserMb: number; - driveCapacityPerRemoteUserMb: number; - enableHcaptcha: boolean; - hcaptchaSiteKey: string | null; - enableRecaptcha: boolean; - recaptchaSiteKey: string | null; - swPublickey: string | null; - maxNoteTextLength: number; - enableEmail: boolean; - enableTwitterIntegration: boolean; - enableGithubIntegration: boolean; - enableDiscordIntegration: boolean; - enableServiceWorker: boolean; - emojis: CustomEmoji[]; - ads: { - id: ID; - ratio: number; - place: string; - url: string; - imageUrl: string; - }[]; -}; - -// @public (undocumented) -type MeDetailed = UserDetailed & { - avatarId: DriveFile['id']; - bannerId: DriveFile['id']; - autoAcceptFollowed: boolean; - alwaysMarkNsfw: boolean; - carefulBot: boolean; - emailNotificationTypes: string[]; - hasPendingReceivedFollowRequest: boolean; - hasUnreadAnnouncement: boolean; - hasUnreadAntenna: boolean; - hasUnreadChannel: boolean; - hasUnreadMentions: boolean; - hasUnreadMessagingMessage: boolean; - hasUnreadNotification: boolean; - hasUnreadSpecifiedNotes: boolean; - hideOnlineStatus: boolean; - injectFeaturedNote: boolean; - integrations: Record; - isDeleted: boolean; - isExplorable: boolean; - mutedWords: string[][]; - mutingNotificationTypes: string[]; - noCrawle: boolean; - receiveAnnouncementEmail: boolean; - usePasswordLessLogin: boolean; - [other: string]: any; -}; - -// @public (undocumented) -type MessagingMessage = { - id: ID; - createdAt: DateString; - file: DriveFile | null; - fileId: DriveFile['id'] | null; - isRead: boolean; - reads: User['id'][]; - text: string | null; - user: User; - userId: User['id']; - recipient?: User | null; - recipientId: User['id'] | null; - group?: UserGroup | null; - groupId: UserGroup['id'] | null; -}; - -// @public (undocumented) -export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"]; - -// @public (undocumented) -type Note = { - id: ID; - createdAt: DateString; - text: string | null; - cw: string | null; - user: User; - userId: User['id']; - reply?: Note; - replyId: Note['id']; - renote?: Note; - renoteId: Note['id']; - files: DriveFile[]; - fileIds: DriveFile['id'][]; - visibility: 'public' | 'home' | 'followers' | 'specified'; - visibleUserIds?: User['id'][]; - localOnly?: boolean; - myReaction?: string; - reactions: Record; - renoteCount: number; - repliesCount: number; - poll?: { - expiresAt: DateString | null; - multiple: boolean; - choices: { - isVoted: boolean; - text: string; - votes: number; - }[]; - }; - emojis: { - name: string; - url: string; - }[]; - uri?: string; - url?: string; - isHidden?: boolean; -}; - -// @public (undocumented) -type NoteFavorite = { - id: ID; - createdAt: DateString; - noteId: Note['id']; - note: Note; -}; - -// @public (undocumented) -type NoteReaction = { - id: ID; - createdAt: DateString; - user: UserLite; - type: string; -}; - -// @public (undocumented) -export const noteVisibilities: readonly ["public", "home", "followers", "specified"]; - -// @public (undocumented) -type Notification_2 = { - id: ID; - createdAt: DateString; - isRead: boolean; -} & ({ - type: 'reaction'; - reaction: string; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'reply'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'renote'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'quote'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'mention'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'pollVote'; - user: User; - userId: User['id']; - note: Note; -} | { - type: 'follow'; - user: User; - userId: User['id']; -} | { - type: 'followRequestAccepted'; - user: User; - userId: User['id']; -} | { - type: 'receiveFollowRequest'; - user: User; - userId: User['id']; -} | { - type: 'groupInvited'; - invitation: UserGroup; - user: User; - userId: User['id']; -} | { - type: 'app'; - header?: string | null; - body: string; - icon?: string | null; -}); - -// @public (undocumented) -export const notificationTypes: readonly ["follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app"]; - -// @public (undocumented) -type OriginType = 'combined' | 'local' | 'remote'; - -// @public (undocumented) -type Page = { - id: ID; - createdAt: DateString; - updatedAt: DateString; - userId: User['id']; - user: User; - content: Record[]; - variables: Record[]; - title: string; - name: string; - summary: string | null; - hideTitleWhenPinned: boolean; - alignCenter: boolean; - font: string; - script: string; - eyeCatchingImageId: DriveFile['id'] | null; - eyeCatchingImage: DriveFile | null; - attachedFiles: any; - likedCount: number; - isLiked?: boolean; -}; - -// @public (undocumented) -type PageEvent = { - pageId: Page['id']; - event: string; - var: any; - userId: User['id']; - user: User; -}; - -// @public (undocumented) -export const permissions: string[]; - -// @public (undocumented) -type ServerInfo = { - machine: string; - cpu: { - model: string; - cores: number; - }; - mem: { - total: number; - }; - fs: { - total: number; - used: number; - }; -}; - -// @public (undocumented) -type Signin = { - id: ID; - createdAt: DateString; - ip: string; - headers: Record; - success: boolean; -}; - -// @public (undocumented) -type Stats = { - notesCount: number; - originalNotesCount: number; - usersCount: number; - originalUsersCount: number; - instances: number; - driveUsageLocal: number; - driveUsageRemote: number; -}; - -// Warning: (ae-forgotten-export) The symbol "StreamEvents" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export class Stream extends EventEmitter { - constructor(origin: string, user: { - token: string; - } | null, options?: { - WebSocket?: any; - }); - // (undocumented) - close(): void; - // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts - // - // (undocumented) - disconnectToChannel(connection: NonSharedConnection): void; - // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts - // - // (undocumented) - removeSharedConnection(connection: SharedConnection): void; - // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts - // - // (undocumented) - removeSharedConnectionPool(pool: Pool): void; - // (undocumented) - send(typeOrPayload: any, payload?: any): void; - // (undocumented) - state: 'initializing' | 'reconnecting' | 'connected'; - // (undocumented) - useChannel(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnection; -} - -// @public (undocumented) -type User = UserLite | UserDetailed; - -// @public (undocumented) -type UserDetailed = UserLite & { - bannerBlurhash: string | null; - bannerColor: string | null; - bannerUrl: string | null; - birthday: string | null; - createdAt: DateString; - description: string | null; - ffVisibility: 'public' | 'followers' | 'private'; - fields: { - name: string; - value: string; - }[]; - followersCount: number; - followingCount: number; - hasPendingFollowRequestFromYou: boolean; - hasPendingFollowRequestToYou: boolean; - isAdmin: boolean; - isBlocked: boolean; - isBlocking: boolean; - isBot: boolean; - isCat: boolean; - isFollowed: boolean; - isFollowing: boolean; - isLocked: boolean; - isModerator: boolean; - isMuted: boolean; - isSilenced: boolean; - isSuspended: boolean; - lang: string | null; - lastFetchedAt?: DateString; - location: string | null; - notesCount: number; - pinnedNoteIds: ID[]; - pinnedNotes: Note[]; - pinnedPage: Page | null; - pinnedPageId: string | null; - publicReactions: boolean; - securityKeys: boolean; - twoFactorEnabled: boolean; - updatedAt: DateString | null; - uri: string | null; - url: string | null; -}; - -// @public (undocumented) -type UserGroup = TODO_2; - -// @public (undocumented) -type UserList = { - id: ID; - createdAt: DateString; - name: string; - userIds: User['id'][]; -}; - -// @public (undocumented) -type UserLite = { - id: ID; - username: string; - host: string | null; - name: string; - onlineStatus: 'online' | 'active' | 'offline' | 'unknown'; - avatarUrl: string; - avatarBlurhash: string; - emojis: { - name: string; - url: string; - }[]; - instance?: { - name: Instance['name']; - softwareName: Instance['softwareName']; - softwareVersion: Instance['softwareVersion']; - iconUrl: Instance['iconUrl']; - faviconUrl: Instance['faviconUrl']; - themeColor: Instance['themeColor']; - }; -}; - -// @public (undocumented) -type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; - -// Warnings were encountered during analysis: -// -// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts -// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts -// src/api.types.ts:595:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts -// src/streaming.types.ts:35:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts - -// (No @packageDocumentation comment for this package) - -``` From de81fac33453b72e76ae5bdc3186da45fc0c55e6 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 26 May 2023 20:56:47 +0200 Subject: [PATCH 39/70] client: remove favorites --- packages/client/src/menu.ts | 6 --- packages/client/src/pages/favorites.vue | 50 -------------------- packages/client/src/router.ts | 4 -- packages/client/src/scripts/get-note-menu.ts | 15 ------ packages/client/src/store.ts | 1 - 5 files changed, 76 deletions(-) delete mode 100644 packages/client/src/pages/favorites.vue diff --git a/packages/client/src/menu.ts b/packages/client/src/menu.ts index 1de63b4dc..8fc98b33a 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/menu.ts @@ -120,12 +120,6 @@ export const menuDef = reactive({ indicated: computed(() => $i != null && $i.hasUnreadSpecifiedNotes), to: '/my/notifications#directNotes', }, - favorites: { - title: 'favorites', - icon: 'fas fa-star', - show: computed(() => $i != null), - to: '/my/favorites', - }, pages: { title: 'pages', icon: 'fas fa-file-alt', diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue deleted file mode 100644 index f9e0b552a..000000000 --- a/packages/client/src/pages/favorites.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 6ba2c09ef..6b9d82b57 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -148,10 +148,6 @@ export const routes = [{ component: page(() => import('./pages/notifications.vue')), hash: 'initialTab', loginRequired: true, -}, { - path: '/my/favorites', - component: page(() => import('./pages/favorites.vue')), - loginRequired: true, }, { name: 'messaging', path: '/my/messaging', diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index 73b960cae..61fc4cc73 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -48,12 +48,6 @@ export function getNoteMenu(props: { }); } - function toggleFavorite(favorite: boolean): void { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: appearNote.id, - }); - } - function toggleWatch(watch: boolean): void { os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { noteId: appearNote.id, @@ -244,15 +238,6 @@ export function getNoteMenu(props: { action: translate, } : undefined, null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: i18n.ts.unfavorite, - action: () => toggleFavorite(false), - } : { - icon: 'fas fa-star', - text: i18n.ts.favorite, - action: () => toggleFavorite(true), - }), { icon: 'fas fa-paperclip', text: i18n.ts.clip, diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index 489cca84d..a267a3634 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -59,7 +59,6 @@ export const defaultStore = markRaw(new Storage('base', { where: 'deviceAccount', default: [ 'notifications', - 'favorites', 'drive', 'followRequests', '-', From aa7171e116f0f031d061aa1df0cdb9c83da5cadd Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 26 May 2023 20:52:31 +0200 Subject: [PATCH 40/70] server: remove favorites --- packages/backend/src/db/postgre.ts | 2 - packages/backend/src/misc/api-permissions.ts | 2 - packages/backend/src/misc/schema.ts | 2 - .../src/models/entities/note-favorite.ts | 35 -------------- packages/backend/src/models/index.ts | 2 - .../src/models/repositories/note-favorite.ts | 29 ----------- .../src/models/schema/note-favorite.ts | 26 ---------- packages/backend/src/server/api/endpoints.ts | 6 --- .../src/server/api/endpoints/i/favorites.ts | 44 ----------------- .../api/endpoints/notes/favorites/create.ts | 48 ------------------- .../api/endpoints/notes/favorites/delete.ts | 42 ---------------- .../src/server/api/endpoints/notes/state.ts | 16 +------ .../src/server/api/endpoints/users/stats.ts | 9 +--- packages/backend/src/server/api/error.ts | 8 ---- 14 files changed, 3 insertions(+), 268 deletions(-) delete mode 100644 packages/backend/src/models/entities/note-favorite.ts delete mode 100644 packages/backend/src/models/repositories/note-favorite.ts delete mode 100644 packages/backend/src/models/schema/note-favorite.ts delete mode 100644 packages/backend/src/server/api/endpoints/i/favorites.ts delete mode 100644 packages/backend/src/server/api/endpoints/notes/favorites/create.ts delete mode 100644 packages/backend/src/server/api/endpoints/notes/favorites/delete.ts diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index b59142597..d8c31ae96 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -33,7 +33,6 @@ import { UserGroup } from '@/models/entities/user-group.js'; import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; import { Hashtag } from '@/models/entities/hashtag.js'; -import { NoteFavorite } from '@/models/entities/note-favorite.js'; import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; import { RegistrationTicket } from '@/models/entities/registration-tickets.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; @@ -134,7 +133,6 @@ export const entities = [ RenoteMuting, Blocking, Note, - NoteFavorite, NoteReaction, NoteWatching, NoteThreadMuting, diff --git a/packages/backend/src/misc/api-permissions.ts b/packages/backend/src/misc/api-permissions.ts index d7c115a50..17ae0d99d 100644 --- a/packages/backend/src/misc/api-permissions.ts +++ b/packages/backend/src/misc/api-permissions.ts @@ -5,8 +5,6 @@ export const kinds = [ 'write:blocks', 'read:drive', 'write:drive', - 'read:favorites', - 'write:favorites', 'read:following', 'write:following', 'read:messaging', diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 1bb07d14e..6b5fcf0f5 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -23,7 +23,6 @@ import { packedHashtagSchema } from '@/models/schema/hashtag.js'; import { packedPageSchema } from '@/models/schema/page.js'; import { packedUserGroupSchema } from '@/models/schema/user-group.js'; import { packedUserGroupInvitationSchema } from '@/models/schema/user-group-invitation.js'; -import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite.js'; import { packedChannelSchema } from '@/models/schema/channel.js'; import { packedAntennaSchema } from '@/models/schema/antenna.js'; import { packedClipSchema } from '@/models/schema/clip.js'; @@ -47,7 +46,6 @@ export const refs = { MessagingMessage: packedMessagingMessageSchema, Note: packedNoteSchema, NoteReaction: packedNoteReactionSchema, - NoteFavorite: packedNoteFavoriteSchema, Notification: packedNotificationSchema, DriveFile: packedDriveFileSchema, DriveFolder: packedDriveFolderSchema, diff --git a/packages/backend/src/models/entities/note-favorite.ts b/packages/backend/src/models/entities/note-favorite.ts deleted file mode 100644 index 8b4449c3e..000000000 --- a/packages/backend/src/models/entities/note-favorite.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './note.js'; -import { User } from './user.js'; - -@Entity() -@Index(['userId', 'noteId'], { unique: true }) -export class NoteFavorite { - @PrimaryColumn(id()) - public id: string; - - @Column('timestamp with time zone', { - comment: 'The created date of the NoteFavorite.', - }) - public createdAt: Date; - - @Index() - @Column(id()) - public userId: User['id']; - - @ManyToOne(() => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user: User | null; - - @Column(id()) - public noteId: Note['id']; - - @ManyToOne(() => Note, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public note: Note | null; -} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index f4aa84d5b..7ba383c4a 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -29,7 +29,6 @@ import { RenoteMutingRepository } from './repositories/renote-muting.js'; import { BlockingRepository } from './repositories/blocking.js'; import { NoteReactionRepository } from './repositories/note-reaction.js'; import { NotificationRepository } from './repositories/notification.js'; -import { NoteFavoriteRepository } from './repositories/note-favorite.js'; import { UserPublickey } from './entities/user-publickey.js'; import { UserKeypair } from './entities/user-keypair.js'; import { AppRepository } from './repositories/app.js'; @@ -64,7 +63,6 @@ export const Announcements = db.getRepository(Announcement); export const AnnouncementReads = db.getRepository(AnnouncementRead); export const Apps = (AppRepository); export const Notes = (NoteRepository); -export const NoteFavorites = (NoteFavoriteRepository); export const NoteWatchings = db.getRepository(NoteWatching); export const NoteThreadMutings = db.getRepository(NoteThreadMuting); export const NoteReactions = (NoteReactionRepository); diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts deleted file mode 100644 index 47d549455..000000000 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { db } from '@/db/postgre.js'; -import { NoteFavorite } from '@/models/entities/note-favorite.js'; -import { User } from '@/models/entities/user.js'; -import { Notes } from '../index.js'; - -export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ - async pack( - src: NoteFavorite['id'] | NoteFavorite, - me?: { id: User['id'] } | null | undefined, - ) { - const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - - return { - id: favorite.id, - createdAt: favorite.createdAt.toISOString(), - noteId: favorite.noteId, - // may throw error - note: await Notes.pack(favorite.note || favorite.noteId, me), - }; - }, - - packMany( - favorites: any[], - me: { id: User['id'] }, - ) { - return Promise.allSettled(favorites.map(x => this.pack(x, me))) - .then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : [])); - }, -}); diff --git a/packages/backend/src/models/schema/note-favorite.ts b/packages/backend/src/models/schema/note-favorite.ts deleted file mode 100644 index d133f7367..000000000 --- a/packages/backend/src/models/schema/note-favorite.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const packedNoteFavoriteSchema = { - type: 'object', - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', - }, - note: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', - }, - noteId: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - }, -} as const; diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index a7757c2b5..c5249191e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -162,7 +162,6 @@ import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; -import * as ep___i_favorites from './endpoints/i/favorites.js'; import * as ep___i_getWordMutedNotesCount from './endpoints/i/get-word-muted-notes-count.js'; import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; import * as ep___i_importFollowing from './endpoints/i/import-following.js'; @@ -215,8 +214,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; -import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; -import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_featured from './endpoints/notes/featured.js'; import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; @@ -459,7 +456,6 @@ const eps = [ ['i/export-mute', ep___i_exportMute], ['i/export-notes', ep___i_exportNotes], ['i/export-user-lists', ep___i_exportUserLists], - ['i/favorites', ep___i_favorites], ['i/get-word-muted-notes-count', ep___i_getWordMutedNotesCount], ['i/import-blocking', ep___i_importBlocking], ['i/import-following', ep___i_importFollowing], @@ -512,8 +508,6 @@ const eps = [ ['notes/conversation', ep___notes_conversation], ['notes/create', ep___notes_create], ['notes/delete', ep___notes_delete], - ['notes/favorites/create', ep___notes_favorites_create], - ['notes/favorites/delete', ep___notes_favorites_delete], ['notes/featured', ep___notes_featured], ['notes/global-timeline', ep___notes_globalTimeline], ['notes/hybrid-timeline', ep___notes_hybridTimeline], diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts deleted file mode 100644 index ddda42a6d..000000000 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NoteFavorites } from '@/models/index.js'; -import define from '@/server/api/define.js'; -import { makePaginationQuery } from '@/server/api/common/make-pagination-query.js'; - -export const meta = { - tags: ['account', 'notes', 'favorites'], - - requireCredential: true, - - kind: 'read:favorites', - - res: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'object', - optional: false, nullable: false, - ref: 'NoteFavorite', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - }, - required: [], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) - .andWhere('favorite.userId = :meId', { meId: user.id }) - .leftJoinAndSelect('favorite.note', 'note'); - - const favorites = await query - .take(ps.limit) - .getMany(); - - return await NoteFavorites.packMany(favorites, user); -}); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts deleted file mode 100644 index f9f769ace..000000000 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { NoteFavorites } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '@/server/api/define.js'; -import { ApiError } from '@/server/api/error.js'; -import { getNote } from '@/server/api/common/getters.js'; - -export const meta = { - tags: ['notes', 'favorites'], - - requireCredential: true, - - kind: 'write:favorites', - - errors: ['NO_SUCH_NOTE', 'ALREADY_FAVORITED'], -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['noteId'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get favoritee - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE'); - throw err; - }); - - // if already favorited - const exist = await NoteFavorites.countBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist) throw new ApiError('ALREADY_FAVORITED'); - - // Create favorite - await NoteFavorites.insert({ - id: genId(), - createdAt: new Date(), - noteId: note.id, - userId: user.id, - }); -}); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts deleted file mode 100644 index 416c70062..000000000 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { NoteFavorites } from '@/models/index.js'; -import define from '@/server/api/define.js'; -import { ApiError } from '@/server/api/error.js'; -import { getNote } from '@/server/api/common/getters.js'; - -export const meta = { - tags: ['notes', 'favorites'], - - requireCredential: true, - - kind: 'write:favorites', - - errors: ['NO_SUCH_NOTE', 'NOT_FAVORITED'], -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['noteId'], -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user) => { - // Get favoritee - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE'); - throw err; - }); - - // if already favorited - const exist = await NoteFavorites.findOneBy({ - noteId: note.id, - userId: user.id, - }); - - if (exist == null) throw new ApiError('NOT_FAVORITED'); - - // Delete favorite - await NoteFavorites.delete(exist.id); -}); diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 85ae2aa23..bb7c937a8 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,4 +1,4 @@ -import { NoteFavorites, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; +import { NoteThreadMutings, NoteWatchings } from '@/models/index.js'; import { ApiError } from '@/server/api/error.js'; import { getNote } from '@/server/api/common/getters.js'; import define from '@/server/api/define.js'; @@ -12,10 +12,6 @@ export const meta = { type: 'object', optional: false, nullable: false, properties: { - isFavorited: { - type: 'boolean', - optional: false, nullable: false, - }, isWatching: { type: 'boolean', optional: false, nullable: false, @@ -51,14 +47,7 @@ export default define(meta, paramDef, async (ps, user) => { throw err; }); - const [favorite, watching, threadMuting] = await Promise.all([ - NoteFavorites.count({ - where: { - userId: user.id, - noteId: note.id, - }, - take: 1, - }), + const [watching, threadMuting] = await Promise.all([ NoteWatchings.count({ where: { userId: user.id, @@ -76,7 +65,6 @@ export default define(meta, paramDef, async (ps, user) => { ]); return { - isFavorited: favorite !== 0, isWatching: watching !== 0, isMutedThread: threadMuting !== 0, }; diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 7197219fb..3780b9eb0 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,4 +1,4 @@ -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; +import { DriveFiles, Followings, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; import { awaitAll } from '@/prelude/await-all.js'; import define from '@/server/api/define.js'; import { ApiError } from '@/server/api/error.js'; @@ -76,10 +76,6 @@ export const meta = { type: 'integer', optional: false, nullable: false, }, - noteFavoritesCount: { - type: 'integer', - optional: false, nullable: false, - }, pageLikesCount: { type: 'integer', optional: false, nullable: false, @@ -148,9 +144,6 @@ export default define(meta, paramDef, async (ps, me) => { .innerJoin('reaction.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite') - .where('favorite.userId = :userId', { userId: user.id }) - .getCount(), pageLikesCount: PageLikes.createQueryBuilder('like') .where('like.userId = :userId', { userId: user.id }) .getCount(), diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 201c234b7..d5ec24f71 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -68,10 +68,6 @@ export const errors: Record message: 'That note is already added to that clip.', httpStatusCode: 409, }, - ALREADY_FAVORITED: { - message: 'That note is already favorited.', - httpStatusCode: 409, - }, ALREADY_FOLLOWING: { message: 'You are already following that user.', httpStatusCode: 409, @@ -332,10 +328,6 @@ export const errors: Record message: 'That note is not added to that clip.', httpStatusCode: 409, }, - NOT_FAVORITED: { - message: 'You have not favorited that note.', - httpStatusCode: 409, - }, NOT_FOLLOWING: { message: 'You are not following that user.', httpStatusCode: 409, From 9859537b024898bbddee11811dde22b55b2305be Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 26 May 2023 21:13:42 +0200 Subject: [PATCH 41/70] BREAKING migrate note favorites to clips The following endpoints are removed: - `api/i/favorites` - `api/notes/favorites/create` - `api/notes/favorites/delete` The following endpoints are changed: - `api/notes/state` - `api/users/stats` closes https://akkoma.dev/FoundKeyGang/FoundKey/issues/374 Changelog: Removed --- .../1685126322423-remove-favourites.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/backend/migration/1685126322423-remove-favourites.js diff --git a/packages/backend/migration/1685126322423-remove-favourites.js b/packages/backend/migration/1685126322423-remove-favourites.js new file mode 100644 index 000000000..fab024744 --- /dev/null +++ b/packages/backend/migration/1685126322423-remove-favourites.js @@ -0,0 +1,33 @@ +export class removeFavourites1685126322423 { + name = 'removeFavourites1685126322423'; + + async up(queryRunner) { + await queryRunner.query(` + WITH "new_clips" AS ( + INSERT INTO "clip" ("id", "createdAt", "userId", "name") + SELECT + RIGHT(GEN_RANDOM_UUID()::text, 10), + NOW(), + "userId", + '⭐' + FROM "note_favorite" + GROUP BY "userId" + RETURNING "id", "userId" + ) + INSERT INTO "clip_note" ("id", "noteId", "clipId") + SELECT + "note_favorite"."id", + "noteId", + "new_clips"."id" + FROM "note_favorite" + JOIN "new_clips" ON "note_favorite"."userId" = "new_clips"."userId" + `); + await queryRunner.query(`DROP TABLE "note_favorite"`); + } + + async down(queryRunner) { + // can't revert the migration to clips, can only recreate the database table + await queryRunner.query(`CREATE TABLE "note_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_af0da35a60b9fa4463a62082b36" PRIMARY KEY ("id"))`); + } +} + From f6381e3227d64485445ca8498b676a78d7d61970 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 00:41:57 +0200 Subject: [PATCH 42/70] remove unused locale strings --- locales/en-US.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index fe1dc137e..abf6e010c 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -32,9 +32,6 @@ signup: "Sign Up" save: "Save" users: "Users" addUser: "Add a user" -favorite: "Add to favorites" -favorites: "Favorites" -unfavorite: "Remove from favorites" pin: "Pin to profile" unpin: "Unpin from profile" copyContent: "Copy contents" From 35e814fab41d311021a4dbe73955150d054211ba Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 11:18:06 +0200 Subject: [PATCH 43/70] fix: don't use psql 13 functions The function GEN_RANDOM_UUID was only introduced to built in postgresql in version 13, however, the installation guide specifies that version 12 should be sufficient. --- packages/backend/migration/1685126322423-remove-favourites.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/migration/1685126322423-remove-favourites.js b/packages/backend/migration/1685126322423-remove-favourites.js index fab024744..57a31e126 100644 --- a/packages/backend/migration/1685126322423-remove-favourites.js +++ b/packages/backend/migration/1685126322423-remove-favourites.js @@ -6,7 +6,7 @@ export class removeFavourites1685126322423 { WITH "new_clips" AS ( INSERT INTO "clip" ("id", "createdAt", "userId", "name") SELECT - RIGHT(GEN_RANDOM_UUID()::text, 10), + LEFT(MD5(RANDOM()::text), 10), NOW(), "userId", '⭐' From a16e921ffcb4d1ecd84d41caa33692bf2c00bc20 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 16:52:11 +0200 Subject: [PATCH 44/70] add position token Co-authored-by: syuilo --- packages/client/src/components/mfm.ts | 6 ++++++ packages/client/src/scripts/mfm-tags.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 64fe5bdb5..58e8571f4 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -185,6 +185,12 @@ export default defineComponent({ style = `transform: rotate(${degrees}deg); transform-origin: center center;`; break; } + case 'position': { + const x = parseFloat(token.props.args.x ?? '0'); + const y = parseFloat(token.props.args.y ?? '0'); + style = `transform: translateX(${x}em) translateY(${y}em);`; + break; + } } if (style == null) { return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 18e8d7038..7f721361e 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -1 +1 @@ -export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate']; +export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position']; From 11121bbb4caf447c79cd774684b947b8b12768d1 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 17:16:22 +0200 Subject: [PATCH 45/70] add scale tag Co-authored-by: syuilo Co-authored-by: tamaina --- packages/client/src/components/mfm.ts | 51 +++++++++++++++---------- packages/client/src/scripts/mfm-tags.ts | 2 +- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 58e8571f4..75f1db07d 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -37,6 +37,10 @@ export default defineComponent({ type: Boolean, default: true, }, + rootScale: { + type: Number, + default: 1 + } }, render() { @@ -50,7 +54,7 @@ export default defineComponent({ return t.match(/^[0-9.]+s$/) ? t : null; }; - const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | VNode[] => { + const genEl = (ast: mfm.MfmNode[], scale: Number) => ast.map((token): VNode | VNode[] => { switch (token.type) { case 'text': { const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); @@ -69,17 +73,17 @@ export default defineComponent({ } case 'bold': { - return h('b', genEl(token.children)); + return h('b', genEl(token.children, scale)); } case 'strike': { - return h('del', genEl(token.children)); + return h('del', genEl(token.children, scale)); } case 'italic': { return h('i', { style: 'font-style: oblique;', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'fn': { @@ -139,18 +143,18 @@ export default defineComponent({ } case 'x2': { return h('span', { - class: 'mfm-x2', - }, genEl(token.children)); + class: 'mfm-x2' + }, genEl(token.children, scale * 2)); } case 'x3': { return h('span', { - class: 'mfm-x3', - }, genEl(token.children)); + class: 'mfm-x3' + }, genEl(token.children, scale * 3)); } case 'x4': { return h('span', { - class: 'mfm-x4', - }, genEl(token.children)); + class: 'mfm-x4' + }, genEl(token.children, scale * 4)); } case 'font': { const family = @@ -167,7 +171,7 @@ export default defineComponent({ case 'blur': { return h('span', { class: '_mfm_blur_', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'rainbow': { const speed = validTime(token.props.args.speed) || '1s'; @@ -176,9 +180,9 @@ export default defineComponent({ } case 'sparkle': { if (!this.$store.state.animatedMfm) { - return genEl(token.children); + return genEl(token.children, scale); } - return h(MkSparkle, {}, genEl(token.children)); + return h(MkSparkle, {}, genEl(token.children, scale)); } case 'rotate': { const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90'; @@ -191,26 +195,33 @@ export default defineComponent({ style = `transform: translateX(${x}em) translateY(${y}em);`; break; } + case 'scale': { + const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5); + const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5); + style = `transform: scale(${x}, ${y});`; + scale = scale * Math.max(x, y); + break; + } } if (style == null) { - return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); + return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); } else { return h('span', { style: 'display: inline-block;' + style, - }, genEl(token.children)); + }, genEl(token.children, scale)); } } case 'small': { return h('small', { class: '_mfm_small_' - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'center': { return h('div', { style: 'text-align:center;', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'url': { @@ -226,7 +237,7 @@ export default defineComponent({ key: Math.random(), url: token.props.url, rel: 'nofollow noopener', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'mention': { @@ -264,7 +275,7 @@ export default defineComponent({ case 'quote': { return h(this.nowrap ? 'span' : 'div', { class: 'quote', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'emojiCode': { @@ -317,6 +328,6 @@ export default defineComponent({ }).flat(); // Parse ast to DOM - return h('span', genEl(ast)); + return h('span', genEl(ast, this.rootScale ?? 1)); }, }); diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 7f721361e..73de7b3be 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -1 +1 @@ -export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position']; +export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale']; From a45b611a7e300872937601a052be7b7d9e907912 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 17:23:26 +0200 Subject: [PATCH 46/70] add fg and bg tag Co-authored-by: syuilo --- packages/client/src/components/mfm.ts | 12 ++++++++++++ packages/client/src/scripts/mfm-tags.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 75f1db07d..e056627bf 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -202,6 +202,18 @@ export default defineComponent({ scale = scale * Math.max(x, y); break; } + case 'fg': { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + style = `color: #${color};`; + break; + } + case 'bg': { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + style = `background-color: #${color};`; + break; + } } if (style == null) { return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 73de7b3be..84415f400 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -1 +1 @@ -export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale']; +export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale', 'fg', 'bg']; From f6ff21ee538a1467ef78ab4e6fd105661e8ae9d5 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 18:41:46 +0200 Subject: [PATCH 47/70] update mfm-js lib --- packages/backend/package.json | 2 +- packages/client/package.json | 2 +- packages/client/src/components/mfm.ts | 3 +-- yarn.lock | 16 ++++++++-------- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 6c62d9231..097f6ee73 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -67,7 +67,7 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "mfm-js": "0.22.1", + "mfm-js": "0.23.3", "mime-types": "2.1.35", "mocha": "10.2.0", "multer": "1.4.5-lts.1", diff --git a/packages/client/package.json b/packages/client/package.json index e8ce60b35..545c58c9f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -34,7 +34,7 @@ "json5": "2.2.1", "katex": "0.16.0", "matter-js": "0.18.0", - "mfm-js": "0.22.1", + "mfm-js": "0.23.3", "photoswipe": "5.2.8", "prismjs": "1.28.0", "punycode": "2.1.1", diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index e056627bf..68f447add 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -10,7 +10,6 @@ import MkSearch from '@/components/mfm-search.vue'; import MkSparkle from '@/components/sparkle.vue'; import MkA from '@/components/global/a.vue'; import { host } from '@/config'; -import { MFM_TAGS } from '@/scripts/mfm-tags'; export default defineComponent({ props: { @@ -46,7 +45,7 @@ export default defineComponent({ render() { if (this.text == null || this.text === '') return; - const ast = (this.plain ? mfm.parsePlain : mfm.parse)(this.text, { fnNameList: MFM_TAGS }); + const ast = (this.plain ? mfm.parseSimple : mfm.parse)(this.text); const validTime = (t: string | true) => { if (typeof t !== 'string') return null; diff --git a/yarn.lock b/yarn.lock index 1786f0c51..e4e95a074 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3750,7 +3750,7 @@ __metadata: koa-send: 5.0.1 koa-slow: 2.1.0 koa-views: 7.0.2 - mfm-js: 0.22.1 + mfm-js: 0.23.3 mime-types: 2.1.35 mocha: 10.2.0 multer: 1.4.5-lts.1 @@ -4720,7 +4720,7 @@ __metadata: json5: 2.2.1 katex: 0.16.0 matter-js: 0.18.0 - mfm-js: 0.22.1 + mfm-js: 0.23.3 photoswipe: 5.2.8 prismjs: 1.28.0 punycode: 2.1.1 @@ -11671,12 +11671,12 @@ __metadata: languageName: node linkType: hard -"mfm-js@npm:0.22.1": - version: 0.22.1 - resolution: "mfm-js@npm:0.22.1" +"mfm-js@npm:0.23.3": + version: 0.23.3 + resolution: "mfm-js@npm:0.23.3" dependencies: - twemoji-parser: 14.0.x - checksum: 6d9756c7bd8abf6462fb6403de4656f607a83839eb6b66a05b10eddcd201b5f78f5fe3d0df029936546143fd9cbf112e8369287aed32026e50bb03ce89b4c4f8 + twemoji-parser: 14.0.0 + checksum: 7079f80a53a9afc8599333f3256fb18a6bf7c01102a2f8f2be657843726a34835e2af34e26bc5b27e45b217fb2f120c0d3006e9fab2a972c845e9f7361e3cc1b languageName: node linkType: hard @@ -16723,7 +16723,7 @@ __metadata: languageName: node linkType: hard -"twemoji-parser@npm:14.0.0, twemoji-parser@npm:14.0.x": +"twemoji-parser@npm:14.0.0": version: 14.0.0 resolution: "twemoji-parser@npm:14.0.0" checksum: 8eede69cf71f94735de7b6fddf5dfbfe3cb2e01baefc3201360984ccc97cfc659f206c8f73bd1405a2282779af3b79a8c9bed3864c672e15e2dc6f8ce4810452 From 404bb3b6b6e72cf31c64bd4baf825467354e9009 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 19:16:44 +0200 Subject: [PATCH 48/70] parseSimple in backend --- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 1a73e31df..b5abf56b5 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -178,7 +178,7 @@ export default define(meta, paramDef, async (ps, _user, token) => { const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; if (newName != null) { - const tokens = mfm.parsePlain(newName); + const tokens = mfm.parseSimple(newName); emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); } From a72f4300aa7b1415b06557f385d6fc4c806df0a1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 10:51:36 +0200 Subject: [PATCH 49/70] remove unused rootScale and scale params --- packages/client/src/components/mfm.ts | 39 ++++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 68f447add..bb0b71a5a 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -36,10 +36,6 @@ export default defineComponent({ type: Boolean, default: true, }, - rootScale: { - type: Number, - default: 1 - } }, render() { @@ -53,7 +49,7 @@ export default defineComponent({ return t.match(/^[0-9.]+s$/) ? t : null; }; - const genEl = (ast: mfm.MfmNode[], scale: Number) => ast.map((token): VNode | VNode[] => { + const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | VNode[] => { switch (token.type) { case 'text': { const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); @@ -72,17 +68,17 @@ export default defineComponent({ } case 'bold': { - return h('b', genEl(token.children, scale)); + return h('b', genEl(token.children)); } case 'strike': { - return h('del', genEl(token.children, scale)); + return h('del', genEl(token.children)); } case 'italic': { return h('i', { style: 'font-style: oblique;', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'fn': { @@ -143,17 +139,17 @@ export default defineComponent({ case 'x2': { return h('span', { class: 'mfm-x2' - }, genEl(token.children, scale * 2)); + }, genEl(token.children)); } case 'x3': { return h('span', { class: 'mfm-x3' - }, genEl(token.children, scale * 3)); + }, genEl(token.children)); } case 'x4': { return h('span', { class: 'mfm-x4' - }, genEl(token.children, scale * 4)); + }, genEl(token.children)); } case 'font': { const family = @@ -170,7 +166,7 @@ export default defineComponent({ case 'blur': { return h('span', { class: '_mfm_blur_', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'rainbow': { const speed = validTime(token.props.args.speed) || '1s'; @@ -179,9 +175,9 @@ export default defineComponent({ } case 'sparkle': { if (!this.$store.state.animatedMfm) { - return genEl(token.children, scale); + return genEl(token.children); } - return h(MkSparkle, {}, genEl(token.children, scale)); + return h(MkSparkle, {}, genEl(token.children)); } case 'rotate': { const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90'; @@ -198,7 +194,6 @@ export default defineComponent({ const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5); const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5); style = `transform: scale(${x}, ${y});`; - scale = scale * Math.max(x, y); break; } case 'fg': { @@ -215,24 +210,24 @@ export default defineComponent({ } } if (style == null) { - return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); + return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); } else { return h('span', { style: 'display: inline-block;' + style, - }, genEl(token.children, scale)); + }, genEl(token.children)); } } case 'small': { return h('small', { class: '_mfm_small_' - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'center': { return h('div', { style: 'text-align:center;', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'url': { @@ -248,7 +243,7 @@ export default defineComponent({ key: Math.random(), url: token.props.url, rel: 'nofollow noopener', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'mention': { @@ -286,7 +281,7 @@ export default defineComponent({ case 'quote': { return h(this.nowrap ? 'span' : 'div', { class: 'quote', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'emojiCode': { @@ -339,6 +334,6 @@ export default defineComponent({ }).flat(); // Parse ast to DOM - return h('span', genEl(ast, this.rootScale ?? 1)); + return h('span', genEl(ast)); }, }); From 3f9b09e3e7da50e0d1b35d996ebc5b4cfbddd5f8 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 11:37:30 +0200 Subject: [PATCH 50/70] simplify MFM position CSS --- packages/client/src/components/mfm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index bb0b71a5a..40b11535f 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -187,7 +187,7 @@ export default defineComponent({ case 'position': { const x = parseFloat(token.props.args.x ?? '0'); const y = parseFloat(token.props.args.y ?? '0'); - style = `transform: translateX(${x}em) translateY(${y}em);`; + style = `transform: translate(${x}em, ${y}em);`; break; } case 'scale': { From 989f0ce41db432c70038075f0c72aa3a9b560458 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 11:40:34 +0200 Subject: [PATCH 51/70] fix MFM fg/bg color regex CSS colors are either 3 or 6 hex digits, not 3 to 6 (which would allow 4 and 5 digit hex codes, which are not accepted). Also adds an explicit null/undefined check. Changes the default color for the $[bg ] function to something different than the fg color so if you use both functions on a piece of text with default values, the text stays somewhat readable. --- packages/client/src/components/mfm.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 40b11535f..2d04a9731 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -197,14 +197,14 @@ export default defineComponent({ break; } case 'fg': { - let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + let color = token.props.args.color ?? 'f00'; + if (!/^([0-9a-f]{3}){1,2}$/i.test(color)) color = 'f00'; style = `color: #${color};`; break; } case 'bg': { - let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + let color = token.props.args.color ?? '0f0'; + if (!/^([0-9a-f]{3}){1,2}$/i.test(color)) color = '0f0'; style = `background-color: #${color};`; break; } From 5408929c2a071ed3829da9f16428c4f0cd6759a7 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 20:19:05 +0200 Subject: [PATCH 52/70] fixup: remove another reference to favorites This is a fixup for commit de81fac33453b72e76ae5bdc3186da45fc0c55e6. --- locales/en-US.yml | 1 - packages/client/src/pages/settings/account-info.vue | 4 ---- 2 files changed, 5 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index abf6e010c..947ed7bfb 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -645,7 +645,6 @@ disableShowingAnimatedImages: "Don't play animated images" verificationEmailSent: "A verification email has been sent. Please follow the included\ \ link to complete verification." emailVerified: "Email has been verified" -noteFavoritesCount: "Number of favorite notes" pageLikesCount: "Number of liked Pages" pageLikedCount: "Number of received Page likes" contact: "Contact" diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index ec40bd039..8287d2c3c 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -50,10 +50,6 @@ - - - - From d55f8742b8cd6b2965c5e93690c8a57d9350b8e1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 20:34:28 +0200 Subject: [PATCH 53/70] cache when computing note count etc. --- packages/backend/src/server/api/endpoints/users/stats.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index 3780b9eb0..1a0e9031e 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -182,6 +182,13 @@ export default define(meta, paramDef, async (ps, me) => { result.followingCount = result.localFollowingCount + result.remoteFollowingCount; result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + + // store the updated counts in the user table to potentially fix the cache + Users.update(user.id, { + followersCount: result.followersCount, + followingCount: result.followingCount, + notesCount: result.notesCount, + }); } return result; From f883d2e31bc4a714e3e67aa1c7a512bf3ff27380 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 20:42:10 +0200 Subject: [PATCH 54/70] fix showing follower/following count --- packages/backend/src/models/repositories/user.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index ddcb97f90..d8772c97d 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -302,10 +302,10 @@ export const UserRepository = db.getRepository(User).extend({ const ffVisible = await this.areFollowersVisibleTo(user, me); - const followingCount = opts.detail ? null : + const followingCount = !opts.detail ? null : ffVisible ? user.followingCount : null; - const followersCount = opts.detail ? null : + const followersCount = !opts.detail ? null : ffVisible ? user.followersCount : null; const packed = { From 98898ece1845ef918e6352bab60e7a77058426ba Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 18:43:52 +0000 Subject: [PATCH 55/70] Translated using Weblate (German) Currently translated at 100.0% (1209 of 1209 strings) Co-authored-by: Johann Co-authored-by: Johann150 Translate-URL: http://translate.akkoma.dev/projects/foundkey/foundkey/de/ Translation: Foundkey/foundkey --- locales/de-DE.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 86eac682d..c4b6b6c7a 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -820,6 +820,7 @@ _ffVisibility: public: "Öffentlich" followers: "Nur für Follower sichtbar" private: "Privat" + nobody: Niemand (auch nicht du) _signup: almostThere: "Fast geschafft" emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich\ @@ -1381,7 +1382,7 @@ addTag: Schlagwörter hinzufügen removeTag: Schlagwörter entfernen exportAll: Alle exportieren exportSelected: Gewählte exportieren -federateBlocks: Andere Instanzen mitteilen, wenn ich jemanden blockiere +federateBlocks: Anderen Instanzen mitteilen, wenn ich jemanden blockiere selectMode: Auswählen selectAll: Alle auswählen renoteUnmute: Renotes zeigen From 30403fbe5cb381a263d9f03bfb5f343742333e13 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 27 May 2023 18:43:53 +0000 Subject: [PATCH 56/70] Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Weblate Translate-URL: http://translate.akkoma.dev/projects/foundkey/foundkey/ Translation: Foundkey/foundkey --- locales/ar-SA.yml | 4 - locales/bn-BD.yml | 4 - locales/ca-ES.yml | 3 - locales/cs-CZ.yml | 3 - locales/de-DE.yml | 4 - locales/es-ES.yml | 4 - locales/fr-FR.yml | 4 - locales/id-ID.yml | 4 - locales/it-IT.yml | 4 - locales/ja-JP.yml | 4 - locales/ja-KS.yml | 3 - locales/kn-IN.yml | 3 - locales/ko-KR.yml | 259 ++++++++-------------------------------------- locales/nl-NL.yml | 3 - locales/pl-PL.yml | 4 - locales/pt-PT.yml | 3 - locales/ro-RO.yml | 3 - locales/ru-RU.yml | 4 - locales/sk-SK.yml | 4 - locales/sv-SE.yml | 3 - locales/tr-TR.yml | 3 - locales/uk-UA.yml | 4 - locales/vi-VN.yml | 4 - locales/zh-CN.yml | 4 - locales/zh-TW.yml | 4 - 25 files changed, 45 insertions(+), 301 deletions(-) diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 2556bdc5e..cf486fb37 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -32,9 +32,6 @@ signup: "أنشئ حسابًا" save: "حفظ" users: "المستخدمون" addUser: "اضافة مستخدم" -favorite: "أضفها للمفضلة" -favorites: "المفضلات" -unfavorite: "إزالة من المفضلة" pin: "دبّسها على الصفحة الشخصية" unpin: "ألغ تدبيسها من ملفك الشخصي" copyContent: "انسخ المحتوى" @@ -581,7 +578,6 @@ loadRawImages: "حمّل الصور الأصلية بدلًا من المصغر disableShowingAnimatedImages: "لا تشغّل الصور المتحركة" verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق." emailVerified: "تُحقّق من بريدك الإلكتروني" -noteFavoritesCount: "عدد الملاحظات المفضلة" pageLikesCount: "عدد الصفحات التي أعجبت بها" pageLikedCount: "عدد صفحاتك المُعجب بها" contact: "التواصل" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 6e160296b..4a84a044f 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -32,9 +32,6 @@ signup: "নিবন্ধন করুন" save: "সংরক্ষণ" users: "ব্যবহারকারীগণ" addUser: "ব্যবহারকারী যোগ করুন" -favorite: "পছন্দ" -favorites: "পছন্দগুলি" -unfavorite: "পছন্দ না" pin: "পিন করা" unpin: "পিন সরান" copyContent: "বিষয়বস্তু কপি করুন" @@ -633,7 +630,6 @@ disableShowingAnimatedImages: "অ্যানিমেটেড চিত্র verificationEmailSent: "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে। সেটআপ সম্পূর্ণ করতে ইমেল এর\ \ লিঙ্ক অনুসরণ করুন।" emailVerified: "ইমেইল নিশ্চিত করা হয়েছে" -noteFavoritesCount: "পছন্দ করা নোটের সংখ্যা" pageLikesCount: "পেজ লাইক করেছেন" pageLikedCount: "পেজ লাইক পেয়েছেন" contact: "পরিচিতি সমূহ" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index e2d229bbb..aec2a9ec6 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -32,9 +32,6 @@ signup: "Registrar-se" save: "Desar" users: "Usuaris" addUser: "Afegir un usuari" -favorite: "Afegir a preferits" -favorites: "Favorits" -unfavorite: "Eliminar dels preferits" pin: "Fixar al perfil" unpin: "Para de fixar del perfil" copyContent: "Copiar el contingut" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 96c3963be..bc31cf821 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -32,9 +32,6 @@ signup: "Registrace" save: "Uložit" users: "Uživatelé" addUser: "Přidat uživatele" -favorite: "Oblíbené" -favorites: "Oblíbené" -unfavorite: "Odebrat z oblízených" pin: "Připnout" unpin: "Odepnout" copyContent: "Zkopírovat obsah" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index c4b6b6c7a..fb39f8141 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -33,9 +33,6 @@ signup: "Registrieren" save: "Speichern" users: "Benutzer" addUser: "Benutzer hinzufügen" -favorite: "Zu Favoriten hinzufügen" -favorites: "Favoriten" -unfavorite: "Aus Favoriten entfernen" pin: "An dein Profil anheften" unpin: "Von deinem Profil lösen" copyContent: "Inhalt kopieren" @@ -654,7 +651,6 @@ disableShowingAnimatedImages: "Animierte Bilder nicht abspielen" verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet.\ \ Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen." emailVerified: "Email-Adresse bestätigt" -noteFavoritesCount: "Anzahl an als Favorit markierter Notizen" pageLikesCount: "Anzahl an als \"Gefällt mir\" markierter Seiten" pageLikedCount: "Anzahl erhaltener \"Gefällt mir\" auf Seiten" contact: "Kontakt" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index fbfcd9352..b8e4014ed 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -33,9 +33,6 @@ signup: "Registrarse" save: "Guardar" users: "Usuarios" addUser: "Agregar usuario" -favorite: "Favorito" -favorites: "Favoritos" -unfavorite: "Quitar de favoritos" pin: "Fijar" unpin: "Desfijar" copyContent: "Copiar contenido" @@ -638,7 +635,6 @@ verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación \ favor, acceda al enlace proporcionado en el correo electrónico para completar\ \ la configuración." emailVerified: "Su dirección de correo electrónico ha sido verificada." -noteFavoritesCount: "Número de notas favoritas" pageLikesCount: "Número de favoritos en la página" pageLikedCount: "Número de favoritos de su página" contact: "Contacto" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 0cea0bfcb..d6df6af2c 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -34,9 +34,6 @@ signup: "S’inscrire" save: "Enregistrer" users: "Utilisateur·rice·s" addUser: "Ajouter un·e utilisateur·rice" -favorite: "Ajouter aux favoris" -favorites: "Favoris" -unfavorite: "Retirer des favoris" pin: "Épingler sur le profil" unpin: "Désépingler" copyContent: "Copier le contenu" @@ -647,7 +644,6 @@ disableShowingAnimatedImages: "Désactiver l'animation des images" verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\ \ lien pour compléter la vérification." emailVerified: "Votre adresse e-mail a été vérifiée" -noteFavoritesCount: "Nombre de notes dans les favoris" pageLikesCount: "Nombre de pages aimées" pageLikedCount: "Nombre de vos pages aimées" contact: "Contact" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 2114dc99b..cc188e570 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -32,9 +32,6 @@ signup: "Daftar" save: "Simpan" users: "Pengguna" addUser: "Tambah pengguna" -favorite: "Favorit" -favorites: "Favorit" -unfavorite: "Hapus favorit" pin: "Sematkan ke profil" unpin: "Lepas sematan dari profil" copyContent: "Salin konten" @@ -639,7 +636,6 @@ disableShowingAnimatedImages: "Jangan mainkan gambar bergerak" verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang\ \ telah disertakan untuk menyelesaikan verifikasi." emailVerified: "Surel telah diverifikasi" -noteFavoritesCount: "Jumlah catatan yang difavoritkan" pageLikesCount: "Jumlah suka yang diterima Halaman" pageLikedCount: "Jumlah Halaman yang disukai" contact: "Kontak" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 1f02dddb7..05a5cee16 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -33,9 +33,6 @@ signup: "Iscriviti" save: "Salva" users: "Utente" addUser: "Aggiungi utente" -favorite: "Preferiti" -favorites: "Preferiti" -unfavorite: "Rimuovi nota dai preferiti" pin: "Fissa sul profilo" unpin: "Non fissare sul profilo" copyContent: "Copia il contenuto" @@ -625,7 +622,6 @@ disableShowingAnimatedImages: "Disabilita le immagini animate" verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere\ \ al collegamento per compiere la verifica." emailVerified: "Il tuo indirizzo email è stato verificato" -noteFavoritesCount: "Conteggio note tra i preferiti" pageLikesCount: "Numero di pagine che ti piacciono" pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\"" contact: "Contatti" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6ff9e5d10..c10755320 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -31,9 +31,6 @@ signup: "新規登録" save: "保存" users: "ユーザー" addUser: "ユーザーを追加" -favorite: "お気に入り" -favorites: "お気に入り" -unfavorite: "お気に入り解除" pin: "ピン留め" unpin: "ピン留め解除" copyContent: "内容をコピー" @@ -587,7 +584,6 @@ loadRawImages: "添付画像のサムネイルをオリジナル画質にする" disableShowingAnimatedImages: "アニメーション画像を再生しない" verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。" emailVerified: "メールアドレスが確認されました" -noteFavoritesCount: "お気に入りノートの数" pageLikesCount: "Pageにいいねした数" pageLikedCount: "Pageにいいねされた数" contact: "連絡先" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index cb78b4651..52d457a81 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -30,9 +30,6 @@ signup: "新規登録" save: "保存" users: "ユーザー" addUser: "ユーザーを追加や" -favorite: "お気に入り" -favorites: "お気に入り" -unfavorite: "やっぱ気に入らん" pin: "ピン留めしとく" unpin: "やっぱピン留めせん" copyContent: "内容をコピー" diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml index 6ae86687f..8c6cb1972 100644 --- a/locales/kn-IN.yml +++ b/locales/kn-IN.yml @@ -27,9 +27,6 @@ signup: "ನೋಂದಣಿ" save: "ಉಳಿಸಿ" users: "ಬಳಕೆದಾರ" addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ" -favorite: "ಮೆಚ್ಚಿನ" -favorites: "ಮೆಚ್ಚಿನವುಗಳು" -unfavorite: "ಮೆಚ್ಚುಗೆ ಅಳಿಸು" pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು" unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ" copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index a729d0a3a..e89035ed7 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1,7 +1,8 @@ ---- _lang_: "한국어" headlineMisskey: "노트로 연결되는 네트워크" -introMisskey: "환영합니다! FoundKey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀" +introMisskey: "환영합니다! FoundKey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고\ + \ 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요\U0001F4E1\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할\ + \ 수도 있습니다\U0001F44D\n새로운 세계를 탐험해 보세요\U0001F680" monthAndDay: "{month}월 {day}일" search: "검색" notifications: "알림" @@ -12,7 +13,6 @@ fetchingAsApObject: "연합에서 조회 중" ok: "OK" gotIt: "알겠어요" cancel: "취소" -enterUsername: "유저명 입력" renotedBy: "{user}님이 Renote" noNotes: "노트가 없습니다" noNotifications: "표시할 알림이 없습니다" @@ -28,16 +28,9 @@ login: "로그인" loggingIn: "로그인 중" logout: "로그아웃" signup: "회원 가입" -uploading: "업로드 중" save: "저장" users: "유저" addUser: "유저 추가" -favorite: "즐겨찾기" -favorites: "즐겨찾기" -unfavorite: "즐겨찾기에서 제거" -favorited: "즐겨찾기에 등록했습니다" -alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다" -cantFavorite: "즐겨찾기에 등록하지 못했습니다" pin: "프로필에 고정" unpin: "프로필에서 고정 해제" copyContent: "내용 복사" @@ -48,7 +41,6 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니 addToList: "리스트에 추가" sendMessage: "메시지 보내기" copyUsername: "유저명 복사" -searchUser: "사용자 검색" reply: "답글" loadMore: "더 보기" showMore: "더 보기" @@ -68,7 +60,6 @@ unfollowConfirm: "{name}님을 언팔로우하시겠습니까?" exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다." importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다." lists: "리스트" -noLists: "리스트가 없습니다" note: "노트" notes: "노트" following: "팔로잉" @@ -80,7 +71,8 @@ error: "오류" somethingHappened: "오류가 발생했습니다" retry: "다시 시도" pageLoadError: "페이지를 불러오지 못했습니다." -pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후 다시 시도해 주세요." +pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후\ + \ 다시 시도해 주세요." serverIsDead: "서버로부터 응답이 없습니다. 잠시 후 다시 시도해주세요." youShouldUpgradeClient: "이 페이지를 표시하려면 새로고침하여 새로운 버전의 클라이언트를 이용해 주십시오." enterListName: "리스트 이름을 입력" @@ -92,21 +84,15 @@ followRequest: "팔로우 요청" followRequests: "팔로우 요청" unfollow: "팔로우 해제" followRequestPending: "팔로우 허가 대기중" -enterEmoji: "이모지 입력" renote: "Renote" unrenote: "Renote 취소" -renoted: "Renote 하였습니다" -cantRenote: "이 게시물은 Renote할 수 없습니다." -cantReRenote: "Renote를 Renote할 수 없습니다." quote: "인용" pinnedNote: "고정해놓은 노트" -pinned: "프로필에 고정" you: "당신" clickToShow: "클릭하여 보기" sensitive: "열람주의" add: "추가" reaction: "리액션" -reactionSetting: "선택기에 표시할 리액션" reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다." attachCancel: "첨부 취소" markAsSensitive: "열람주의로 설정" @@ -130,14 +116,13 @@ editWidgetsExit: "편집 종료" customEmojis: "커스텀 이모지" emoji: "이모지" emojis: "이모지" -emojiName: "이모지 이름" -emojiUrl: "이모지 URL" addEmoji: "이모지 추가" -settingGuide: "추천 설정" cacheRemoteFiles: "리모트 파일을 캐시" -cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다." +cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라\ + \ 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다." flagAsBot: "나는 봇입니다" -flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다." +flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여\ + \ 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다." flagAsCat: "나는 고양이다냥" flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요." flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기" @@ -147,40 +132,32 @@ addAccount: "계정 추가" loginFailed: "로그인에 실패했습니다" showOnRemote: "리모트에서 보기" general: "일반" -wallpaper: "배경" setWallpaper: "배경화면 설정" removeWallpaper: "배경 제거" -searchWith: "검색: {q}" youHaveNoLists: "리스트가 없습니다" followConfirm: "{name}님을 팔로우 하시겠습니까?" proxyAccount: "프록시 계정" -proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 인스턴스로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다." +proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트\ + \ 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 인스턴스로 배달되지 않기 때문에, 대신 프록시 계정이\ + \ 해당 유저를 팔로우하도록 합니다." host: "호스트" selectUser: "유저 선택" recipient: "수신인" annotation: "내용에 대한 주석" federation: "연합" -instances: "인스턴스" registeredAt: "등록 날짜" latestRequestSentAt: "마지막으로 요청을 보낸 시간" latestRequestReceivedAt: "마지막으로 요청을 받은 시간" latestStatus: "마지막 상태" -storageUsage: "스토리지 사용량" charts: "차트" perHour: "1시간마다" perDay: "1일마다" stopActivityDelivery: "액티비티 보내지 않기" blockThisInstance: "이 인스턴스를 차단" -operations: "작업" software: "소프트웨어" version: "버전" -metadata: "메타데이터" withNFiles: "{n}개의 파일" -monitor: "모니터" jobQueue: "작업 대기열" -cpuAndMemory: "CPU와 메모리" -network: "네트워크" -disk: "디스크" instanceInfo: "인스턴스 정보" statistics: "통계" clearQueue: "대기열 비우기" @@ -189,7 +166,8 @@ clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되 clearCachedFiles: "캐시 비우기" clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?" blockedInstances: "차단된 인스턴스" -blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다." +blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와\ + \ 통신할 수 없게 됩니다." muteAndBlock: "뮤트 및 차단" mutedUsers: "뮤트한 유저" blockedUsers: "차단한 유저" @@ -211,9 +189,6 @@ all: "전체" subscribing: "구독 중" publishing: "배포 중" notResponding: "응답 없음" -instanceFollowing: "인스턴스의 팔로잉" -instanceFollowers: "인스턴스의 팔로워" -instanceUsers: "인스턴스의 유저" changePassword: "비밀번호 변경" security: "보안" retypedNotMatch: "입력이 일치하지 않습니다." @@ -229,7 +204,6 @@ lookup: "조회" announcements: "공지사항" imageUrl: "이미지 URL" remove: "삭제" -removed: "삭제하였습니다" removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?" deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?" resetAreYouSure: "초기화 하시겠습니까?" @@ -237,7 +211,8 @@ saved: "저장하였습니다" messaging: "대화" upload: "업로드" keepOriginalUploading: "원본 이미지를 유지" -keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서 웹 공개용 이미지를 생성합니다." +keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서\ + \ 웹 공개용 이미지를 생성합니다." fromDrive: "드라이브에서" fromUrl: "URL로부터" uploadFromUrl: "URL 업로드" @@ -269,7 +244,6 @@ lightThemes: "밝은 테마" darkThemes: "어두운 테마" syncDeviceDarkMode: "디바이스의 다크 모드 설정과 동기화" drive: "드라이브" -fileName: "파일명" selectFile: "파일 선택" selectFiles: "파일 선택" selectFolder: "폴더 선택" @@ -280,8 +254,6 @@ createFolder: "폴더 만들기" renameFolder: "폴더 이름 바꾸기" deleteFolder: "폴더 삭제" addFile: "파일 추가" -emptyDrive: "드라이브가 비어 있습니다" -emptyFolder: "폴더가 비어 있습니다" unableToDelete: "삭제할 수 없습니다" inputNewFileName: "바꿀 파일명을 입력해 주세요" inputNewDescription: "새 캡션을 입력해 주세요" @@ -318,7 +290,6 @@ pages: "페이지" enableLocalTimeline: "로컬 타임라인 활성화" enableGlobalTimeline: "글로벌 타임라인 활성화" disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다." -registration: "등록" enableRegistration: "신규 회원가입을 활성화" invite: "초대" driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량" @@ -327,22 +298,12 @@ inMb: "메가바이트 단위" iconUrl: "아이콘 URL" bannerUrl: "배너 이미지 URL" backgroundImageUrl: "배경 이미지 URL" -basicInfo: "기본 정보" pinnedUsers: "고정된 유저" pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다." -pinnedPages: "고정한 페이지" -pinnedPagesDescription: "인스턴스의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다." -pinnedClipId: "고정할 클립의 ID" -pinnedNotes: "고정해놓은 노트" -hcaptcha: "hCaptcha" -enableHcaptcha: "hCaptcha 활성화" hcaptchaSiteKey: "사이트 키" hcaptchaSecretKey: "시크릿 키" -recaptcha: "reCAPTCHA" -enableRecaptcha: "reCAPTCHA 활성화" recaptchaSiteKey: "사이트 키" recaptchaSecretKey: "시크릿 키" -avoidMultiCaptchaConfirm: "여러 Captcha를 사용하는 경우 간섭이 발생할 가능성이 있습니다. 다른 Captcha를 비활성화하시겠습니까? 취소를 눌러 여러 Captcha를 활성화한 상태로 두는 것도 가능합니다." antennas: "안테나" manageAntennas: "안테나 관리" name: "이름" @@ -352,7 +313,6 @@ antennaExcludeKeywords: "제외할 키워드" antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다" notifyAntenna: "새로운 노트를 알림" withFileAntenna: "파일이 첨부된 노트만" -enableServiceworker: "ServiceWorker 사용" antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다" caseSensitive: "대소문자를 구분" withReplies: "답글 포함" @@ -367,11 +327,8 @@ popularUsers: "인기 유저" recentlyUpdatedUsers: "최근 활동한 유저" recentlyRegisteredUsers: "최근 가입한 유저" recentlyDiscoveredUsers: "최근 발견한 유저" -exploreUsersCount: "{count}명의 유저가 있습니다" -exploreFediverse: "연합우주를 탐색" popularTags: "인기 태그" userList: "리스트" -about: "정보" aboutMisskey: "FoundKey에 대하여" administrator: "관리자" token: "토큰" @@ -391,7 +348,6 @@ share: "공유" notFound: "찾을 수 없습니다" notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다." uploadFolder: "기본 업로드 위치" -cacheClear: "캐시 지우기" markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시" markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시" markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시" @@ -422,7 +378,6 @@ noMessagesYet: "아직 대화가 없습니다" newMessageExists: "새 메시지가 있습니다" onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다" signinRequired: "로그인 해주세요" -invitations: "초대" invitationCode: "초대 코드" checking: "확인하는 중입니다" available: "사용 가능합니다" @@ -441,7 +396,6 @@ or: "혹은" language: "언어" uiLanguage: "UI 표시 언어" groupInvited: "그룹에 초대되었습니다" -aboutX: "{x}에 대하여" useOsNativeEmojis: "OS 기본 이모지를 사용" disableDrawer: "드로어 메뉴를 사용하지 않기" youHaveNoGroups: "그룹이 없습니다" @@ -449,47 +403,42 @@ joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을 noHistory: "기록이 없습니다" signinHistory: "로그인 기록" disableAnimatedMfm: "움직임이 있는 MFM을 비활성화" -doing: "잠시만요" category: "카테고리" tags: "태그" -docSource: "이 문서의 소스" createAccount: "계정 만들기" existingAccount: "기존 계정" -regenerate: "재생성" fontSize: "글자 크기" noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다" openImageInNewTab: "새 탭에서 이미지 열기" dashboard: "대시보드" local: "로컬" remote: "리모트" -total: "합계" -weekOverWeekChanges: "지난주보다" dayOverDayChanges: "어제보다" appearance: "모양" clientSettings: "클라이언트 설정" -accountSettings: "계정 설정" -numberOfDays: "며칠동안" -hideThisNote: "이 노트를 숨기기" showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시" objectStorage: "오브젝트 스토리지" useObjectStorage: "오브젝트 스토리지를 사용" objectStorageBaseUrl: "Base URL" -objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어, AWS S3의 경우 'https://.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/' 와 같이 지정합니다." +objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는\ + \ 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어,\ + \ AWS S3의 경우 'https://.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/'\ + \ 와 같이 지정합니다." objectStorageBucket: "Bucket" objectStorageBucketDesc: "사용 서비스의 bucket명을 지정해주세요." objectStoragePrefix: "Prefix" objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장됩니다." objectStorageEndpoint: "Endpoint" -objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요. '' 혹은 ':' 와 같이 지정합니다." +objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요.\ + \ '' 혹은 ':' 와 같이 지정합니다." objectStorageRegion: "Region" -objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는 경우, 비워 두거나 'us-east-1'으로 설정해 주세요." +objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는\ + \ 경우, 비워 두거나 'us-east-1'으로 설정해 주세요." objectStorageUseSSL: "SSL 사용" objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요" objectStorageUseProxy: "연결에 프록시를 사용" objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요" objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기" -serverLogs: "서버 로그" -deleteAll: "모두 삭제" showFixedPostForm: "타임라인 상단에 글 작성란을 표시" newNoteRecived: "새 노트가 있습니다" sounds: "소리" @@ -500,7 +449,6 @@ popout: "새 창으로 열기" volume: "음량" masterVolume: "마스터 볼륨" details: "자세히" -chooseEmoji: "이모지 선택" unableToProcess: "작업을 완료할 수 없습니다" recentUsed: "최근 사용" install: "설치" @@ -514,28 +462,27 @@ sort: "정렬" ascendingOrder: "오름차순" descendingOrder: "내림차순" scratchpad: "스크래치 패드" -scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. FoundKey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다." +scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. FoundKey 와 상호 작용하는 코드를\ + \ 작성, 실행 및 결과를 확인할 수 있습니다." output: "출력" -script: "스크립트" updateRemoteUser: "리모트 유저 정보 갱신" deleteAllFiles: "모든 파일 삭제" deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?" removeAllFollowing: "모든 팔로잉 해제" -removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요." +removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게\ + \ 된 경우 등에 실행해 주세요." userSuspended: "이 계정은 정지된 상태입니다." userSilenced: "이 계정은 사일런스된 상태입니다." yourAccountSuspendedTitle: "계정이 정지되었습니다" -yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오." +yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한\ + \ 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오." menu: "메뉴" divider: "구분선" addItem: "항목 추가" relays: "릴레이" addRelay: "릴레이 추가" inboxUrl: "Inbox 주소" -addedRelays: "추가된 릴레이" -serviceworkerInfo: "푸시 알림을 수행하려면 활성화해야 합니다." deletedNote: "삭제된 노트" -invisibleNote: "비공개 노트" enableInfiniteScroll: "자동으로 좀 더 보기" visibility: "공개 범위" poll: "투표" @@ -545,15 +492,12 @@ disablePlayer: "플레이어 닫기" themeEditor: "테마 에디터" description: "설명" describeFile: "캡션 추가" -enterFileDescription: "캡션 입력" author: "작성자" leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?" manage: "관리" plugins: "플러그인" deck: "덱" -undeck: "덱 해제" useBlurEffectForModal: "모달에 흐림 효과 사용" -useFullReactionPicker: "모든 기능이 포함된 리액션 선택기 사용" width: "폭" height: "높이" large: "크게" @@ -565,7 +509,6 @@ enableAll: "전체 선택" disableAll: "전체 해제" tokenRequested: "계정 접근 허용" pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다." -notificationType: "알림 유형" edit: "편집" useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용" emailServer: "메일 서버" @@ -590,10 +533,7 @@ userSaysSomething: "{name}님이 무언가를 말했습니다" makeActive: "활성화" display: "표시" copy: "복사" -metrics: "통계" overview: "요약" -logs: "로그" -delayed: "지연" database: "데이터베이스" channel: "채널" create: "생성" @@ -603,11 +543,11 @@ useGlobalSetting: "글로벌 설정을 사용하기" useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다." other: "기타" regenerateLoginToken: "로그인 토큰을 재생성" -regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다." +regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다.\ + \ 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다." setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다." fileIdOrUrl: "파일 ID 또는 URL" behavior: "동작" -sample: "예시" abuseReports: "신고" reportAbuse: "신고" reportAbuseOf: "{name}을 신고하기" @@ -621,12 +561,8 @@ forwardReportIsAnonymous: "리모트 인스턴스에서는 나의 정보를 볼 send: "전송" abuseMarkAsResolved: "해결됨으로 표시" openInNewTab: "새 탭에서 열기" -openInSideView: "사이드뷰로 열기" defaultNavigationBehaviour: "기본 탐색 동작" -editTheseSettingsMayBreakAccount: "이 설정을 변경하면 계정이 손상될 수 있습니다." instanceTicker: "노트의 인스턴스 정보" -waitingFor: "{x}을(를) 기다리고 있습니다" -random: "랜덤" system: "시스템" switchUi: "UI 전환" desktop: "데스크탑" @@ -660,16 +596,12 @@ alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정" loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시" disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음" verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요." -notSet: "설정되지 않음" emailVerified: "메일 주소가 확인되었습니다." -noteFavoritesCount: "즐겨찾기한 노트 수" pageLikesCount: "좋아요 한 Page 수" pageLikedCount: "Page에 받은 좋아요 수" contact: "연락처" useSystemFont: "시스템 기본 글꼴을 사용" clips: "클립" -experimentalFeatures: "실험실" -developer: "개발자" makeExplorable: "\"발견하기\"에 내 계정 보이기" makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다." showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시" @@ -680,28 +612,16 @@ wide: "넓게" narrow: "좁게" reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?" needReloadToApply: "변경 사항은 새로고침하면 적용됩니다." -showTitlebar: "타이틀 바를 표시하기" clearCache: "캐시 비우기" onlineUsersCount: "{n}명이 접속 중" -nUsers: "{n} 유저" -nNotes: "{n} 노트" -myTheme: "내 테마" backgroundColor: "배경 색" accentColor: "강조 색상" textColor: "문자 색" saveAs: "다른 이름으로 저장" -advanced: "고급" -value: "값" createdAt: "생성된 날짜" updatedAt: "수정한 날짜" -saveConfirm: "저장하시겠습니까?" deleteConfirm: "삭제하시겠습니까?" -invalidValue: "올바른 값이 아닙니다." -registry: "레지스트리" closeAccount: "계정 폐쇄" -currentVersion: "현재 버전" -latestVersion: "최신 버전" -youAreRunningUpToDateClient: "사용 중인 클라이언트는 최신입니다." newVersionOfClientAvailable: "새로운 버전의 클라이언트를 이용할 수 있습니다." usageAmount: "사용량" capacity: "용량" @@ -710,12 +630,9 @@ editCode: "코드 수정" apply: "적용" receiveAnnouncementFromInstance: "이 인스턴스의 알림을 이메일로 수신할게요" emailNotification: "메일 알림" -publish: "게시" -inChannelSearch: "채널에서 검색" useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기" typingUsers: "{users} 님이 입력하고 있어요.." jumpToSpecifiedDate: "특정 날짜로 이동" -showingPastTimeline: "과거의 타임라인을 표시하고 있어요" clear: "지우기" markAllAsRead: "모두 읽은 상태로 표시" goBack: "뒤로" @@ -728,7 +645,6 @@ notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어 info: "정보" userInfo: "유저 정보" unknown: "알 수 없음" -onlineStatus: "온라인 상태" hideOnlineStatus: "온라인 상태 숨기기" hideOnlineStatusDescription: "온라인 상태를 숨기면, 검색과 같은 일부 기능에 영향을 미칠 수 있습니다." online: "온라인" @@ -749,26 +665,15 @@ switch: "전환" noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습니다." noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다." configure: "설정하기" -postToGallery: "갤러리에 업로드" -gallery: "갤러리" recentPosts: "최근 포스트" -popularPosts: "인기 포스트" shareWithNote: "노트로 공유" -expiration: "기한" -memo: "메모" -priority: "우선순위" -high: "높음" -middle: "보통" -low: "낮음" emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다." ratio: "비율" previewNoteText: "본문 미리보기" customCss: "CSS 사용자화" -customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다." -global: "글로벌" +customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수\ + \ 있습니다." squareAvatars: "프로필 아이콘을 사각형으로 표시" -sent: "전송" -received: "수신" searchResult: "검색 결과" hashtags: "해시태그" troubleshooting: "문제 해결" @@ -779,7 +684,8 @@ whatIsNew: "패치 정보 보기" translate: "번역" translatedFrom: "{x}에서 번역" accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다" -usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다." +usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다.\ + \ 사용자명은 나중에 변경할 수 없습니다." keepCw: "CW 유지하기" pubSub: "Pub/Sub 계정" lastCommunication: "마지막 통신" @@ -843,7 +749,8 @@ _ffVisibility: _signup: almostThere: "거의 다 끝났습니다" emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다." - emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요." + emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해\ + \ 주세요." _accountDelete: accountDelete: "계정 삭제" mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다." @@ -851,18 +758,10 @@ _accountDelete: requestAccountDelete: "계정 삭제 요청" started: "삭제 작업이 시작되었습니다." inProgress: "삭제 진행 중" -_ad: - back: "뒤로" - reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" _forgotPassword: enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다." ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오." contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오." -_gallery: - my: "내 갤러리" - liked: "좋아요 한 갤러리" - like: "좋아요!" - unlike: "좋아요 취소" _email: _follow: title: "새로운 팔로워가 있습니다" @@ -871,7 +770,6 @@ _email: _plugin: install: "플러그인 설치" installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다." - manage: "플러그인 관리" _registry: scope: "범위" key: "키" @@ -880,17 +778,16 @@ _registry: createKey: "키 생성" _aboutMisskey: about: "FoundKey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다." - contributors: "주요 기여자" allContributors: "모든 기여자" source: "소스 코드" - translation: "FoundKey를 번역하기" _nsfw: respect: "열람주의로 설정된 미디어 숨기기" ignore: "열람 주의 미디어 항상 표시" force: "미디어 항상 숨기기" _mfm: cheatSheet: "MFM 도움말" - intro: "MFM는 FoundKey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할 수 있습니다." + intro: "MFM는 FoundKey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할\ + \ 수 있습니다." dummy: "FoundKey로 연합우주의 세계가 펼쳐집니다" mention: "멘션" mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다." @@ -1001,68 +898,6 @@ _theme: alreadyInstalled: "이미 설치된 테마입니다" invalid: "테마 형식이 올바르지 않습니다" make: "테마 만들기" - base: "베이스" - addConstant: "상수 추가" - constant: "상수" - defaultValue: "기본값" - color: "색" - refProp: "프로퍼티를 참조" - refConst: "상수를 참조" - key: "키" - func: "함수" - funcKind: "함수 종류" - argument: "매개변수" - basedProp: "기준으로 할 속성 이름" - alpha: "불투명도" - darken: "어두움" - lighten: "밝음" - inputConstantName: "상수 이름을 입력하세요" - importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다." - deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?" - keys: - accent: "강조 색상" - bg: "배경" - fg: "텍스트" - focus: "포커스" - indicator: "인디케이터" - panel: "패널" - shadow: "그림자" - header: "헤더" - navBg: "사이드바 배경" - navFg: "사이드바 텍스트" - navHoverFg: "사이드바 텍스트 (호버)" - navActive: "사이드바 텍스트 (활성)" - navIndicator: "사이드바 인디케이터" - link: "링크" - hashtag: "해시태그" - mention: "멘션" - mentionMe: "나에게 보낸 멘션" - renote: "Renote" - modalBg: "모달 배경" - divider: "구분선" - scrollbarHandle: "스크롤바 핸들" - scrollbarHandleHover: "스크롤바 핸들 (호버)" - dateLabelFg: "날짜 레이블 텍스트" - infoBg: "정보창 배경" - infoFg: "정보창 텍스트" - infoWarnBg: "경고창 배경" - infoWarnFg: "경고창 텍스트" - cwBg: "CW 버튼 배경" - cwFg: "CW 버튼 텍스트" - cwHoverBg: "CW 버튼 배경 (호버)" - toastBg: "알림창 배경" - toastFg: "알림창 텍스트" - buttonBg: "버튼 배경" - buttonHoverBg: "버튼 배경 (호버)" - inputBorder: "입력 필드 테두리" - listItemHoverBg: "리스트 항목 배경 (호버)" - driveFolderBg: "드라이브 폴더 배경" - wallpaperOverlay: "배경화면 오버레이" - badge: "배지" - messageBg: "채팅 배경" - accentDarken: "강조 색상 (어두움)" - accentLighten: "강조 색상 (밝음)" - fgHighlighted: "강조된 텍스트" _sfx: note: "새 노트" noteMy: "내 노트" @@ -1100,7 +935,8 @@ _tutorial: step4_1: "노트 작성을 끝내셨나요?" step4_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다." step5_1: "이제, 다른 사람을 팔로우하여 타임라인을 활기차게 만들어보도록 합시다." - step5_2: "{featured}에서 이 인스턴스의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요. 마음에 드는 사람을 골라 팔로우해 보세요!" + step5_2: "{featured}에서 이 인스턴스의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요.\ + \ 마음에 드는 사람을 골라 팔로우해 보세요!" step5_3: "다른 유저를 팔로우하려면 해당 유저의 아이콘을 클릭하여 프로필 페이지를 띄운 후, 팔로우 버튼을 눌러 주세요." step5_4: "사용자에 따라 팔로우가 승인될 때까지 시간이 걸릴 수 있습니다." step6_1: "타임라인에 다른 사용자의 노트가 나타난다면 성공입니다." @@ -1108,7 +944,7 @@ _tutorial: step6_3: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다." step7_1: "이것으로 FoundKey의 기본 튜토리얼을 마치겠습니다. 수고하셨습니다!" step7_2: "FoundKey에 대해 더 알고 싶으시다면 {help}를 참고해 주세요." - step7_3: "그럼 FoundKey를 즐기세요! 🚀" + step7_3: "그럼 FoundKey를 즐기세요! \U0001F680" _2fa: alreadyRegistered: "이미 설정이 완료되었습니다." registerDevice: "디바이스 등록" @@ -1118,7 +954,8 @@ _2fa: step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:" step3: "앱에 표시된 토큰을 입력하시면 완료됩니다." step4: "다음 로그인부터는 토큰을 입력해야 합니다." - securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다." + securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할\ + \ 수 있습니다." _permissions: "read:account": "계정의 정보를 봅니다" "write:account": "계정의 정보를 변경합니다" @@ -1148,10 +985,6 @@ _permissions: "write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다" "read:channels": "채널을 보기" "write:channels": "채널을 추가하거나 삭제합니다" - "read:gallery": "갤러리를 봅니다" - "write:gallery": "갤러리를 추가하거나 삭제합니다" - "read:gallery-likes": "갤러리의 좋아요를 확인합니다" - "write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다" _auth: shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?" shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?" @@ -1328,7 +1161,6 @@ _relayStatus: accepted: "승인됨" rejected: "거절됨" _notification: - fileUploaded: "파일이 업로드되었습니다" youGotMention: "{name}님이 멘션함" youGotReply: "{name}님이 답글함" youGotQuote: "{name}님이 인용함" @@ -1343,7 +1175,6 @@ _notification: pollEnded: "투표 결과가 발표되었습니다" emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다" _types: - all: "전부" follow: "팔로잉" mention: "멘션" reply: "답글" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 83d71406c..a56ad8947 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -32,9 +32,6 @@ signup: "Registreren" save: "Opslaan" users: "Gebruikers" addUser: "Toevoegen gebruiker" -favorite: "Favorieten" -favorites: "Toevoegen aan favorieten" -unfavorite: "Verwijderen uit favorieten" pin: "Vastmaken aan profielpagina" unpin: "Losmaken van profielpagina" copyContent: "Kopiëren inhoud" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 50af17e43..4b5d1eae9 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -33,9 +33,6 @@ signup: "Zarejestruj się" save: "Zapisz" users: "Użytkownicy" addUser: "Dodaj użytkownika" -favorite: "Dodaj do ulubionych" -favorites: "Ulubione" -unfavorite: "Usuń z ulubionych" pin: "Przypnij do profilu" unpin: "Odepnij z profilu" copyContent: "Skopiuj zawartość" @@ -604,7 +601,6 @@ disableShowingAnimatedImages: "Nie odtwarzaj animowanych obrazów" verificationEmailSent: "Wiadomość weryfikacyjna została wysłana. Odwiedź uwzględniony\ \ odnośnik, aby ukończyć weryfikację." emailVerified: "Adres e-mail został potwierdzony" -noteFavoritesCount: "Liczba polubionych wpisów" pageLikesCount: "Liczba otrzymanych polubień stron" pageLikedCount: "Liczba polubionych stron" contact: "Kontakt" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 604e74b9f..4940370b5 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -32,9 +32,6 @@ signup: "Registrar-se" save: "Guardar" users: "Usuários" addUser: "Adicionar usuário" -favorite: "Favoritar" -favorites: "Favoritar" -unfavorite: "Remover dos favoritos" pin: "Afixar no perfil" unpin: "Desafixar do perfil" copyContent: "Copiar conteúdos" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 9ac0445f0..0b5a6d9de 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -32,9 +32,6 @@ signup: "Înregistrează-te" save: "Salvează" users: "Utilizatori" addUser: "Adăugă utilizator" -favorite: "Adaugă la favorite" -favorites: "Favorite" -unfavorite: "Elimină din favorite" pin: "Fixează pe profil" unpin: "Anulati fixare" copyContent: "Copiază conținutul" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index dd20877dd..a9c16f7cd 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -32,9 +32,6 @@ signup: "Регистрация" save: "Сохранить" users: "Пользователи" addUser: "Добавить пользователя" -favorite: "В избранное" -favorites: "Избранное" -unfavorite: "Убрать из избранного" pin: "Закрепить в профиле" unpin: "Открепить от профиля" copyContent: "Скопировать содержимое" @@ -631,7 +628,6 @@ disableShowingAnimatedImages: "Не проигрывать анимацию" verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста,\ \ по ссылке из письма, чтобы завершить проверку." emailVerified: "Адрес электронной почты подтверждён." -noteFavoritesCount: "Количество добавленного в избранное" pageLikesCount: "Количество понравившихся страниц" pageLikedCount: "Количество страниц, понравившихся другим" contact: "Как связаться" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index b5f4d0059..729927812 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -32,9 +32,6 @@ signup: "Registrovať" save: "Uložiť" users: "Používatelia" addUser: "Pridať používateľa" -favorite: "Páči sa mi" -favorites: "Obľúbené" -unfavorite: "Nepáči sa mi" pin: "Pripnúť" unpin: "Odopnúť" copyContent: "Kopírovať obsah" @@ -623,7 +620,6 @@ disableShowingAnimatedImages: "Neprehrávať animované obrázky" verificationEmailSent: "Odoslali sme overovací email. Overenie dokončíte kliknutím\ \ na odkaz v emaili." emailVerified: "Email overený" -noteFavoritesCount: "Počet obľúbených poznámok" pageLikesCount: "Počet obľúbených stránok" pageLikedCount: "Počet prijatých \"páči sa mi\"" contact: "Kontakt" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 2965e968a..90bfa58ef 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -32,9 +32,6 @@ signup: "Registrera" save: "Spara" users: "Användare" addUser: "Lägg till användare" -favorite: "Lägg till i favoriter" -favorites: "Favoriter" -unfavorite: "Avfavorisera" pin: "Fäst till profil" unpin: "Lossa från profil" copyContent: "Kopiera innehåll" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 12bd05821..0d1df8c5d 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -28,9 +28,6 @@ logout: "Çıkış Yap" signup: "Kayıt Ol" users: "Kullanıcı" addUser: "Kullanıcı Ekle" -favorite: "Favoriler" -favorites: "Favoriler" -unfavorite: "Favorilerden Kaldır" pin: "Sabitlenmiş" unpin: "Sabitlemeyi kaldır" copyContent: "İçeriği kopyala" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index b53de0c81..17b338818 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -32,9 +32,6 @@ signup: "Реєстрація" save: "Зберегти" users: "Користувачі" addUser: "Додати користувача" -favorite: "Обране" -favorites: "Обране" -unfavorite: "Видалити з обраного" pin: "Закріпити" unpin: "Відкріпити" copyContent: "Скопіювати контент" @@ -631,7 +628,6 @@ disableShowingAnimatedImages: "Не програвати анімовані зо verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть\ \ по посиланню в листі для підтвердження." emailVerified: "Електронну пошту підтверджено." -noteFavoritesCount: "Кількість улюблених нотаток" pageLikesCount: "Кількість отриманих вподобань сторінки" pageLikedCount: "Кількість вподобаних сторінок" contact: "Контакт" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 2a9bcab74..cc21255ad 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -32,9 +32,6 @@ signup: "Đăng ký" save: "Lưu" users: "Người dùng" addUser: "Thêm người dùng" -favorite: "Thêm vào yêu thích" -favorites: "Lượt thích" -unfavorite: "Bỏ thích" pin: "Ghim" unpin: "Bỏ ghim" copyContent: "Chép nội dung" @@ -628,7 +625,6 @@ disableShowingAnimatedImages: "Không phát ảnh động" verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết\ \ đính kèm để hoàn tất xác minh." emailVerified: "Email đã được xác minh" -noteFavoritesCount: "Số lượng tút yêu thích" pageLikesCount: "Số lượng trang đã thích" pageLikedCount: "Số lượng thích trang đã nhận" contact: "Liên hệ" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index df3c768af..acb6dc301 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -30,9 +30,6 @@ signup: "新用户注册" save: "保存" users: "用户" addUser: "添加用户" -favorite: "收藏" -favorites: "收藏" -unfavorite: "取消收藏" pin: "置顶" unpin: "取消置顶" copyContent: "复制内容" @@ -583,7 +580,6 @@ loadRawImages: "添加附件图像的缩略图时使用原始图像质量" disableShowingAnimatedImages: "不播放动画" verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。" emailVerified: "电子邮件地址已验证" -noteFavoritesCount: "收藏的帖子数" pageLikesCount: "页面点赞次数" pageLikedCount: "页面被点赞次数" contact: "联系人" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 706d4a777..0d3ae4629 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -30,9 +30,6 @@ signup: "註冊" save: "儲存" users: "使用者" addUser: "新增使用者" -favorite: "我的最愛" -favorites: "我的最愛" -unfavorite: "從我的最愛中移除" pin: "置頂" unpin: "取消置頂" copyContent: "複製內容" @@ -582,7 +579,6 @@ loadRawImages: "以原始圖檔顯示附件圖檔的縮圖" disableShowingAnimatedImages: "不播放動態圖檔" verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。" emailVerified: "已成功驗證您的電郵" -noteFavoritesCount: "我的最愛貼文的數目" pageLikesCount: "頁面被按讚次數" pageLikedCount: "頁面被按讚次數" contact: "聯絡人" From 4dcca239b1d50772604a12a0a7f366a0b0416e17 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 22:59:03 +0200 Subject: [PATCH 57/70] make container of new notes button click-through --- packages/client/src/pages/timeline.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 1ea7b652a..cdc99af4d 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -159,12 +159,14 @@ definePageMetadata(computed(() => ({ top: calc(var(--stickyTop, 0px) + 16px); z-index: 1000; width: 100%; + pointer-events: none; > button { display: block; margin: var(--margin) auto 0 auto; padding: 8px 16px; border-radius: 32px; + pointer-events: initial; } } From cc342f1443cf84444787217c65a3e8f590f71ecb Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 28 May 2023 23:23:40 +0200 Subject: [PATCH 58/70] refactor createNote, translate comments Use the already existing functions isPost and toArray instead of reimplementing them. --- .../src/remote/activitypub/models/note.ts | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 3607ad62b..acda4566e 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -19,7 +19,7 @@ import { fromHtml } from '@/mfm/from-html.js'; import { shouldBlockInstance } from '@/misc/should-block-instance.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { parseAudience } from '../audience.js'; -import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; +import { IObject, getOneApId, getApId, getOneApHrefNullable, isPost, IPost, isEmoji, getApType } from '../type.js'; import { DbResolver } from '../db-resolver.js'; import { apLogger } from '../logger.js'; import { resolvePerson } from './person.js'; @@ -33,7 +33,7 @@ export function validateNote(object: IObject): Error | null { return new Error('invalid Note: object is null'); } - if (!validPost.includes(getApType(object))) { + if (!isPost(object)) { return new Error(`invalid Note: invalid object type ${getApType(object)}`); } @@ -96,7 +96,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si let visibility = noteAudience.visibility; const visibleUsers = noteAudience.visibleUsers; - // Audience (to, cc) が指定されてなかった場合 + // If audience(to,cc) was not specified if (visibility === 'specified' && visibleUsers.length === 0) { if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している // こちらから匿名GET出来たものならばpublic @@ -109,20 +109,19 @@ export async function createNote(value: string | IObject, resolver: Resolver, si const apMentions = await extractApMentions(note.tag, resolver); const apHashtags = await extractApHashtags(note.tag); - // 添付ファイル - // TODO: attachmentは必ずしもImageではない - // TODO: attachmentは必ずしも配列ではない - // Noteがsensitiveなら添付もsensitiveにする + // Attachments handling + // TODO: attachments are not necessarily images + // If the note is marked as sensitive, the images should be marked sensitive too. const limit = promiseLimit(2); - note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; + note.attachment = toArray(note.attachment); const files = note.attachment .map(attach => attach.sensitive = note.sensitive) ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x, resolver)) as Promise))) .filter(image => image != null) : []; - // リプライ + // Reply handling const reply: Note | null = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver).then(x => { if (x == null) { @@ -132,7 +131,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si return x; } }).catch(async e => { - // トークだったらinReplyToのエラーは無視 + // ignore inReplyTo if it is a messaging message const uri = getApId(note.inReplyTo); if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); @@ -218,7 +217,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si apLogger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); await vote(actor, reply, index); - // リモートフォロワーにUpdate配信 + // Federate an Update to other servers deliverQuestionUpdate(reply.id); } return null; @@ -243,26 +242,26 @@ export async function createNote(value: string | IObject, resolver: Resolver, si await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id); return null; } + } else { + return await post(actor, { + createdAt: note.published ? new Date(note.published) : null, + files, + reply, + renote: quote, + name: note.name, + cw, + text, + localOnly: false, + visibility, + visibleUsers, + apMentions, + apHashtags, + apEmojis, + poll, + uri: note.id, + url: getOneApHrefNullable(note.url), + }, silent); } - - return await post(actor, { - createdAt: note.published ? new Date(note.published) : null, - files, - reply, - renote: quote, - name: note.name, - cw, - text, - localOnly: false, - visibility, - visibleUsers, - apMentions, - apHashtags, - apEmojis, - poll, - uri: note.id, - url: getOneApHrefNullable(note.url), - }, silent); } /** From c9df2fd060f5f35e065cb7895d6ef968f2defe1c Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 28 May 2023 23:24:39 +0200 Subject: [PATCH 59/70] validate that note attributed to is not local --- packages/backend/src/remote/activitypub/models/note.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index acda4566e..e630eb715 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -52,6 +52,9 @@ export function validateNote(object: IObject): Error | null { if (attributedToHost !== expectHost) { return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${attributedToHost}`); } + if (attributedToHost === config.hostname) { + return new Error('invalid Note: by local author'); + } return null; } From bda8488194fb27fc3f8151324a108ffd35e1c217 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 28 May 2023 23:25:19 +0200 Subject: [PATCH 60/70] fix messaging messages with multiple recipients instead of returning early, this should only return after all messaging messages have been created --- packages/backend/src/remote/activitypub/models/note.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index e630eb715..e12d99b63 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -243,8 +243,8 @@ export async function createNote(value: string | IObject, resolver: Resolver, si if (isTalk) { for (const recipient of visibleUsers) { await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id); - return null; } + return null; } else { return await post(actor, { createdAt: note.published ? new Date(note.published) : null, From 2696c34f6c12b78169a9e75f52cc503affcc2cfb Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 28 May 2023 23:26:59 +0200 Subject: [PATCH 61/70] fix some audience parsing assumptions The assumptions made in the comment is actually wrong. The comment says: "If value is a string, this means it must have passed through the resolver, which means it must be public." But this is not true because we do signed GET requests which means we may well get non-public posts using the resolver. --- packages/backend/src/remote/activitypub/models/note.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index e12d99b63..e1d256ae9 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -101,10 +101,8 @@ export async function createNote(value: string | IObject, resolver: Resolver, si // If audience(to,cc) was not specified if (visibility === 'specified' && visibleUsers.length === 0) { - if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している - // こちらから匿名GET出来たものならばpublic - visibility = 'public'; - } + // TODO derive audience from context (e.g. whose inbox this was in?) + throw new Error('audience not understood'); } let isTalk = note._misskey_talk && visibility === 'specified'; From 89fad52272b1caecc171e0bb979fdde213e369f6 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 28 May 2023 23:31:06 +0200 Subject: [PATCH 62/70] remove redundant check Whether the actor is suspended is already checked in the performOneActivity function and does not need to be checked here again since the actor is still the same as it was there. --- .../backend/src/remote/activitypub/kernel/announce/note.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 5c6196588..e0861024a 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -14,10 +14,6 @@ import { shouldBlockInstance } from '@/misc/should-block-instance.js'; export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); - if (actor.isSuspended) { - return; - } - // Cancel if the announced from host is blocked. if (await shouldBlockInstance(extractDbHost(uri))) return; From 3ae8049d81d6018f93de15d76d76ff84abfd02a4 Mon Sep 17 00:00:00 2001 From: Jeder Date: Tue, 30 Aug 2022 10:19:22 +0200 Subject: [PATCH 63/70] client: change followers only icon to closed lock Reviewed-on: https://akkoma.dev/FoundKeyGang/FoundKey/pulls/388 Changelog: Changed --- packages/client/src/components/post-form.vue | 2 +- packages/client/src/components/visibility-picker.vue | 2 +- packages/client/src/components/visibility.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 04b7d3800..bbe7d5451 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -21,7 +21,7 @@ diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue index 50a6ff667..1b0707578 100644 --- a/packages/client/src/components/visibility-picker.vue +++ b/packages/client/src/components/visibility-picker.vue @@ -16,7 +16,7 @@