Merge pull request '[Chore] Partial translating of ActivityPub/Boot code + Formatting' (#9229) from prettykool/calckey:develop into develop

Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9229
This commit is contained in:
Cleo 2022-12-18 21:01:22 +00:00
commit b1933d00b9
21 changed files with 132 additions and 129 deletions

View File

@ -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

View File

@ -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({

View File

@ -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<void> {
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}`);

View File

@ -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<string> {
const uri = getApId(note);

View File

@ -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<string> => {
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}`;
}
}
};

View File

@ -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);

View File

@ -6,8 +6,9 @@ import { In } from 'typeorm';
import { genId } from '@/misc/gen-id.js';
export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => {
// 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()!);

View File

@ -12,7 +12,7 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<st
}
if (followee.host != null) {
return `skip: フォローしようとしているユーザーはローカルユーザーではありません`;
return `skip: user you are trying to follow is not a local user`;
}
await follow(actor, followee, activity.id);

View File

@ -6,7 +6,7 @@ import { relayRejected } from '@/services/relay.js';
import { Users } from '@/models/index.js';
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
// ※ 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);

View File

@ -23,5 +23,5 @@ export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<st
return `ok: unfollowed`;
}
return `skip: フォローされていない`;
return `skip: skip: not followed`;
};

View File

@ -13,7 +13,7 @@ export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<str
}
if (blockee.host != null) {
return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
return `skip: The user you are trying to unblock is not a local user`;
}
await unblock(await Users.findOneByOrFail({ id: actor.id }), blockee);

View File

@ -14,7 +14,7 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<st
}
if (followee.host != null) {
return `skip: フォロー解除しようとしているユーザーはローカルユーザーではありません`;
return `skip: The user you are trying to unfollow is not a local user`;
}
const req = await FollowRequests.findOneBy({
@ -37,5 +37,5 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<st
return `ok: unfollowed`;
}
return `skip: リクエストもフォローもされていない`;
return `skip: Not requested or followed`;
};

View File

@ -6,7 +6,7 @@ import Resolver from '../../resolver.js';
import { updatePerson } from '../../models/person.js';
/**
* Updateアクティビティを捌きます
* Handler for the Update activity
*/
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
if ('actor' in activity && actor.uri !== activity.actor) {

View File

@ -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<DriveFile> {
// 投稿者が凍結されていたらスキップ
// 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<DriveFile> {
// TODO
// リモートサーバーからフェッチしてきて登録
// Fetch from remote server and register
return await createImage(actor, value);
}

View File

@ -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<Note | null> {
const dbResolver = new DbResolver();
@ -63,7 +63,7 @@ export async function fetchNote(object: string | IObject): Promise<Note | null>
}
/**
* Noteを作成します
* Create a Note.
*/
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
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<Note | null> {
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();

View File

@ -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<CacheableUser | null> {
if (typeof uri !== 'string') throw new Error('uri is not string');
@ -111,7 +111,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Cac
const cached = uriPersonCache.get(uri);
if (cached) return cached;
// URIがこのサーバーを指しているならデータベースからフェッチ
// Fetch from the database if the URI points to this server
if (uri.startsWith(config.url + '/')) {
const id = uri.split('/').pop();
const u = await Users.findOneBy({ id });
@ -119,7 +119,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Cac
return u;
}
//#region このサーバーに既に登録されていたらそれを返す
//#region Returns if already registered with this server
const exist = await Users.findOneBy({ uri });
if (exist) {
@ -132,7 +132,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Cac
}
/**
* Personを作成します
* Create Person.
*/
export async function createPerson(uri: string, resolver?: Resolver): Promise<User> {
if (typeof uri !== 'string') throw new Error('uri is not string');
@ -210,7 +210,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
} catch (e) {
// duplicate key error
if (isDuplicateKeyValueError(e)) {
// /users/@a => /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<Us
usersChart.update(user!, true);
// ハッシュタグ更新
// Hashtag update
updateUsertags(user!, tags);
//#region アバターとヘッダー画像をフェッチ
//#region Fetch avatar and header image
const [avatar, banner] = await Promise.all([
person.icon,
person.image,
@ -260,7 +260,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
user!.bannerId = bannerId;
//#endregion
//#region カスタム絵文字取得
//#region Get custom emoji
const emojis = await extractEmojis(person.tag || [], host).catch(e => {
logger.info(`extractEmojis: ${e}`);
return [] as Emoji[];

View File

@ -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');

View File

@ -6,7 +6,7 @@ import { updatePerson } from './models/person.js';
export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
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(() => {

View File

@ -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,
});

View File

@ -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({

View File

@ -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[] = [];