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:
commit
b1933d00b9
|
@ -18,62 +18,62 @@ const ev = new Xev();
|
||||||
* Init process
|
* Init process
|
||||||
*/
|
*/
|
||||||
export default async function() {
|
export default async function() {
|
||||||
process.title = `Calckey (${cluster.isPrimary ? 'master' : 'worker'})`;
|
process.title = `Calckey (${cluster.isPrimary ? 'master' : 'worker'})`;
|
||||||
|
|
||||||
if (cluster.isPrimary || envOption.disableClustering) {
|
if (cluster.isPrimary || envOption.disableClustering) {
|
||||||
await masterMain();
|
await masterMain();
|
||||||
|
|
||||||
if (cluster.isPrimary) {
|
if (cluster.isPrimary) {
|
||||||
ev.mount();
|
ev.mount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cluster.isWorker || envOption.disableClustering) {
|
if (cluster.isWorker || envOption.disableClustering) {
|
||||||
await workerMain();
|
await workerMain();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ユニットテスト時にMisskeyが子プロセスで起動された時のため
|
// For when Calckey is started in a child process during unit testing.
|
||||||
// それ以外のときは process.send は使えないので弾く
|
// Otherwise, process.send cannot be used, so start it.
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send('ok');
|
process.send('ok');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Events
|
//#region Events
|
||||||
|
|
||||||
// Listen new workers
|
// Listen new workers
|
||||||
cluster.on('fork', worker => {
|
cluster.on('fork', worker => {
|
||||||
clusterLogger.debug(`Process forked: [${worker.id}]`);
|
clusterLogger.debug(`Process forked: [${worker.id}]`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen online workers
|
// Listen online workers
|
||||||
cluster.on('online', worker => {
|
cluster.on('online', worker => {
|
||||||
clusterLogger.debug(`Process is now online: [${worker.id}]`);
|
clusterLogger.debug(`Process is now online: [${worker.id}]`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for dying workers
|
// Listen for dying workers
|
||||||
cluster.on('exit', worker => {
|
cluster.on('exit', worker => {
|
||||||
// Replace the dead worker,
|
// Replace the dead worker,
|
||||||
// we're not sentimental
|
// we're not sentimental
|
||||||
clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
|
clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
|
||||||
cluster.fork();
|
cluster.fork();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display detail of unhandled promise rejection
|
// Display detail of unhandled promise rejection
|
||||||
if (!envOption.quiet) {
|
if (!envOption.quiet) {
|
||||||
process.on('unhandledRejection', console.dir);
|
process.on('unhandledRejection', console.dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display detail of uncaught exception
|
// Display detail of uncaught exception
|
||||||
process.on('uncaughtException', err => {
|
process.on('uncaughtException', err => {
|
||||||
try {
|
try {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
} catch { }
|
} catch { }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dying away...
|
// Dying away...
|
||||||
process.on('exit', code => {
|
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
|
//#endregion
|
||||||
|
|
|
@ -19,7 +19,7 @@ export class Notification {
|
||||||
public createdAt: Date;
|
public createdAt: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知の受信者
|
* Notification Recipient ID
|
||||||
*/
|
*/
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
|
@ -35,7 +35,7 @@ export class Notification {
|
||||||
public notifiee: User | null;
|
public notifiee: User | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知の送信者(initiator)
|
* Notification sender (initiator)
|
||||||
*/
|
*/
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
|
@ -52,19 +52,19 @@ export class Notification {
|
||||||
public notifier: User | null;
|
public notifier: User | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知の種類。
|
* Notification types:
|
||||||
* follow - フォローされた
|
* follow - Follow request
|
||||||
* mention - 投稿で自分が言及された
|
* mention - User was referenced in a post.
|
||||||
* reply - (自分または自分がWatchしている)投稿が返信された
|
* reply - A post that a user made (or was watching) has been replied to.
|
||||||
* renote - (自分または自分がWatchしている)投稿がRenoteされた
|
* renote - A post that a user made (or was watching) has been renoted.
|
||||||
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
|
* quote - A post that a user made (or was watching) has been quoted and renoted.
|
||||||
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
||||||
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
|
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
|
||||||
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
||||||
* receiveFollowRequest - フォローリクエストされた
|
* receiveFollowRequest - フォローリクエストされた
|
||||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
* followRequestAccepted - A follow request has been accepted.
|
||||||
* groupInvited - グループに招待された
|
* groupInvited - グループに招待された
|
||||||
* app - アプリ通知
|
* app - App notifications.
|
||||||
*/
|
*/
|
||||||
@Index()
|
@Index()
|
||||||
@Column('enum', {
|
@Column('enum', {
|
||||||
|
@ -74,12 +74,12 @@ export class Notification {
|
||||||
public type: typeof notificationTypes[number];
|
public type: typeof notificationTypes[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知が読まれたかどうか
|
* Whether the notification was read.
|
||||||
*/
|
*/
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
comment: 'Whether the Notification is read.',
|
comment: 'Whether the notification was read.',
|
||||||
})
|
})
|
||||||
public isRead: boolean;
|
public isRead: boolean;
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ export class Notification {
|
||||||
public choice: number | null;
|
public choice: number | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アプリ通知のbody
|
* App notification body
|
||||||
*/
|
*/
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 2048, nullable: true,
|
length: 2048, nullable: true,
|
||||||
|
@ -138,8 +138,8 @@ export class Notification {
|
||||||
public customBody: string | null;
|
public customBody: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アプリ通知のheader
|
* App notification header
|
||||||
* (省略時はアプリ名で表示されることを期待)
|
* (If omitted, it is expected to be displayed with the app name)
|
||||||
*/
|
*/
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 256, nullable: true,
|
||||||
|
@ -147,8 +147,8 @@ export class Notification {
|
||||||
public customHeader: string | null;
|
public customHeader: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アプリ通知のicon(URL)
|
* App notification icon (URL)
|
||||||
* (省略時はアプリアイコンで表示されることを期待)
|
* (If omitted, it is expected to be displayed as an app icon)
|
||||||
*/
|
*/
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024, nullable: true,
|
length: 1024, nullable: true,
|
||||||
|
@ -156,7 +156,7 @@ export class Notification {
|
||||||
public customIcon: string | null;
|
public customIcon: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アプリ通知のアプリ(のトークン)
|
* App notification app (token for)
|
||||||
*/
|
*/
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { Notes } from '@/models/index.js';
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アナウンスアクティビティを捌きます
|
* Handle announcement activities
|
||||||
*/
|
*/
|
||||||
export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
@ -23,25 +23,25 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// アナウンス先をブロックしてたら中断
|
// Interrupt if you block the announcement destination
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const unlock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 既に同じURIを持つものが登録されていないかチェック
|
// Check if something with the same URI is already registered
|
||||||
const exist = await fetchNote(uri);
|
const exist = await fetchNote(uri);
|
||||||
if (exist) {
|
if (exist) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Announce対象をresolve
|
// Resolve Announce target
|
||||||
let renote;
|
let renote;
|
||||||
try {
|
try {
|
||||||
renote = await resolveNote(targetUri);
|
renote = await resolveNote(targetUri);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 対象が4xxならスキップ
|
// Skip if target is 4xx
|
||||||
if (e instanceof StatusError) {
|
if (e instanceof StatusError) {
|
||||||
if (e.isClientError) {
|
if (e.isClientError) {
|
||||||
logger.warn(`Ignored announce target ${targetUri} - ${e.statusCode}`);
|
logger.warn(`Ignored announce target ${targetUri} - ${e.statusCode}`);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { extractDbHost } from '@/misc/convert-host.js';
|
||||||
import { StatusError } from '@/misc/fetch.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> {
|
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
|
||||||
const uri = getApId(note);
|
const uri = getApId(note);
|
||||||
|
|
|
@ -5,45 +5,47 @@ import { toSingle } from '@/prelude/array.js';
|
||||||
import { deleteActor } from './actor.js';
|
import { deleteActor } from './actor.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 削除アクティビティを捌きます
|
* Handle delete activity
|
||||||
*/
|
*/
|
||||||
export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<string> => {
|
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');
|
throw new Error('invalid actor');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 削除対象objectのtype
|
// Type of object to be deleted
|
||||||
let formerType: string | undefined;
|
let formerType: string | undefined;
|
||||||
|
|
||||||
if (typeof activity.object === 'string') {
|
if (typeof activity.object === 'string') {
|
||||||
// typeが不明だけど、どうせ消えてるのでremote resolveしない
|
// The type is unknown, but it has disappeared
|
||||||
formerType = undefined;
|
// anyway, so it does not remote resolve
|
||||||
|
formerType = undefined;
|
||||||
} else {
|
} else {
|
||||||
const object = activity.object as IObject;
|
const object = activity.object as IObject;
|
||||||
if (isTombstone(object)) {
|
if (isTombstone(object)) {
|
||||||
formerType = toSingle(object.formerType);
|
formerType = toSingle(object.formerType);
|
||||||
} else {
|
} else {
|
||||||
formerType = toSingle(object.type);
|
formerType = toSingle(object.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uri = getApId(activity.object);
|
const uri = getApId(activity.object);
|
||||||
|
|
||||||
// type不明でもactorとobjectが同じならばそれはPersonに違いない
|
// Even if type is unknown, if actor and object are the same,
|
||||||
if (!formerType && actor.uri === uri) {
|
// it must be `Person`.
|
||||||
|
if (!formerType && actor.uri === uri) {
|
||||||
formerType = 'Person';
|
formerType = 'Person';
|
||||||
}
|
}
|
||||||
|
|
||||||
// それでもなかったらおそらくNote
|
// If not, fallback to `Note`.
|
||||||
if (!formerType) {
|
if (!formerType) {
|
||||||
formerType = 'Note';
|
formerType = 'Note';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validPost.includes(formerType)) {
|
if (validPost.includes(formerType)) {
|
||||||
return await deleteNote(actor, uri);
|
return await deleteNote(actor, uri);
|
||||||
} else if (validActor.includes(formerType)) {
|
} else if (validActor.includes(formerType)) {
|
||||||
return await deleteActor(actor, uri);
|
return await deleteActor(actor, uri);
|
||||||
} else {
|
} else {
|
||||||
return `Unknown type ${formerType}`;
|
return `Unknown type ${formerType}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default async function(actor: CacheableRemoteUser, uri: string): Promise<
|
||||||
if (message == null) return 'message not found';
|
if (message == null) return 'message not found';
|
||||||
|
|
||||||
if (message.userId !== actor.id) {
|
if (message.userId !== actor.id) {
|
||||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
return 'The user trying to delete the post is not the post author';
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteMessage(message);
|
await deleteMessage(message);
|
||||||
|
@ -30,7 +30,7 @@ export default async function(actor: CacheableRemoteUser, uri: string): Promise<
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.userId !== actor.id) {
|
if (note.userId !== actor.id) {
|
||||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
return 'The user trying to delete the post is not the post author';
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteNode(actor, note);
|
await deleteNode(actor, note);
|
||||||
|
|
|
@ -6,8 +6,9 @@ import { In } from 'typeorm';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
|
|
||||||
export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => {
|
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 uris = getApIds(activity.object);
|
||||||
|
|
||||||
const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!);
|
const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!);
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<st
|
||||||
}
|
}
|
||||||
|
|
||||||
if (followee.host != null) {
|
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);
|
await follow(actor, followee, activity.id);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { relayRejected } from '@/services/relay.js';
|
||||||
import { Users } from '@/models/index.js';
|
import { Users } from '@/models/index.js';
|
||||||
|
|
||||||
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
|
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 dbResolver = new DbResolver();
|
||||||
const follower = await dbResolver.getUserFromApId(activity.actor);
|
const follower = await dbResolver.getUserFromApId(activity.actor);
|
||||||
|
|
|
@ -23,5 +23,5 @@ export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<st
|
||||||
return `ok: unfollowed`;
|
return `ok: unfollowed`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `skip: フォローされていない`;
|
return `skip: skip: not followed`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<str
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockee.host != null) {
|
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);
|
await unblock(await Users.findOneByOrFail({ id: actor.id }), blockee);
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<st
|
||||||
}
|
}
|
||||||
|
|
||||||
if (followee.host != null) {
|
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({
|
const req = await FollowRequests.findOneBy({
|
||||||
|
@ -37,5 +37,5 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<st
|
||||||
return `ok: unfollowed`;
|
return `ok: unfollowed`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `skip: リクエストもフォローもされていない`;
|
return `skip: Not requested or followed`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Resolver from '../../resolver.js';
|
||||||
import { updatePerson } from '../../models/person.js';
|
import { updatePerson } from '../../models/person.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updateアクティビティを捌きます
|
* Handler for the Update activity
|
||||||
*/
|
*/
|
||||||
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
|
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
|
||||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
|
|
|
@ -11,10 +11,10 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imageを作成します。
|
* create an Image.
|
||||||
*/
|
*/
|
||||||
export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||||
// 投稿者が凍結されていたらスキップ
|
// Skip if author is frozen.
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
throw new Error('actor has been suspended');
|
throw new Error('actor has been suspended');
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi
|
||||||
});
|
});
|
||||||
|
|
||||||
if (file.isLink) {
|
if (file.isLink) {
|
||||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
// If the URL is different, it means that the same image was previously
|
||||||
// URLを更新する
|
// registered with a different URL, so update the URL
|
||||||
if (file.url !== image.url) {
|
if (file.url !== image.url) {
|
||||||
await DriveFiles.update({ id: file.id }, {
|
await DriveFiles.update({ id: file.id }, {
|
||||||
url: image.url,
|
url: image.url,
|
||||||
|
@ -55,14 +55,14 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imageを解決します。
|
* Resolve Image.
|
||||||
*
|
*
|
||||||
* Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
|
* If the target Image is registered in Calckey, return it, otherwise
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* Fetch from remote server, register with Calckey and return it.
|
||||||
*/
|
*/
|
||||||
export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
// リモートサーバーからフェッチしてきて登録
|
// Fetch from remote server and register
|
||||||
return await createImage(actor, value);
|
return await createImage(actor, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
export async function fetchNote(object: string | IObject): Promise<Note | null> {
|
||||||
const dbResolver = new DbResolver();
|
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> {
|
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
|
||||||
if (resolver == null) resolver = new Resolver();
|
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}`);
|
logger.info(`Creating the Note: ${note.id}`);
|
||||||
|
|
||||||
// 投稿者をフェッチ
|
// Fetch author
|
||||||
const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
|
const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
|
||||||
|
|
||||||
// 投稿者が凍結されていたらスキップ
|
// Skip if author is suspended.
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
throw new Error('actor has been suspended');
|
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;
|
let visibility = noteAudience.visibility;
|
||||||
const visibleUsers = noteAudience.visibleUsers;
|
const visibleUsers = noteAudience.visibleUsers;
|
||||||
|
|
||||||
// Audience (to, cc) が指定されてなかった場合
|
// If Audience (to, cc) was not specified
|
||||||
if (visibility === 'specified' && visibleUsers.length === 0) {
|
if (visibility === 'specified' && visibleUsers.length === 0) {
|
||||||
if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
|
if (typeof value === 'string') { // If the input is a string, GET occurs in resolver
|
||||||
// こちらから匿名GET出来たものならばpublic
|
// Public if you can GET anonymously from here
|
||||||
visibility = 'public';
|
visibility = 'public';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||||
const apMentions = await extractApMentions(note.tag);
|
const apMentions = await extractApMentions(note.tag);
|
||||||
const apHashtags = await extractApHashtags(note.tag);
|
const apHashtags = await extractApHashtags(note.tag);
|
||||||
|
|
||||||
// 添付ファイル
|
// Attachments
|
||||||
// TODO: attachmentは必ずしもImageではない
|
// TODO: attachmentは必ずしもImageではない
|
||||||
// TODO: attachmentは必ずしも配列ではない
|
// TODO: attachmentは必ずしも配列ではない
|
||||||
// Noteがsensitiveなら添付もsensitiveにする
|
// Noteがsensitiveなら添付もsensitiveにする
|
||||||
|
@ -127,7 +127,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||||
.filter(image => image != null)
|
.filter(image => image != null)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// リプライ
|
// Reply
|
||||||
const reply: Note | null = note.inReplyTo
|
const reply: Note | null = note.inReplyTo
|
||||||
? await resolveNote(note.inReplyTo, resolver).then(x => {
|
? await resolveNote(note.inReplyTo, resolver).then(x => {
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
|
@ -153,7 +153,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// 引用
|
// Quote
|
||||||
let quote: Note | undefined | null;
|
let quote: Note | undefined | null;
|
||||||
|
|
||||||
if (note._misskey_quote || note.quoteUrl) {
|
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;
|
const cw = note.summary === '' ? null : note.summary;
|
||||||
|
|
||||||
// テキストのパース
|
// Text parsing
|
||||||
let text: string | null = null;
|
let text: string | null = null;
|
||||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') {
|
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') {
|
||||||
text = note.source.content;
|
text = note.source.content;
|
||||||
|
@ -265,23 +265,23 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Noteを解決します。
|
* Resolve Note.
|
||||||
*
|
*
|
||||||
* Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ
|
* If the target Note is registered in Calckey, return it, otherwise
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* Fetch from remote server, register with Calckey and return it.
|
||||||
*/
|
*/
|
||||||
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
|
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
|
||||||
const uri = typeof value === 'string' ? value : value.id;
|
const uri = typeof value === 'string' ? value : value.id;
|
||||||
if (uri == null) throw new Error('missing uri');
|
if (uri == null) throw new Error('missing uri');
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// Abort if origin host is blocked
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
if (meta.blockedHosts.includes(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`);
|
if (meta.blockedHosts.includes(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`);
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const unlock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region Returns if already registered with this server
|
||||||
const exist = await fetchNote(uri);
|
const exist = await fetchNote(uri);
|
||||||
|
|
||||||
if (exist) {
|
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');
|
throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note');
|
||||||
}
|
}
|
||||||
|
|
||||||
// リモートサーバーからフェッチしてきて登録
|
// Fetch from remote server and register
|
||||||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
// If the attached `Note` Object is specified here instead of the uri, the note will be generated without going through the server fetch.
|
||||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
|
||||||
return await createNote(uri, resolver, true);
|
return await createNote(uri, resolver, true);
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
unlock();
|
||||||
|
|
|
@ -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> {
|
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<CacheableUser | null> {
|
||||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
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);
|
const cached = uriPersonCache.get(uri);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
// Fetch from the database if the URI points to this server
|
||||||
if (uri.startsWith(config.url + '/')) {
|
if (uri.startsWith(config.url + '/')) {
|
||||||
const id = uri.split('/').pop();
|
const id = uri.split('/').pop();
|
||||||
const u = await Users.findOneBy({ id });
|
const u = await Users.findOneBy({ id });
|
||||||
|
@ -119,7 +119,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Cac
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region Returns if already registered with this server
|
||||||
const exist = await Users.findOneBy({ uri });
|
const exist = await Users.findOneBy({ uri });
|
||||||
|
|
||||||
if (exist) {
|
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> {
|
export async function createPerson(uri: string, resolver?: Resolver): Promise<User> {
|
||||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
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) {
|
} catch (e) {
|
||||||
// duplicate key error
|
// duplicate key error
|
||||||
if (isDuplicateKeyValueError(e)) {
|
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({
|
const u = await Users.findOneBy({
|
||||||
uri: person.id,
|
uri: person.id,
|
||||||
});
|
});
|
||||||
|
@ -235,10 +235,10 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||||
|
|
||||||
usersChart.update(user!, true);
|
usersChart.update(user!, true);
|
||||||
|
|
||||||
// ハッシュタグ更新
|
// Hashtag update
|
||||||
updateUsertags(user!, tags);
|
updateUsertags(user!, tags);
|
||||||
|
|
||||||
//#region アバターとヘッダー画像をフェッチ
|
//#region Fetch avatar and header image
|
||||||
const [avatar, banner] = await Promise.all([
|
const [avatar, banner] = await Promise.all([
|
||||||
person.icon,
|
person.icon,
|
||||||
person.image,
|
person.image,
|
||||||
|
@ -260,7 +260,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||||
user!.bannerId = bannerId;
|
user!.bannerId = bannerId;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region カスタム絵文字取得
|
//#region Get custom emoji
|
||||||
const emojis = await extractEmojis(person.tag || [], host).catch(e => {
|
const emojis = await extractEmojis(person.tag || [], host).catch(e => {
|
||||||
logger.info(`extractEmojis: ${e}`);
|
logger.info(`extractEmojis: ${e}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
|
|
|
@ -43,10 +43,10 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
|
||||||
export async function updateQuestion(value: any, resolver?: Resolver) {
|
export async function updateQuestion(value: any, resolver?: Resolver) {
|
||||||
const uri = typeof value === 'string' ? value : value.id;
|
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');
|
if (uri.startsWith(config.url + '/')) throw new Error('uri points local');
|
||||||
|
|
||||||
//#region このサーバーに既に登録されているか
|
//#region Already registered with this server?
|
||||||
const note = await Notes.findOneBy({ uri });
|
const note = await Notes.findOneBy({ uri });
|
||||||
if (note == null) throw new Error('Question is not registed');
|
if (note == null) throw new Error('Question is not registed');
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { updatePerson } from './models/person.js';
|
||||||
export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
|
export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
|
||||||
await performActivity(actor, activity);
|
await performActivity(actor, activity);
|
||||||
|
|
||||||
// ついでにリモートユーザーの情報が古かったら更新しておく
|
// Update the remote user information if it is out of date
|
||||||
if (actor.uri) {
|
if (actor.uri) {
|
||||||
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
|
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
|
|
|
@ -58,7 +58,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 通知を作成
|
// Create notification that request was accepted.
|
||||||
createNotification(follower.id, 'followRequestAccepted', {
|
createNotification(follower.id, 'followRequestAccepted', {
|
||||||
notifierId: followee.id,
|
notifierId: followee.id,
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { User } from '@/models/entities/user.js';
|
||||||
import { FollowRequests, Users } from '@/models/index.js';
|
import { FollowRequests, Users } from '@/models/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指定したユーザー宛てのフォローリクエストをすべて承認
|
* Approve all follow requests for the specified user
|
||||||
* @param user ユーザー
|
* @param user User.
|
||||||
*/
|
*/
|
||||||
export default async function(user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }) {
|
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({
|
const requests = await FollowRequests.findBy({
|
||||||
|
|
|
@ -11,7 +11,7 @@ export async function doPostSuspend(user: { id: User['id']; host: User['host'] }
|
||||||
publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
|
publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
|
||||||
|
|
||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
// 知り得る全SharedInboxにDelete配信
|
// Send Delete to all known SharedInboxes
|
||||||
const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user));
|
const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user));
|
||||||
|
|
||||||
const queue: string[] = [];
|
const queue: string[] = [];
|
||||||
|
|
Loading…
Reference in New Issue