diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index 694682e72..185dab673 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -18,62 +18,62 @@ const ev = new Xev(); * Init process */ export default async function() { - process.title = `Calckey (${cluster.isPrimary ? 'master' : 'worker'})`; - - if (cluster.isPrimary || envOption.disableClustering) { + process.title = `Calckey (${cluster.isPrimary ? 'master' : 'worker'})`; + + if (cluster.isPrimary || envOption.disableClustering) { await masterMain(); - + if (cluster.isPrimary) { - ev.mount(); + ev.mount(); } - } - - if (cluster.isWorker || envOption.disableClustering) { + } + + if (cluster.isWorker || envOption.disableClustering) { await workerMain(); - } - - // ユニットテスト時にMisskeyが子プロセスで起動された時のため - // それ以外のときは process.send は使えないので弾く - if (process.send) { + } + + // For when Calckey is started in a child process during unit testing. + // Otherwise, process.send cannot be used, so start it. + if (process.send) { process.send('ok'); - } + } } //#region Events // Listen new workers cluster.on('fork', worker => { - clusterLogger.debug(`Process forked: [${worker.id}]`); + clusterLogger.debug(`Process forked: [${worker.id}]`); }); // Listen online workers cluster.on('online', worker => { - clusterLogger.debug(`Process is now online: [${worker.id}]`); + clusterLogger.debug(`Process is now online: [${worker.id}]`); }); // Listen for dying workers cluster.on('exit', worker => { - // Replace the dead worker, - // we're not sentimental - clusterLogger.error(chalk.red(`[${worker.id}] died :(`)); - cluster.fork(); + // Replace the dead worker, + // we're not sentimental + clusterLogger.error(chalk.red(`[${worker.id}] died :(`)); + cluster.fork(); }); // Display detail of unhandled promise rejection if (!envOption.quiet) { - process.on('unhandledRejection', console.dir); + process.on('unhandledRejection', console.dir); } // Display detail of uncaught exception process.on('uncaughtException', err => { - try { + try { logger.error(err); - } catch { } + } catch { } }); // Dying away... process.on('exit', code => { - logger.info(`The process is going to exit with code ${code}`); + logger.info(`The process is going to exit with code ${code}`); }); //#endregion diff --git a/packages/backend/src/models/entities/notification.ts b/packages/backend/src/models/entities/notification.ts index db3dba363..2cf8a1939 100644 --- a/packages/backend/src/models/entities/notification.ts +++ b/packages/backend/src/models/entities/notification.ts @@ -19,7 +19,7 @@ export class Notification { public createdAt: Date; /** - * 通知の受信者 + * Notification Recipient ID */ @Index() @Column({ @@ -35,7 +35,7 @@ export class Notification { public notifiee: User | null; /** - * 通知の送信者(initiator) + * Notification sender (initiator) */ @Index() @Column({ @@ -52,19 +52,19 @@ export class Notification { public notifier: User | null; /** - * 通知の種類。 - * follow - フォローされた - * mention - 投稿で自分が言及された - * reply - (自分または自分がWatchしている)投稿が返信された - * renote - (自分または自分がWatchしている)投稿がRenoteされた - * quote - (自分または自分がWatchしている)投稿が引用Renoteされた + * Notification types: + * follow - Follow request + * mention - User was referenced in a post. + * reply - A post that a user made (or was watching) has been replied to. + * renote - A post that a user made (or was watching) has been renoted. + * quote - A post that a user made (or was watching) has been quoted and renoted. * reaction - (自分または自分がWatchしている)投稿にリアクションされた * pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した * receiveFollowRequest - フォローリクエストされた - * followRequestAccepted - 自分の送ったフォローリクエストが承認された + * followRequestAccepted - A follow request has been accepted. * groupInvited - グループに招待された - * app - アプリ通知 + * app - App notifications. */ @Index() @Column('enum', { @@ -74,12 +74,12 @@ export class Notification { public type: typeof notificationTypes[number]; /** - * 通知が読まれたかどうか + * Whether the notification was read. */ @Index() @Column('boolean', { default: false, - comment: 'Whether the Notification is read.', + comment: 'Whether the notification was read.', }) public isRead: boolean; @@ -130,7 +130,7 @@ export class Notification { public choice: number | null; /** - * アプリ通知のbody + * App notification body */ @Column('varchar', { length: 2048, nullable: true, @@ -138,8 +138,8 @@ export class Notification { public customBody: string | null; /** - * アプリ通知のheader - * (省略時はアプリ名で表示されることを期待) + * App notification header + * (If omitted, it is expected to be displayed with the app name) */ @Column('varchar', { length: 256, nullable: true, @@ -147,8 +147,8 @@ export class Notification { public customHeader: string | null; /** - * アプリ通知のicon(URL) - * (省略時はアプリアイコンで表示されることを期待) + * App notification icon (URL) + * (If omitted, it is expected to be displayed as an app icon) */ @Column('varchar', { length: 1024, nullable: true, @@ -156,7 +156,7 @@ export class Notification { public customIcon: string | null; /** - * アプリ通知のアプリ(のトークン) + * App notification app (token for) */ @Index() @Column({ diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 759cb4ae8..d06265941 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -14,7 +14,7 @@ import { Notes } from '@/models/index.js'; const logger = apLogger; /** - * アナウンスアクティビティを捌きます + * Handle announcement activities */ export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); @@ -23,25 +23,25 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac return; } - // アナウンス先をブロックしてたら中断 + // Interrupt if you block the announcement destination const meta = await fetchMeta(); if (meta.blockedHosts.includes(extractDbHost(uri))) return; const unlock = await getApLock(uri); try { - // 既に同じURIを持つものが登録されていないかチェック + // Check if something with the same URI is already registered const exist = await fetchNote(uri); if (exist) { return; } - // Announce対象をresolve + // Resolve Announce target let renote; try { renote = await resolveNote(targetUri); } catch (e) { - // 対象が4xxならスキップ + // Skip if target is 4xx if (e instanceof StatusError) { if (e.isClientError) { logger.warn(`Ignored announce target ${targetUri} - ${e.statusCode}`); diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts index f8dabe06e..d7ee2d8c7 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts @@ -7,7 +7,7 @@ import { extractDbHost } from '@/misc/convert-host.js'; import { StatusError } from '@/misc/fetch.js'; /** - * 投稿作成アクティビティを捌きます + * Handle post creation activity */ export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index c7064f553..01ff8f5db 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -5,45 +5,47 @@ import { toSingle } from '@/prelude/array.js'; import { deleteActor } from './actor.js'; /** - * 削除アクティビティを捌きます + * Handle delete activity */ export default async (actor: CacheableRemoteUser, activity: IDelete): Promise => { - if ('actor' in activity && actor.uri !== activity.actor) { + if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); - } - - // 削除対象objectのtype - let formerType: string | undefined; - + } + + // Type of object to be deleted + let formerType: string | undefined; + if (typeof activity.object === 'string') { - // typeが不明だけど、どうせ消えてるのでremote resolveしない - formerType = undefined; + // The type is unknown, but it has disappeared + // anyway, so it does not remote resolve + formerType = undefined; } else { - const object = activity.object as IObject; - if (isTombstone(object)) { + const object = activity.object as IObject; + if (isTombstone(object)) { formerType = toSingle(object.formerType); - } else { + } else { formerType = toSingle(object.type); - } + } } - - const uri = getApId(activity.object); - - // type不明でもactorとobjectが同じならばそれはPersonに違いない - if (!formerType && actor.uri === uri) { + + const uri = getApId(activity.object); + + // Even if type is unknown, if actor and object are the same, + // it must be `Person`. + if (!formerType && actor.uri === uri) { formerType = 'Person'; - } + } - // それでもなかったらおそらくNote - if (!formerType) { + // If not, fallback to `Note`. + if (!formerType) { formerType = 'Note'; - } + } - if (validPost.includes(formerType)) { + if (validPost.includes(formerType)) { return await deleteNote(actor, uri); - } else if (validActor.includes(formerType)) { + } else if (validActor.includes(formerType)) { return await deleteActor(actor, uri); - } else { + } else { return `Unknown type ${formerType}`; - } + } }; diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index 1f44c3556..df416b2ba 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -21,7 +21,7 @@ export default async function(actor: CacheableRemoteUser, uri: string): Promise< if (message == null) return 'message not found'; if (message.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; + return 'The user trying to delete the post is not the post author'; } await deleteMessage(message); @@ -30,7 +30,7 @@ export default async function(actor: CacheableRemoteUser, uri: string): Promise< } if (note.userId !== actor.id) { - return '投稿を削除しようとしているユーザーは投稿の作成者ではありません'; + return 'The user trying to delete the post is not the post author'; } await deleteNode(actor, note); diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index aa2f1f536..53e5daa51 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -6,8 +6,9 @@ import { In } from 'typeorm'; import { genId } from '@/misc/gen-id.js'; export default async (actor: CacheableRemoteUser, activity: IFlag): Promise => { - // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので - // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する + // The object is `(User | Note) | (User | Note) []`, but it cannot be + // matched with all patterns of the DB schema, so the target user is the first + // user and it is stored as a comment. 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 a9e92fa22..5cd1ec82b 100644 --- a/packages/backend/src/remote/activitypub/kernel/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/follow.ts @@ -12,7 +12,7 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { - // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある + // ※ `activity.actor` must be an existing local user, since `activity` is a follow request thrown from us. const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.actor); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts index a6e3929b0..39ef92363 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts @@ -23,5 +23,5 @@ export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index 102b7b134..1fa90ff50 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -11,10 +11,10 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; const logger = apLogger; /** - * Imageを作成します。 + * create an Image. */ export async function createImage(actor: CacheableRemoteUser, value: any): Promise { - // 投稿者が凍結されていたらスキップ + // Skip if author is frozen. if (actor.isSuspended) { throw new Error('actor has been suspended'); } @@ -39,8 +39,8 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi }); if (file.isLink) { - // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 - // URLを更新する + // If the URL is different, it means that the same image was previously + // registered with a different URL, so update the URL if (file.url !== image.url) { await DriveFiles.update({ id: file.id }, { url: image.url, @@ -55,14 +55,14 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi } /** - * Imageを解決します。 + * Resolve Image. * - * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + * If the target Image is registered in Calckey, return it, otherwise + * Fetch from remote server, register with Calckey and return it. */ export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise { // TODO - // リモートサーバーからフェッチしてきて登録 + // Fetch from remote server and register return await createImage(actor, value); } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index d5db2a2cf..64dc965ec 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -53,9 +53,9 @@ export function validateNote(object: any, uri: string) { } /** - * Noteをフェッチします。 + * Fetch Notes. * - * Misskeyに対象のNoteが登録されていればそれを返します。 + * If the target Note is registered in Calckey, it will be returned. */ export async function fetchNote(object: string | IObject): Promise { const dbResolver = new DbResolver(); @@ -63,7 +63,7 @@ export async function fetchNote(object: string | IObject): Promise } /** - * Noteを作成します。 + * Create a Note. */ export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = new Resolver(); @@ -89,10 +89,10 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s logger.info(`Creating the Note: ${note.id}`); - // 投稿者をフェッチ + // Fetch author const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; - // 投稿者が凍結されていたらスキップ + // Skip if author is suspended. if (actor.isSuspended) { throw new Error('actor has been suspended'); } @@ -101,10 +101,10 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s 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 + if (typeof value === 'string') { // If the input is a string, GET occurs in resolver + // Public if you can GET anonymously from here visibility = 'public'; } } @@ -114,7 +114,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const apMentions = await extractApMentions(note.tag); const apHashtags = await extractApHashtags(note.tag); - // 添付ファイル + // Attachments // TODO: attachmentは必ずしもImageではない // TODO: attachmentは必ずしも配列ではない // Noteがsensitiveなら添付もsensitiveにする @@ -127,7 +127,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s .filter(image => image != null) : []; - // リプライ + // Reply const reply: Note | null = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver).then(x => { if (x == null) { @@ -153,7 +153,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s }) : null; - // 引用 + // Quote let quote: Note | undefined | null; if (note._misskey_quote || note.quoteUrl) { @@ -196,7 +196,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const cw = note.summary === '' ? null : note.summary; - // テキストのパース + // Text parsing let text: string | null = null; if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') { text = note.source.content; @@ -265,23 +265,23 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s } /** - * Noteを解決します。 + * Resolve Note. * - * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ - * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 + * If the target Note is registered in Calckey, return it, otherwise + * Fetch from remote server, register with Calckey and return it. */ export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value === 'string' ? value : value.id; if (uri == null) throw new Error('missing uri'); - // ブロックしてたら中断 + // Abort if origin host is blocked const meta = await fetchMeta(); if (meta.blockedHosts.includes(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`); const unlock = await getApLock(uri); try { - //#region このサーバーに既に登録されていたらそれを返す + //#region Returns if already registered with this server const exist = await fetchNote(uri); if (exist) { @@ -293,9 +293,9 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); } - // リモートサーバーからフェッチしてきて登録 - // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが - // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 + // Fetch from remote server and register + // If the attached `Note` Object is specified here instead of the uri, the note will be generated without going through the server fetch. + // Since the attached Note Object may be disguised, always specify the uri and fetch it from the server. return await createNote(uri, resolver, true); } finally { unlock(); diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 21ef83af7..b23479be0 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -101,9 +101,9 @@ function validateActor(x: IObject, uri: string): IActor { } /** - * Personをフェッチします。 + * Fetch a Person. * - * Misskeyに対象のPersonが登録されていればそれを返します。 + * If the target Person is registered in Calckey, it will be returned. */ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -111,7 +111,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); @@ -210,7 +210,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise /users/:id のように入力がaliasなときにエラーになることがあるのを対応 + // /users/@a => /users/:id Corresponds to an error that may occur when the input is an alias like const u = await Users.findOneBy({ uri: person.id, }); @@ -235,10 +235,10 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index 94a50d4f7..b87d6ac1b 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -43,10 +43,10 @@ export async function extractPollFromQuestion(source: string | IObject, resolver export async function updateQuestion(value: any, resolver?: Resolver) { const uri = typeof value === 'string' ? value : value.id; - // URIがこのサーバーを指しているならスキップ + // Skip if URI points to this server if (uri.startsWith(config.url + '/')) throw new Error('uri points local'); - //#region このサーバーに既に登録されているか + //#region Already registered with this server? const note = await Notes.findOneBy({ uri }); if (note == null) throw new Error('Question is not registed'); diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index a3c10ba94..d79043aaf 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -6,7 +6,7 @@ import { updatePerson } from './models/person.js'; export default async (actor: CacheableRemoteUser, activity: IObject): Promise => { await performActivity(actor, activity); - // ついでにリモートユーザーの情報が古かったら更新しておく + // Update the remote user information if it is out of date if (actor.uri) { if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { setImmediate(() => { diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 72c24676b..ec6d2e6c9 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -58,7 +58,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ followerId: follower.id, }); - // 通知を作成 + // Create notification that request was accepted. createNotification(follower.id, 'followRequestAccepted', { notifierId: followee.id, }); diff --git a/packages/backend/src/services/following/requests/accept-all.ts b/packages/backend/src/services/following/requests/accept-all.ts index 5fbb549e0..31f3926c0 100644 --- a/packages/backend/src/services/following/requests/accept-all.ts +++ b/packages/backend/src/services/following/requests/accept-all.ts @@ -3,8 +3,8 @@ import { User } from '@/models/entities/user.js'; import { FollowRequests, Users } from '@/models/index.js'; /** - * 指定したユーザー宛てのフォローリクエストをすべて承認 - * @param user ユーザー + * Approve all follow requests for the specified user + * @param user User. */ export default async function(user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }) { const requests = await FollowRequests.findBy({ diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts index e96b06a35..83fc079fd 100644 --- a/packages/backend/src/services/suspend-user.ts +++ b/packages/backend/src/services/suspend-user.ts @@ -11,7 +11,7 @@ export async function doPostSuspend(user: { id: User['id']; host: User['host'] } publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); if (Users.isLocalUser(user)) { - // 知り得る全SharedInboxにDelete配信 + // Send Delete to all known SharedInboxes const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user)); const queue: string[] = [];