mirror of
https://codeberg.org/yeentown/barkey
synced 2024-11-26 18:03:01 +00:00
test
This commit is contained in:
parent
92c78218bc
commit
a2eac9fff6
82 changed files with 671 additions and 671 deletions
|
@ -15,7 +15,7 @@ let isSupportedCpu: undefined | boolean = undefined;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AiService {
|
export class AiService {
|
||||||
#model: nsfw.NSFWJS;
|
private model: nsfw.NSFWJS;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -26,7 +26,7 @@ export class AiService {
|
||||||
public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
|
public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
|
||||||
try {
|
try {
|
||||||
if (isSupportedCpu === undefined) {
|
if (isSupportedCpu === undefined) {
|
||||||
const cpuFlags = await this.#getCpuFlags();
|
const cpuFlags = await this.getCpuFlags();
|
||||||
isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required));
|
isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,12 @@ export class AiService {
|
||||||
|
|
||||||
const tf = await import('@tensorflow/tfjs-node');
|
const tf = await import('@tensorflow/tfjs-node');
|
||||||
|
|
||||||
if (this.#model == null) this.#model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
if (this.model == null) this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
||||||
|
|
||||||
const buffer = await fs.promises.readFile(path);
|
const buffer = await fs.promises.readFile(path);
|
||||||
const image = await tf.node.decodeImage(buffer, 3) as any;
|
const image = await tf.node.decodeImage(buffer, 3) as any;
|
||||||
try {
|
try {
|
||||||
const predictions = await this.#model.classify(image);
|
const predictions = await this.model.classify(image);
|
||||||
return predictions;
|
return predictions;
|
||||||
} finally {
|
} finally {
|
||||||
image.dispose();
|
image.dispose();
|
||||||
|
@ -53,7 +53,7 @@ export class AiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getCpuFlags(): Promise<string[]> {
|
private async getCpuFlags(): Promise<string[]> {
|
||||||
const str = await si.cpuFlags();
|
const str = await si.cpuFlags();
|
||||||
return str.split(/\s+/);
|
return str.split(/\s+/);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AntennaService implements OnApplicationShutdown {
|
export class AntennaService implements OnApplicationShutdown {
|
||||||
#antennasFetched: boolean;
|
private antennasFetched: boolean;
|
||||||
#antennas: Antenna[];
|
private antennas: Antenna[];
|
||||||
#blockingCache: Cache<User['id'][]>;
|
private blockingCache: Cache<User['id'][]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redisSubscriber)
|
@Inject(DI.redisSubscriber)
|
||||||
|
@ -49,9 +49,9 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private globalEventServie: GlobalEventService,
|
private globalEventServie: GlobalEventService,
|
||||||
) {
|
) {
|
||||||
this.#antennasFetched = false;
|
this.antennasFetched = false;
|
||||||
this.#antennas = [];
|
this.antennas = [];
|
||||||
this.#blockingCache = new Cache<User['id'][]>(1000 * 60 * 5);
|
this.blockingCache = new Cache<User['id'][]>(1000 * 60 * 5);
|
||||||
|
|
||||||
this.redisSubscriber.on('message', this.onRedisMessage);
|
this.redisSubscriber.on('message', this.onRedisMessage);
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
const { type, body } = obj.message;
|
const { type, body } = obj.message;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'antennaCreated':
|
case 'antennaCreated':
|
||||||
this.#antennas.push(body);
|
this.antennas.push(body);
|
||||||
break;
|
break;
|
||||||
case 'antennaUpdated':
|
case 'antennaUpdated':
|
||||||
this.#antennas[this.#antennas.findIndex(a => a.id === body.id)] = body;
|
this.antennas[this.antennas.findIndex(a => a.id === body.id)] = body;
|
||||||
break;
|
break;
|
||||||
case 'antennaDeleted':
|
case 'antennaDeleted':
|
||||||
this.#antennas = this.#antennas.filter(a => a.id !== body.id);
|
this.antennas = this.antennas.filter(a => a.id !== body.id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -137,7 +137,7 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
if (note.visibility === 'specified') return false;
|
if (note.visibility === 'specified') return false;
|
||||||
|
|
||||||
// アンテナ作成者がノート作成者にブロックされていたらスキップ
|
// アンテナ作成者がノート作成者にブロックされていたらスキップ
|
||||||
const blockings = await this.#blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
|
const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
|
||||||
if (blockings.some(blocking => blocking === antenna.userId)) return false;
|
if (blockings.some(blocking => blocking === antenna.userId)) return false;
|
||||||
|
|
||||||
if (note.visibility === 'followers') {
|
if (note.visibility === 'followers') {
|
||||||
|
@ -218,11 +218,11 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAntennas() {
|
public async getAntennas() {
|
||||||
if (!this.#antennasFetched) {
|
if (!this.antennasFetched) {
|
||||||
this.#antennas = await this.antennasRepository.find();
|
this.antennas = await this.antennasRepository.find();
|
||||||
this.#antennasFetched = true;
|
this.antennasFetched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.#antennas;
|
return this.antennas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,13 @@ const retryDelay = 100;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppLockService {
|
export class AppLockService {
|
||||||
#lock: (key: string, timeout?: number) => Promise<() => void>;
|
private lock: (key: string, timeout?: number) => Promise<() => void>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
) {
|
) {
|
||||||
this.#lock = promisify(redisLock(this.redisClient, retryDelay));
|
this.lock = promisify(redisLock(this.redisClient, retryDelay));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,14 +27,14 @@ export class AppLockService {
|
||||||
* @returns Unlock function
|
* @returns Unlock function
|
||||||
*/
|
*/
|
||||||
public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> {
|
public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> {
|
||||||
return this.#lock(`ap-object:${uri}`, timeout);
|
return this.lock(`ap-object:${uri}`, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> {
|
public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> {
|
||||||
return this.#lock(`instance:${host}`, timeout);
|
return this.lock(`instance:${host}`, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> {
|
public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> {
|
||||||
return this.#lock(`chart-insert:${lockKey}`, timeout);
|
return this.lock(`chart-insert:${lockKey}`, timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export class CaptchaService {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getCaptchaResponse(url: string, secret: string, response: string): Promise<CaptchaResponse> {
|
private async getCaptchaResponse(url: string, secret: string, response: string): Promise<CaptchaResponse> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
secret,
|
secret,
|
||||||
response,
|
response,
|
||||||
|
@ -46,7 +46,7 @@ export class CaptchaService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async verifyRecaptcha(secret: string, response: string): Promise<void> {
|
public async verifyRecaptcha(secret: string, response: string): Promise<void> {
|
||||||
const result = await this.#getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => {
|
const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => {
|
||||||
throw `recaptcha-request-failed: ${e}`;
|
throw `recaptcha-request-failed: ${e}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ export class CaptchaService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async verifyHcaptcha(secret: string, response: string): Promise<void> {
|
public async verifyHcaptcha(secret: string, response: string): Promise<void> {
|
||||||
const result = await this.#getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => {
|
const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => {
|
||||||
throw `hcaptcha-request-failed: ${e}`;
|
throw `hcaptcha-request-failed: ${e}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,8 @@ export class CreateNotificationService {
|
||||||
this.globalEventServie.publishMainStream(notifieeId, 'unreadNotification', packed);
|
this.globalEventServie.publishMainStream(notifieeId, 'unreadNotification', packed);
|
||||||
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
|
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
|
||||||
|
|
||||||
if (type === 'follow') this.#emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
||||||
if (type === 'receiveFollowRequest') this.#emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
|
@ -90,7 +90,7 @@ export class CreateNotificationService {
|
||||||
|
|
||||||
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
|
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
|
||||||
|
|
||||||
async #emailNotificationFollow(userId: User['id'], follower: User) {
|
private async emailNotificationFollow(userId: User['id'], follower: User) {
|
||||||
/*
|
/*
|
||||||
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
|
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
|
||||||
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
|
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
|
||||||
|
@ -101,7 +101,7 @@ export class CreateNotificationService {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
async #emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
|
private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
|
||||||
/*
|
/*
|
||||||
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
|
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
|
||||||
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return;
|
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('receiveFollowRequest')) return;
|
||||||
|
|
|
@ -23,7 +23,7 @@ type PopulatedEmoji = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CustomEmojiService {
|
export class CustomEmojiService {
|
||||||
#cache: Cache<Emoji | null>;
|
private cache: Cache<Emoji | null>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -40,7 +40,7 @@ export class CustomEmojiService {
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private reactionService: ReactionService,
|
private reactionService: ReactionService,
|
||||||
) {
|
) {
|
||||||
this.#cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12);
|
this.cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async add(data: {
|
public async add(data: {
|
||||||
|
@ -67,7 +67,7 @@ export class CustomEmojiService {
|
||||||
return emoji;
|
return emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
#normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
|
private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
|
||||||
// クエリに使うホスト
|
// クエリに使うホスト
|
||||||
let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
|
let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
|
||||||
: src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
|
: src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
|
||||||
|
@ -79,14 +79,14 @@ export class CustomEmojiService {
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
#parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
private parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
||||||
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
|
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
|
||||||
if (!match) return { name: null, host: null };
|
if (!match) return { name: null, host: null };
|
||||||
|
|
||||||
const name = match[1];
|
const name = match[1];
|
||||||
|
|
||||||
// ホスト正規化
|
// ホスト正規化
|
||||||
const host = this.utilityService.toPunyNullable(this.#normalizeHost(match[2], noteUserHost));
|
const host = this.utilityService.toPunyNullable(this.normalizeHost(match[2], noteUserHost));
|
||||||
|
|
||||||
return { name, host };
|
return { name, host };
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ export class CustomEmojiService {
|
||||||
* @returns 絵文字情報, nullは未マッチを意味する
|
* @returns 絵文字情報, nullは未マッチを意味する
|
||||||
*/
|
*/
|
||||||
public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> {
|
public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> {
|
||||||
const { name, host } = this.#parseEmojiStr(emojiName, noteUserHost);
|
const { name, host } = this.parseEmojiStr(emojiName, noteUserHost);
|
||||||
if (name == null) return null;
|
if (name == null) return null;
|
||||||
|
|
||||||
const queryOrNull = async () => (await this.emojisRepository.findOneBy({
|
const queryOrNull = async () => (await this.emojisRepository.findOneBy({
|
||||||
|
@ -106,7 +106,7 @@ export class CustomEmojiService {
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
})) ?? null;
|
})) ?? null;
|
||||||
|
|
||||||
const emoji = await this.#cache.fetch(`${name} ${host}`, queryOrNull);
|
const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull);
|
||||||
|
|
||||||
if (emoji == null) return null;
|
if (emoji == null) return null;
|
||||||
|
|
||||||
|
@ -132,20 +132,20 @@ export class CustomEmojiService {
|
||||||
let emojis: { name: string | null; host: string | null; }[] = [];
|
let emojis: { name: string | null; host: string | null; }[] = [];
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
emojis = emojis.concat(note.emojis
|
emojis = emojis.concat(note.emojis
|
||||||
.map(e => this.#parseEmojiStr(e, note.userHost)));
|
.map(e => this.parseEmojiStr(e, note.userHost)));
|
||||||
if (note.renote) {
|
if (note.renote) {
|
||||||
emojis = emojis.concat(note.renote.emojis
|
emojis = emojis.concat(note.renote.emojis
|
||||||
.map(e => this.#parseEmojiStr(e, note.renote!.userHost)));
|
.map(e => this.parseEmojiStr(e, note.renote!.userHost)));
|
||||||
if (note.renote.user) {
|
if (note.renote.user) {
|
||||||
emojis = emojis.concat(note.renote.user.emojis
|
emojis = emojis.concat(note.renote.user.emojis
|
||||||
.map(e => this.#parseEmojiStr(e, note.renote!.userHost)));
|
.map(e => this.parseEmojiStr(e, note.renote!.userHost)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
|
const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
|
||||||
emojis = emojis.concat(customReactions);
|
emojis = emojis.concat(customReactions);
|
||||||
if (note.user) {
|
if (note.user) {
|
||||||
emojis = emojis.concat(note.user.emojis
|
emojis = emojis.concat(note.user.emojis
|
||||||
.map(e => this.#parseEmojiStr(e, note.userHost)));
|
.map(e => this.parseEmojiStr(e, note.userHost)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[];
|
return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[];
|
||||||
|
@ -155,7 +155,7 @@ export class CustomEmojiService {
|
||||||
* 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します
|
* 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します
|
||||||
*/
|
*/
|
||||||
public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
|
public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
|
||||||
const notCachedEmojis = emojis.filter(emoji => this.#cache.get(`${emoji.name} ${emoji.host}`) == null);
|
const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null);
|
||||||
const emojisQuery: any[] = [];
|
const emojisQuery: any[] = [];
|
||||||
const hosts = new Set(notCachedEmojis.map(e => e.host));
|
const hosts = new Set(notCachedEmojis.map(e => e.host));
|
||||||
for (const host of hosts) {
|
for (const host of hosts) {
|
||||||
|
@ -169,7 +169,7 @@ export class CustomEmojiService {
|
||||||
select: ['name', 'host', 'originalUrl', 'publicUrl'],
|
select: ['name', 'host', 'originalUrl', 'publicUrl'],
|
||||||
}) : [];
|
}) : [];
|
||||||
for (const emoji of _emojis) {
|
for (const emoji of _emojis) {
|
||||||
this.#cache.set(`${emoji.name} ${emoji.host}`, emoji);
|
this.cache.set(`${emoji.name} ${emoji.host}`, emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ const pipeline = util.promisify(stream.pipeline);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DownloadService {
|
export class DownloadService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -27,11 +27,11 @@ export class DownloadService {
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('download');
|
this.logger = this.loggerService.getLogger('download');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async downloadUrl(url: string, path: string): Promise<void> {
|
public async downloadUrl(url: string, path: string): Promise<void> {
|
||||||
this.#logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
this.logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||||
|
|
||||||
const timeout = 30 * 1000;
|
const timeout = 30 * 1000;
|
||||||
const operationTimeout = 60 * 1000;
|
const operationTimeout = 60 * 1000;
|
||||||
|
@ -60,8 +60,8 @@ export class DownloadService {
|
||||||
},
|
},
|
||||||
}).on('response', (res: Got.Response) => {
|
}).on('response', (res: Got.Response) => {
|
||||||
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) {
|
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) {
|
||||||
if (this.#isPrivateIp(res.ip)) {
|
if (this.isPrivateIp(res.ip)) {
|
||||||
this.#logger.warn(`Blocked address: ${res.ip}`);
|
this.logger.warn(`Blocked address: ${res.ip}`);
|
||||||
req.destroy();
|
req.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,13 +70,13 @@ export class DownloadService {
|
||||||
if (contentLength != null) {
|
if (contentLength != null) {
|
||||||
const size = Number(contentLength);
|
const size = Number(contentLength);
|
||||||
if (size > maxSize) {
|
if (size > maxSize) {
|
||||||
this.#logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`);
|
this.logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`);
|
||||||
req.destroy();
|
req.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).on('downloadProgress', (progress: Got.Progress) => {
|
}).on('downloadProgress', (progress: Got.Progress) => {
|
||||||
if (progress.transferred > maxSize) {
|
if (progress.transferred > maxSize) {
|
||||||
this.#logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
|
this.logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
|
||||||
req.destroy();
|
req.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -91,14 +91,14 @@ export class DownloadService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
this.logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async downloadTextFile(url: string): Promise<string> {
|
public async downloadTextFile(url: string): Promise<string> {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
this.#logger.info(`text file: Temp file is ${path}`);
|
this.logger.info(`text file: Temp file is ${path}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// write content at URL to temp file
|
// write content at URL to temp file
|
||||||
|
@ -112,7 +112,7 @@ export class DownloadService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#isPrivateIp(ip: string): boolean {
|
private isPrivateIp(ip: string): boolean {
|
||||||
for (const net of this.config.allowedPrivateNetworks ?? []) {
|
for (const net of this.config.allowedPrivateNetworks ?? []) {
|
||||||
const cidr = new IPCIDR(net);
|
const cidr = new IPCIDR(net);
|
||||||
if (cidr.contains(ip)) {
|
if (cidr.contains(ip)) {
|
||||||
|
|
|
@ -74,8 +74,8 @@ type UploadFromUrlArgs = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DriveService {
|
export class DriveService {
|
||||||
#registerLogger: Logger;
|
private registerLogger: Logger;
|
||||||
#downloaderLogger: Logger;
|
private downloaderLogger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -110,8 +110,8 @@ export class DriveService {
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
) {
|
) {
|
||||||
const logger = new Logger('drive', 'blue');
|
const logger = new Logger('drive', 'blue');
|
||||||
this.#registerLogger = logger.createSubLogger('register', 'yellow');
|
this.registerLogger = logger.createSubLogger('register', 'yellow');
|
||||||
this.#downloaderLogger = logger.createSubLogger('downloader');
|
this.downloaderLogger = logger.createSubLogger('downloader');
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
@ -122,7 +122,7 @@ export class DriveService {
|
||||||
* @param hash Hash for original
|
* @param hash Hash for original
|
||||||
* @param size Size for original
|
* @param size Size for original
|
||||||
*/
|
*/
|
||||||
async #save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise<DriveFile> {
|
private async save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise<DriveFile> {
|
||||||
// thunbnail, webpublic を必要なら生成
|
// thunbnail, webpublic を必要なら生成
|
||||||
const alts = await this.generateAlts(path, type, !file.uri);
|
const alts = await this.generateAlts(path, type, !file.uri);
|
||||||
|
|
||||||
|
@ -161,25 +161,25 @@ export class DriveService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Uploads
|
//#region Uploads
|
||||||
this.#registerLogger.info(`uploading original: ${key}`);
|
this.registerLogger.info(`uploading original: ${key}`);
|
||||||
const uploads = [
|
const uploads = [
|
||||||
this.#upload(key, fs.createReadStream(path), type, name),
|
this.upload(key, fs.createReadStream(path), type, name),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (alts.webpublic) {
|
if (alts.webpublic) {
|
||||||
webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${alts.webpublic.ext}`;
|
webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${alts.webpublic.ext}`;
|
||||||
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
||||||
|
|
||||||
this.#registerLogger.info(`uploading webpublic: ${webpublicKey}`);
|
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
|
||||||
uploads.push(this.#upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name));
|
uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alts.thumbnail) {
|
if (alts.thumbnail) {
|
||||||
thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${alts.thumbnail.ext}`;
|
thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${alts.thumbnail.ext}`;
|
||||||
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
|
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
|
||||||
|
|
||||||
this.#registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
|
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
|
||||||
uploads.push(this.#upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type));
|
uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(uploads);
|
await Promise.all(uploads);
|
||||||
|
@ -211,12 +211,12 @@ export class DriveService {
|
||||||
|
|
||||||
if (alts.thumbnail) {
|
if (alts.thumbnail) {
|
||||||
thumbnailUrl = this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data);
|
thumbnailUrl = this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data);
|
||||||
this.#registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`);
|
this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alts.webpublic) {
|
if (alts.webpublic) {
|
||||||
webpublicUrl = this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data);
|
webpublicUrl = this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data);
|
||||||
this.#registerLogger.info(`web stored: ${webpublicAccessKey}`);
|
this.registerLogger.info(`web stored: ${webpublicAccessKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
file.storedInternal = true;
|
file.storedInternal = true;
|
||||||
|
@ -251,7 +251,7 @@ export class DriveService {
|
||||||
thumbnail,
|
thumbnail,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`);
|
this.registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`);
|
||||||
return {
|
return {
|
||||||
webpublic: null,
|
webpublic: null,
|
||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
|
@ -260,7 +260,7 @@ export class DriveService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) {
|
if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) {
|
||||||
this.#registerLogger.debug('web image and thumbnail not created (not an required file)');
|
this.registerLogger.debug('web image and thumbnail not created (not an required file)');
|
||||||
return {
|
return {
|
||||||
webpublic: null,
|
webpublic: null,
|
||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
|
@ -290,7 +290,7 @@ export class DriveService {
|
||||||
metadata.height && metadata.height <= 2048
|
metadata.height && metadata.height <= 2048
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#registerLogger.warn(`sharp failed: ${err}`);
|
this.registerLogger.warn(`sharp failed: ${err}`);
|
||||||
return {
|
return {
|
||||||
webpublic: null,
|
webpublic: null,
|
||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
|
@ -301,7 +301,7 @@ export class DriveService {
|
||||||
let webpublic: IImage | null = null;
|
let webpublic: IImage | null = null;
|
||||||
|
|
||||||
if (generateWeb && !satisfyWebpublic) {
|
if (generateWeb && !satisfyWebpublic) {
|
||||||
this.#registerLogger.info('creating web image');
|
this.registerLogger.info('creating web image');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (['image/jpeg', 'image/webp'].includes(type)) {
|
if (['image/jpeg', 'image/webp'].includes(type)) {
|
||||||
|
@ -311,14 +311,14 @@ export class DriveService {
|
||||||
} else if (['image/svg+xml'].includes(type)) {
|
} else if (['image/svg+xml'].includes(type)) {
|
||||||
webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
|
webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
|
||||||
} else {
|
} else {
|
||||||
this.#registerLogger.debug('web image not created (not an required image)');
|
this.registerLogger.debug('web image not created (not an required image)');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#registerLogger.warn('web image not created (an error occured)', err as Error);
|
this.registerLogger.warn('web image not created (an error occured)', err as Error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (satisfyWebpublic) this.#registerLogger.info('web image not created (original satisfies webpublic)');
|
if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)');
|
||||||
else this.#registerLogger.info('web image not created (from remote)');
|
else this.registerLogger.info('web image not created (from remote)');
|
||||||
}
|
}
|
||||||
// #endregion webpublic
|
// #endregion webpublic
|
||||||
|
|
||||||
|
@ -329,10 +329,10 @@ export class DriveService {
|
||||||
if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) {
|
if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) {
|
||||||
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280);
|
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280);
|
||||||
} else {
|
} else {
|
||||||
this.#registerLogger.debug('thumbnail not created (not an required file)');
|
this.registerLogger.debug('thumbnail not created (not an required file)');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#registerLogger.warn('thumbnail not created (an error occured)', err as Error);
|
this.registerLogger.warn('thumbnail not created (an error occured)', err as Error);
|
||||||
}
|
}
|
||||||
// #endregion thumbnail
|
// #endregion thumbnail
|
||||||
|
|
||||||
|
@ -345,7 +345,7 @@ export class DriveService {
|
||||||
/**
|
/**
|
||||||
* Upload to ObjectStorage
|
* Upload to ObjectStorage
|
||||||
*/
|
*/
|
||||||
async #upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
|
private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
|
||||||
if (type === 'image/apng') type = 'image/png';
|
if (type === 'image/apng') type = 'image/png';
|
||||||
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
|
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
|
||||||
|
|
||||||
|
@ -369,10 +369,10 @@ export class DriveService {
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await upload.promise();
|
const result = await upload.promise();
|
||||||
if (result) this.#registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
|
if (result) this.registerLogger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #deleteOldFile(user: IRemoteUser) {
|
private async deleteOldFile(user: IRemoteUser) {
|
||||||
const q = this.driveFilesRepository.createQueryBuilder('file')
|
const q = this.driveFilesRepository.createQueryBuilder('file')
|
||||||
.where('file.userId = :userId', { userId: user.id })
|
.where('file.userId = :userId', { userId: user.id })
|
||||||
.andWhere('file.isLink = FALSE');
|
.andWhere('file.isLink = FALSE');
|
||||||
|
@ -430,7 +430,7 @@ export class DriveService {
|
||||||
sensitiveThresholdForPorn: 0.75,
|
sensitiveThresholdForPorn: 0.75,
|
||||||
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
||||||
});
|
});
|
||||||
this.#registerLogger.info(`${JSON.stringify(info)}`);
|
this.registerLogger.info(`${JSON.stringify(info)}`);
|
||||||
|
|
||||||
// 現状 false positive が多すぎて実用に耐えない
|
// 現状 false positive が多すぎて実用に耐えない
|
||||||
//if (info.porn && instance.disallowUploadWhenPredictedAsPorn) {
|
//if (info.porn && instance.disallowUploadWhenPredictedAsPorn) {
|
||||||
|
@ -448,7 +448,7 @@ export class DriveService {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (much) {
|
if (much) {
|
||||||
this.#registerLogger.info(`file with same hash is found: ${much.id}`);
|
this.registerLogger.info(`file with same hash is found: ${much.id}`);
|
||||||
return much;
|
return much;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,11 +463,11 @@ export class DriveService {
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) {
|
if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) {
|
||||||
driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb;
|
driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb;
|
||||||
this.#registerLogger.debug('drive capacity override applied');
|
this.registerLogger.debug('drive capacity override applied');
|
||||||
this.#registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
|
this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
|
||||||
|
|
||||||
// If usage limit exceeded
|
// If usage limit exceeded
|
||||||
if (usage + info.size > driveCapacity) {
|
if (usage + info.size > driveCapacity) {
|
||||||
|
@ -475,7 +475,7 @@ export class DriveService {
|
||||||
throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
|
throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
|
||||||
} else {
|
} else {
|
||||||
// (アバターまたはバナーを含まず)最も古いファイルを削除する
|
// (アバターまたはバナーを含まず)最も古いファイルを削除する
|
||||||
this.#deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser);
|
this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -566,22 +566,22 @@ export class DriveService {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// duplicate key error (when already registered)
|
// duplicate key error (when already registered)
|
||||||
if (isDuplicateKeyValueError(err)) {
|
if (isDuplicateKeyValueError(err)) {
|
||||||
this.#registerLogger.info(`already registered ${file.uri}`);
|
this.registerLogger.info(`already registered ${file.uri}`);
|
||||||
|
|
||||||
file = await this.driveFilesRepository.findOneBy({
|
file = await this.driveFilesRepository.findOneBy({
|
||||||
uri: file.uri!,
|
uri: file.uri!,
|
||||||
userId: user ? user.id : IsNull(),
|
userId: user ? user.id : IsNull(),
|
||||||
}) as DriveFile;
|
}) as DriveFile;
|
||||||
} else {
|
} else {
|
||||||
this.#registerLogger.error(err as Error);
|
this.registerLogger.error(err as Error);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
file = await (this.#save(file, path, detectedName, info.type.mime, info.md5, info.size));
|
file = await (this.save(file, path, detectedName, info.type.mime, info.md5, info.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#registerLogger.succ(`drive file has been created ${file.id}`);
|
this.registerLogger.succ(`drive file has been created ${file.id}`);
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
this.driveFileEntityService.pack(file, { self: true }).then(packedFile => {
|
this.driveFileEntityService.pack(file, { self: true }).then(packedFile => {
|
||||||
|
@ -624,7 +624,7 @@ export class DriveService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#deletePostProcess(file, isExpired);
|
this.deletePostProcess(file, isExpired);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteFileSync(file: DriveFile, isExpired = false) {
|
public async deleteFileSync(file: DriveFile, isExpired = false) {
|
||||||
|
@ -654,10 +654,10 @@ export class DriveService {
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#deletePostProcess(file, isExpired);
|
this.deletePostProcess(file, isExpired);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #deletePostProcess(file: DriveFile, isExpired = false) {
|
private async deletePostProcess(file: DriveFile, isExpired = false) {
|
||||||
// リモートファイル期限切れ削除後は直リンクにする
|
// リモートファイル期限切れ削除後は直リンクにする
|
||||||
if (isExpired && file.userHost !== null && file.uri != null) {
|
if (isExpired && file.userHost !== null && file.uri != null) {
|
||||||
this.driveFilesRepository.update(file.id, {
|
this.driveFilesRepository.update(file.id, {
|
||||||
|
@ -725,10 +725,10 @@ export class DriveService {
|
||||||
await this.downloadService.downloadUrl(url, path);
|
await this.downloadService.downloadUrl(url, path);
|
||||||
|
|
||||||
const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders });
|
const driveFile = await this.addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders });
|
||||||
this.#downloaderLogger.succ(`Got: ${driveFile.id}`);
|
this.downloaderLogger.succ(`Got: ${driveFile.id}`);
|
||||||
return driveFile!;
|
return driveFile!;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#downloaderLogger.error(`Failed to create drive file: ${err}`, {
|
this.downloaderLogger.error(`Failed to create drive file: ${err}`, {
|
||||||
url: url,
|
url: url,
|
||||||
e: err,
|
e: err,
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -22,7 +22,7 @@ export class EmailService {
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('email');
|
this.logger = this.loggerService.getLogger('email');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendEmail(to: string, subject: string, html: string, text: string) {
|
public async sendEmail(to: string, subject: string, html: string, text: string) {
|
||||||
|
@ -134,9 +134,9 @@ export class EmailService {
|
||||||
</html>`,
|
</html>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#logger.info(`Message sent: ${info.messageId}`);
|
this.logger.info(`Message sent: ${info.messageId}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#logger.error(err as Error);
|
this.logger.error(err as Error);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { UtilityService } from './UtilityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FederatedInstanceService {
|
export class FederatedInstanceService {
|
||||||
#cache: Cache<Instance>;
|
private cache: Cache<Instance>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.instancesRepository)
|
@Inject(DI.instancesRepository)
|
||||||
|
@ -17,13 +17,13 @@ export class FederatedInstanceService {
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
this.#cache = new Cache<Instance>(1000 * 60 * 60);
|
this.cache = new Cache<Instance>(1000 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async registerOrFetchInstanceDoc(host: string): Promise<Instance> {
|
public async registerOrFetchInstanceDoc(host: string): Promise<Instance> {
|
||||||
host = this.utilityService.toPuny(host);
|
host = this.utilityService.toPuny(host);
|
||||||
|
|
||||||
const cached = this.#cache.get(host);
|
const cached = this.cache.get(host);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
const index = await this.instancesRepository.findOneBy({ host });
|
const index = await this.instancesRepository.findOneBy({ host });
|
||||||
|
@ -36,10 +36,10 @@ export class FederatedInstanceService {
|
||||||
lastCommunicatedAt: new Date(),
|
lastCommunicatedAt: new Date(),
|
||||||
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
this.#cache.set(host, i);
|
this.cache.set(host, i);
|
||||||
return i;
|
return i;
|
||||||
} else {
|
} else {
|
||||||
this.#cache.set(host, index);
|
this.cache.set(host, index);
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ type NodeInfo = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FetchInstanceMetadataService {
|
export class FetchInstanceMetadataService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.instancesRepository)
|
@Inject(DI.instancesRepository)
|
||||||
|
@ -42,7 +42,7 @@ export class FetchInstanceMetadataService {
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('metadata', 'cyan');
|
this.logger = this.loggerService.getLogger('metadata', 'cyan');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchInstanceMetadata(instance: Instance, force = false): Promise<void> {
|
public async fetchInstanceMetadata(instance: Instance, force = false): Promise<void> {
|
||||||
|
@ -57,24 +57,24 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.info(`Fetching metadata of ${instance.host} ...`);
|
this.logger.info(`Fetching metadata of ${instance.host} ...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [info, dom, manifest] = await Promise.all([
|
const [info, dom, manifest] = await Promise.all([
|
||||||
this.#fetchNodeinfo(instance).catch(() => null),
|
this.fetchNodeinfo(instance).catch(() => null),
|
||||||
this.#fetchDom(instance).catch(() => null),
|
this.fetchDom(instance).catch(() => null),
|
||||||
this.#fetchManifest(instance).catch(() => null),
|
this.fetchManifest(instance).catch(() => null),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [favicon, icon, themeColor, name, description] = await Promise.all([
|
const [favicon, icon, themeColor, name, description] = await Promise.all([
|
||||||
this.#fetchFaviconUrl(instance, dom).catch(() => null),
|
this.fetchFaviconUrl(instance, dom).catch(() => null),
|
||||||
this.#fetchIconUrl(instance, dom, manifest).catch(() => null),
|
this.fetchIconUrl(instance, dom, manifest).catch(() => null),
|
||||||
this.#getThemeColor(info, dom, manifest).catch(() => null),
|
this.getThemeColor(info, dom, manifest).catch(() => null),
|
||||||
this.#getSiteName(info, dom, manifest).catch(() => null),
|
this.getSiteName(info, dom, manifest).catch(() => null),
|
||||||
this.#getDescription(info, dom, manifest).catch(() => null),
|
this.getDescription(info, dom, manifest).catch(() => null),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.#logger.succ(`Successfuly fetched metadata of ${instance.host}`);
|
this.logger.succ(`Successfuly fetched metadata of ${instance.host}`);
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
infoUpdatedAt: new Date(),
|
infoUpdatedAt: new Date(),
|
||||||
|
@ -96,16 +96,16 @@ export class FetchInstanceMetadataService {
|
||||||
|
|
||||||
await this.instancesRepository.update(instance.id, updates);
|
await this.instancesRepository.update(instance.id, updates);
|
||||||
|
|
||||||
this.#logger.succ(`Successfuly updated metadata of ${instance.host}`);
|
this.logger.succ(`Successfuly updated metadata of ${instance.host}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #fetchNodeinfo(instance: Instance): Promise<NodeInfo> {
|
private async fetchNodeinfo(instance: Instance): Promise<NodeInfo> {
|
||||||
this.#logger.info(`Fetching nodeinfo of ${instance.host} ...`);
|
this.logger.info(`Fetching nodeinfo of ${instance.host} ...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
|
const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
|
||||||
|
@ -137,18 +137,18 @@ export class FetchInstanceMetadataService {
|
||||||
throw err.statusCode ?? err.message;
|
throw err.statusCode ?? err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
|
this.logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
|
||||||
|
|
||||||
return info as NodeInfo;
|
return info as NodeInfo;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`);
|
this.logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`);
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #fetchDom(instance: Instance): Promise<DOMWindow['document']> {
|
private async fetchDom(instance: Instance): Promise<DOMWindow['document']> {
|
||||||
this.#logger.info(`Fetching HTML of ${instance.host} ...`);
|
this.logger.info(`Fetching HTML of ${instance.host} ...`);
|
||||||
|
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ export class FetchInstanceMetadataService {
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #fetchManifest(instance: Instance): Promise<Record<string, unknown> | null> {
|
private async fetchManifest(instance: Instance): Promise<Record<string, unknown> | null> {
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
|
|
||||||
const manifestUrl = url + '/manifest.json';
|
const manifestUrl = url + '/manifest.json';
|
||||||
|
@ -170,7 +170,7 @@ export class FetchInstanceMetadataService {
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise<string | null> {
|
private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise<string | null> {
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
|
@ -197,7 +197,7 @@ export class FetchInstanceMetadataService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
return (new URL(manifest.icons[0].src, url)).href;
|
return (new URL(manifest.icons[0].src, url)).href;
|
||||||
|
@ -225,7 +225,7 @@ export class FetchInstanceMetadataService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
|
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
|
||||||
|
|
||||||
if (themeColor) {
|
if (themeColor) {
|
||||||
|
@ -236,7 +236,7 @@ export class FetchInstanceMetadataService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (info && info.metadata) {
|
if (info && info.metadata) {
|
||||||
if (info.metadata.nodeName || info.metadata.name) {
|
if (info.metadata.nodeName || info.metadata.name) {
|
||||||
return info.metadata.nodeName ?? info.metadata.name;
|
return info.metadata.nodeName ?? info.metadata.name;
|
||||||
|
@ -258,7 +258,7 @@ export class FetchInstanceMetadataService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (info && info.metadata) {
|
if (info && info.metadata) {
|
||||||
if (info.metadata.nodeDescription || info.metadata.description) {
|
if (info.metadata.nodeDescription || info.metadata.description) {
|
||||||
return info.metadata.nodeDescription ?? info.metadata.description;
|
return info.metadata.nodeDescription ?? info.metadata.description;
|
||||||
|
|
|
@ -61,7 +61,7 @@ export class FileInfoService {
|
||||||
const warnings = [] as string[];
|
const warnings = [] as string[];
|
||||||
|
|
||||||
const size = await this.getFileSize(path);
|
const size = await this.getFileSize(path);
|
||||||
const md5 = await this.#calcHash(path);
|
const md5 = await this.calcHash(path);
|
||||||
|
|
||||||
let type = await this.detectType(path);
|
let type = await this.detectType(path);
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export class FileInfoService {
|
||||||
let orientation: number | undefined;
|
let orientation: number | undefined;
|
||||||
|
|
||||||
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
|
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
|
||||||
const imageSize = await this.#detectImageSize(path).catch(e => {
|
const imageSize = await this.detectImageSize(path).catch(e => {
|
||||||
warnings.push(`detectImageSize failed: ${e}`);
|
warnings.push(`detectImageSize failed: ${e}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
@ -98,7 +98,7 @@ export class FileInfoService {
|
||||||
let blurhash: string | undefined;
|
let blurhash: string | undefined;
|
||||||
|
|
||||||
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) {
|
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) {
|
||||||
blurhash = await this.#getBlurhash(path).catch(e => {
|
blurhash = await this.getBlurhash(path).catch(e => {
|
||||||
warnings.push(`getBlurhash failed: ${e}`);
|
warnings.push(`getBlurhash failed: ${e}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
@ -108,7 +108,7 @@ export class FileInfoService {
|
||||||
let porn = false;
|
let porn = false;
|
||||||
|
|
||||||
if (!opts.skipSensitiveDetection) {
|
if (!opts.skipSensitiveDetection) {
|
||||||
await this.#detectSensitivity(
|
await this.detectSensitivity(
|
||||||
path,
|
path,
|
||||||
type.mime,
|
type.mime,
|
||||||
opts.sensitiveThreshold ?? 0.5,
|
opts.sensitiveThreshold ?? 0.5,
|
||||||
|
@ -135,7 +135,7 @@ export class FileInfoService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async #detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> {
|
private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> {
|
||||||
let sensitive = false;
|
let sensitive = false;
|
||||||
let porn = false;
|
let porn = false;
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ export class FileInfoService {
|
||||||
let frameIndex = 0;
|
let frameIndex = 0;
|
||||||
let targetIndex = 0;
|
let targetIndex = 0;
|
||||||
let nextIndex = 1;
|
let nextIndex = 1;
|
||||||
for await (const path of this.#asyncIterateFrames(outDir, command)) {
|
for await (const path of this.asyncIterateFrames(outDir, command)) {
|
||||||
try {
|
try {
|
||||||
const index = frameIndex++;
|
const index = frameIndex++;
|
||||||
if (index !== targetIndex) {
|
if (index !== targetIndex) {
|
||||||
|
@ -230,7 +230,7 @@ export class FileInfoService {
|
||||||
return [sensitive, porn];
|
return [sensitive, porn];
|
||||||
}
|
}
|
||||||
|
|
||||||
async *#asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator<string, void> {
|
private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator<string, void> {
|
||||||
const watcher = new FSWatcher({
|
const watcher = new FSWatcher({
|
||||||
cwd,
|
cwd,
|
||||||
disableGlobbing: true,
|
disableGlobbing: true,
|
||||||
|
@ -245,7 +245,7 @@ export class FileInfoService {
|
||||||
const current = `${i}.png`;
|
const current = `${i}.png`;
|
||||||
const next = `${i + 1}.png`;
|
const next = `${i + 1}.png`;
|
||||||
const framePath = join(cwd, current);
|
const framePath = join(cwd, current);
|
||||||
if (await this.#exists(join(cwd, next))) {
|
if (await this.exists(join(cwd, next))) {
|
||||||
yield framePath;
|
yield framePath;
|
||||||
} else if (!finished) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
} else if (!finished) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
||||||
watcher.add(next);
|
watcher.add(next);
|
||||||
|
@ -261,7 +261,7 @@ export class FileInfoService {
|
||||||
command.once('error', reject);
|
command.once('error', reject);
|
||||||
});
|
});
|
||||||
yield framePath;
|
yield framePath;
|
||||||
} else if (await this.#exists(framePath)) {
|
} else if (await this.exists(framePath)) {
|
||||||
yield framePath;
|
yield framePath;
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
@ -269,7 +269,7 @@ export class FileInfoService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#exists(path: string): Promise<boolean> {
|
private exists(path: string): Promise<boolean> {
|
||||||
return fs.promises.access(path).then(() => true, () => false);
|
return fs.promises.access(path).then(() => true, () => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ export class FileInfoService {
|
||||||
/**
|
/**
|
||||||
* Calculate MD5 hash
|
* Calculate MD5 hash
|
||||||
*/
|
*/
|
||||||
async #calcHash(path: string): Promise<string> {
|
private async calcHash(path: string): Promise<string> {
|
||||||
const hash = crypto.createHash('md5').setEncoding('hex');
|
const hash = crypto.createHash('md5').setEncoding('hex');
|
||||||
await pipeline(fs.createReadStream(path), hash);
|
await pipeline(fs.createReadStream(path), hash);
|
||||||
return hash.read();
|
return hash.read();
|
||||||
|
@ -342,7 +342,7 @@ export class FileInfoService {
|
||||||
/**
|
/**
|
||||||
* Detect dimensions of image
|
* Detect dimensions of image
|
||||||
*/
|
*/
|
||||||
async #detectImageSize(path: string): Promise<{
|
private async detectImageSize(path: string): Promise<{
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
wUnits: string;
|
wUnits: string;
|
||||||
|
@ -358,7 +358,7 @@ export class FileInfoService {
|
||||||
/**
|
/**
|
||||||
* Calculate average color of image
|
* Calculate average color of image
|
||||||
*/
|
*/
|
||||||
#getBlurhash(path: string): Promise<string> {
|
private getBlurhash(path: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
sharp(path)
|
sharp(path)
|
||||||
.raw()
|
.raw()
|
||||||
|
|
|
@ -15,12 +15,12 @@ export class HttpRequestService {
|
||||||
/**
|
/**
|
||||||
* Get http non-proxy agent
|
* Get http non-proxy agent
|
||||||
*/
|
*/
|
||||||
#http: http.Agent;
|
private http: http.Agent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get https non-proxy agent
|
* Get https non-proxy agent
|
||||||
*/
|
*/
|
||||||
#https: https.Agent;
|
private https: https.Agent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get http proxy or non-proxy agent
|
* Get http proxy or non-proxy agent
|
||||||
|
@ -42,13 +42,13 @@ export class HttpRequestService {
|
||||||
lookup: false, // nativeのdns.lookupにfallbackしない
|
lookup: false, // nativeのdns.lookupにfallbackしない
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#http = new http.Agent({
|
this.http = new http.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
lookup: cache.lookup,
|
lookup: cache.lookup,
|
||||||
} as http.AgentOptions);
|
} as http.AgentOptions);
|
||||||
|
|
||||||
this.#https = new https.Agent({
|
this.https = new https.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
lookup: cache.lookup,
|
lookup: cache.lookup,
|
||||||
|
@ -65,7 +65,7 @@ export class HttpRequestService {
|
||||||
scheduling: 'lifo',
|
scheduling: 'lifo',
|
||||||
proxy: config.proxy,
|
proxy: config.proxy,
|
||||||
})
|
})
|
||||||
: this.#http;
|
: this.http;
|
||||||
|
|
||||||
this.httpsAgent = config.proxy
|
this.httpsAgent = config.proxy
|
||||||
? new HttpsProxyAgent({
|
? new HttpsProxyAgent({
|
||||||
|
@ -76,7 +76,7 @@ export class HttpRequestService {
|
||||||
scheduling: 'lifo',
|
scheduling: 'lifo',
|
||||||
proxy: config.proxy,
|
proxy: config.proxy,
|
||||||
})
|
})
|
||||||
: this.#https;
|
: this.https;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +86,7 @@ export class HttpRequestService {
|
||||||
*/
|
*/
|
||||||
public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
|
public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
|
||||||
if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) {
|
if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) {
|
||||||
return url.protocol === 'http:' ? this.#http : this.#https;
|
return url.protocol === 'http:' ? this.http : this.https;
|
||||||
} else {
|
} else {
|
||||||
return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent;
|
return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,19 @@ import { genObjectId } from '@/misc/id/object-id.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IdService {
|
export class IdService {
|
||||||
#metohd: string;
|
private metohd: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
) {
|
) {
|
||||||
this.#metohd = config.id.toLowerCase();
|
this.metohd = config.id.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public genId(date?: Date): string {
|
public genId(date?: Date): string {
|
||||||
if (!date || (date > new Date())) date = new Date();
|
if (!date || (date > new Date())) date = new Date();
|
||||||
|
|
||||||
switch (this.#metohd) {
|
switch (this.metohd) {
|
||||||
case 'aid': return genAid(date);
|
case 'aid': return genAid(date);
|
||||||
case 'meid': return genMeid(date);
|
case 'meid': return genMeid(date);
|
||||||
case 'meidg': return genMeidg(date);
|
case 'meidg': return genMeidg(date);
|
||||||
|
|
|
@ -10,7 +10,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InstanceActorService {
|
export class InstanceActorService {
|
||||||
#cache: Cache<ILocalUser>;
|
private cache: Cache<ILocalUser>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
|
@ -18,11 +18,11 @@ export class InstanceActorService {
|
||||||
|
|
||||||
private createSystemUserService: CreateSystemUserService,
|
private createSystemUserService: CreateSystemUserService,
|
||||||
) {
|
) {
|
||||||
this.#cache = new Cache<ILocalUser>(Infinity);
|
this.cache = new Cache<ILocalUser>(Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getInstanceActor(): Promise<ILocalUser> {
|
public async getInstanceActor(): Promise<ILocalUser> {
|
||||||
const cached = this.#cache.get(null);
|
const cached = this.cache.get(null);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
|
@ -31,11 +31,11 @@ export class InstanceActorService {
|
||||||
}) as ILocalUser | undefined;
|
}) as ILocalUser | undefined;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
this.#cache.set(null, user);
|
this.cache.set(null, user);
|
||||||
return user;
|
return user;
|
||||||
} else {
|
} else {
|
||||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as ILocalUser;
|
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as ILocalUser;
|
||||||
this.#cache.set(null, created);
|
this.cache.set(null, created);
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ import Logger from '@/logger.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoggerService {
|
export class LoggerService {
|
||||||
#syslogClient;
|
private syslogClient;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
) {
|
) {
|
||||||
if (this.config.syslog) {
|
if (this.config.syslog) {
|
||||||
this.#syslogClient = new SyslogPro.RFC5424({
|
this.syslogClient = new SyslogPro.RFC5424({
|
||||||
applacationName: 'Misskey',
|
applacationName: 'Misskey',
|
||||||
timestamp: true,
|
timestamp: true,
|
||||||
encludeStructuredData: true,
|
encludeStructuredData: true,
|
||||||
|
@ -28,6 +28,6 @@ export class LoggerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLogger(domain: string, color?: string | undefined, store?: boolean) {
|
public getLogger(domain: string, color?: string | undefined, store?: boolean) {
|
||||||
return new Logger(domain, color, store, this.#syslogClient);
|
return new Logger(domain, color, store, this.syslogClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,10 +142,10 @@ export class MessagingService {
|
||||||
|
|
||||||
public async deleteMessage(message: MessagingMessage) {
|
public async deleteMessage(message: MessagingMessage) {
|
||||||
await this.messagingMessagesRepository.delete(message.id);
|
await this.messagingMessagesRepository.delete(message.id);
|
||||||
this.#postDeleteMessage(message);
|
this.postDeleteMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #postDeleteMessage(message: MessagingMessage) {
|
private async postDeleteMessage(message: MessagingMessage) {
|
||||||
if (message.recipientId) {
|
if (message.recipientId) {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: message.userId });
|
const user = await this.usersRepository.findOneByOrFail({ id: message.userId });
|
||||||
const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId });
|
const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId });
|
||||||
|
|
|
@ -7,24 +7,24 @@ import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetaService implements OnApplicationShutdown {
|
export class MetaService implements OnApplicationShutdown {
|
||||||
#cache: Meta | undefined;
|
private cache: Meta | undefined;
|
||||||
#intervalId: NodeJS.Timer;
|
private intervalId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
) {
|
) {
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
this.#intervalId = setInterval(() => {
|
this.intervalId = setInterval(() => {
|
||||||
this.fetch(true).then(meta => {
|
this.fetch(true).then(meta => {
|
||||||
this.#cache = meta;
|
this.cache = meta;
|
||||||
});
|
});
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(noCache = false): Promise<Meta> {
|
async fetch(noCache = false): Promise<Meta> {
|
||||||
if (!noCache && this.#cache) return this.#cache;
|
if (!noCache && this.cache) return this.cache;
|
||||||
|
|
||||||
return await this.db.transaction(async transactionalEntityManager => {
|
return await this.db.transaction(async transactionalEntityManager => {
|
||||||
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
|
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
|
||||||
|
@ -37,7 +37,7 @@ export class MetaService implements OnApplicationShutdown {
|
||||||
const meta = metas[0];
|
const meta = metas[0];
|
||||||
|
|
||||||
if (meta) {
|
if (meta) {
|
||||||
this.#cache = meta;
|
this.cache = meta;
|
||||||
return meta;
|
return meta;
|
||||||
} else {
|
} else {
|
||||||
// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
|
// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
|
||||||
|
@ -51,13 +51,13 @@ export class MetaService implements OnApplicationShutdown {
|
||||||
)
|
)
|
||||||
.then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]));
|
.then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]));
|
||||||
|
|
||||||
this.#cache = saved;
|
this.cache = saved;
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public onApplicationShutdown(signal?: string | undefined) {
|
||||||
clearInterval(this.#intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,7 @@ export class NoteCreateService {
|
||||||
|
|
||||||
emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens);
|
emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens);
|
||||||
|
|
||||||
mentionedUsers = data.apMentions ?? await this.#extractMentionedUsers(user, combinedTokens);
|
mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32);
|
tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32);
|
||||||
|
@ -300,14 +300,14 @@ export class NoteCreateService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = await this.#insertNote(user, data, tags, emojis, mentionedUsers);
|
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
|
||||||
|
|
||||||
setImmediate(() => this.#postNoteCreated(note, user, data, silent, tags!, mentionedUsers!));
|
setImmediate(() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!));
|
||||||
|
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) {
|
private async insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) {
|
||||||
const insert = new Note({
|
const insert = new Note({
|
||||||
id: this.idService.genId(data.createdAt!),
|
id: this.idService.genId(data.createdAt!),
|
||||||
createdAt: data.createdAt!,
|
createdAt: data.createdAt!,
|
||||||
|
@ -403,7 +403,7 @@ export class NoteCreateService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #postNoteCreated(note: Note, user: {
|
private async postNoteCreated(note: Note, user: {
|
||||||
id: User['id'];
|
id: User['id'];
|
||||||
username: User['username'];
|
username: User['username'];
|
||||||
host: User['host'];
|
host: User['host'];
|
||||||
|
@ -428,7 +428,7 @@ export class NoteCreateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment notes count (user)
|
// Increment notes count (user)
|
||||||
this.#incNotesCountOfUser(user);
|
this.incNotesCountOfUser(user);
|
||||||
|
|
||||||
// Word mute
|
// Word mute
|
||||||
mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({
|
mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({
|
||||||
|
@ -473,12 +473,12 @@ export class NoteCreateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
this.#saveReply(data.reply, note);
|
this.saveReply(data.reply, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
||||||
if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
|
if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
|
||||||
this.#incRenoteCount(data.renote);
|
this.incRenoteCount(data.renote);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.poll && data.poll.expiresAt) {
|
if (data.poll && data.poll.expiresAt) {
|
||||||
|
@ -536,7 +536,7 @@ export class NoteCreateService {
|
||||||
const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note);
|
const nm = new NotificationManager(this.mutingsRepository, this.createNotificationService, user, note);
|
||||||
const nmRelatedPromises = [];
|
const nmRelatedPromises = [];
|
||||||
|
|
||||||
await this.#createMentionedEvents(mentionedUsers, note, nm);
|
await this.createMentionedEvents(mentionedUsers, note, nm);
|
||||||
|
|
||||||
// If has in reply to note
|
// If has in reply to note
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
|
@ -590,7 +590,7 @@ export class NoteCreateService {
|
||||||
//#region AP deliver
|
//#region AP deliver
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const noteActivity = await this.#renderNoteOrRenoteActivity(data, note);
|
const noteActivity = await this.renderNoteOrRenoteActivity(data, note);
|
||||||
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
|
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
|
||||||
|
|
||||||
// メンションされたリモートユーザーに配送
|
// メンションされたリモートユーザーに配送
|
||||||
|
@ -644,10 +644,10 @@ export class NoteCreateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register to search database
|
// Register to search database
|
||||||
this.#index(note);
|
this.index(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
#incRenoteCount(renote: Note) {
|
private incRenoteCount(renote: Note) {
|
||||||
this.notesRepository.createQueryBuilder().update()
|
this.notesRepository.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
renoteCount: () => '"renoteCount" + 1',
|
renoteCount: () => '"renoteCount" + 1',
|
||||||
|
@ -657,7 +657,7 @@ export class NoteCreateService {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async #createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) {
|
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) {
|
||||||
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
||||||
const threadMuted = await this.noteThreadMutingsRepository.findOneBy({
|
const threadMuted = await this.noteThreadMutingsRepository.findOneBy({
|
||||||
userId: u.id,
|
userId: u.id,
|
||||||
|
@ -686,11 +686,11 @@ export class NoteCreateService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#saveReply(reply: Note, note: Note) {
|
private saveReply(reply: Note, note: Note) {
|
||||||
this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1);
|
this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #renderNoteOrRenoteActivity(data: Option, note: Note) {
|
private async renderNoteOrRenoteActivity(data: Option, note: Note) {
|
||||||
if (data.localOnly) return null;
|
if (data.localOnly) return null;
|
||||||
|
|
||||||
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
|
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
|
||||||
|
@ -700,7 +700,7 @@ export class NoteCreateService {
|
||||||
return this.apRendererService.renderActivity(content);
|
return this.apRendererService.renderActivity(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
#index(note: Note) {
|
private index(note: Note) {
|
||||||
if (note.text == null || this.config.elasticsearch == null) return;
|
if (note.text == null || this.config.elasticsearch == null) return;
|
||||||
/*
|
/*
|
||||||
es!.index({
|
es!.index({
|
||||||
|
@ -714,7 +714,7 @@ export class NoteCreateService {
|
||||||
});*/
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
#incNotesCountOfUser(user: { id: User['id']; }) {
|
private incNotesCountOfUser(user: { id: User['id']; }) {
|
||||||
this.usersRepository.createQueryBuilder().update()
|
this.usersRepository.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
|
@ -724,7 +724,7 @@ export class NoteCreateService {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async #extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise<User[]> {
|
private async extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise<User[]> {
|
||||||
if (tokens == null) return [];
|
if (tokens == null) return [];
|
||||||
|
|
||||||
const mentions = extractMentions(tokens);
|
const mentions = extractMentions(tokens);
|
||||||
|
|
|
@ -79,16 +79,16 @@ export class NoteDeleteService {
|
||||||
? this.apRendererService.renderUndo(this.apRendererService.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note), user)
|
? this.apRendererService.renderUndo(this.apRendererService.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note), user)
|
||||||
: this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user));
|
: this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user));
|
||||||
|
|
||||||
this.#deliverToConcerned(user, note, content);
|
this.deliverToConcerned(user, note, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// also deliever delete activity to cascaded notes
|
// also deliever delete activity to cascaded notes
|
||||||
const cascadingNotes = (await this.#findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes
|
const cascadingNotes = (await this.findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes
|
||||||
for (const cascadingNote of cascadingNotes) {
|
for (const cascadingNote of cascadingNotes) {
|
||||||
if (!cascadingNote.user) continue;
|
if (!cascadingNote.user) continue;
|
||||||
if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue;
|
if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue;
|
||||||
const content = this.apRendererService.renderActivity(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
|
const content = this.apRendererService.renderActivity(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
|
||||||
this.#deliverToConcerned(cascadingNote.user, cascadingNote, content);
|
this.deliverToConcerned(cascadingNote.user, cascadingNote, content);
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ export class NoteDeleteService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #findCascadingNotes(note: Note) {
|
private async findCascadingNotes(note: Note) {
|
||||||
const cascadingNotes: Note[] = [];
|
const cascadingNotes: Note[] = [];
|
||||||
|
|
||||||
const recursive = async (noteId: string) => {
|
const recursive = async (noteId: string) => {
|
||||||
|
@ -132,7 +132,7 @@ export class NoteDeleteService {
|
||||||
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
|
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getMentionedRemoteUsers(note: Note) {
|
private async getMentionedRemoteUsers(note: Note) {
|
||||||
const where = [] as any[];
|
const where = [] as any[];
|
||||||
|
|
||||||
// mention / reply / dm
|
// mention / reply / dm
|
||||||
|
@ -157,10 +157,10 @@ export class NoteDeleteService {
|
||||||
}) as IRemoteUser[];
|
}) as IRemoteUser[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async #deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) {
|
private async deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) {
|
||||||
this.apDeliverManagerService.deliverToFollowers(user, content);
|
this.apDeliverManagerService.deliverToFollowers(user, content);
|
||||||
this.relayService.deliverToRelays(user, content);
|
this.relayService.deliverToRelays(user, content);
|
||||||
const remoteUsers = await this.#getMentionedRemoteUsers(note);
|
const remoteUsers = await this.getMentionedRemoteUsers(note);
|
||||||
for (const remoteUser of remoteUsers) {
|
for (const remoteUser of remoteUsers) {
|
||||||
this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
|
this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ export class NotificationService {
|
||||||
|
|
||||||
if (result.affected === 0) return;
|
if (result.affected === 0) return;
|
||||||
|
|
||||||
if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.#postReadAllNotifications(userId);
|
if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.postReadAllNotifications(userId);
|
||||||
else return this.#postReadNotifications(userId, notificationIds);
|
else return this.postReadNotifications(userId, notificationIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async readNotificationByQuery(
|
public async readNotificationByQuery(
|
||||||
|
@ -55,12 +55,12 @@ export class NotificationService {
|
||||||
return this.readNotification(userId, notificationIds);
|
return this.readNotification(userId, notificationIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
#postReadAllNotifications(userId: User['id']) {
|
private postReadAllNotifications(userId: User['id']) {
|
||||||
this.globalEventService.publishMainStream(userId, 'readAllNotifications');
|
this.globalEventService.publishMainStream(userId, 'readAllNotifications');
|
||||||
return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
|
return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
#postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
|
private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
|
||||||
this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds);
|
this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds);
|
||||||
return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
|
return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const ACTOR_USERNAME = 'relay.actor' as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RelayService {
|
export class RelayService {
|
||||||
#relaysCache: Cache<Relay[]>;
|
private relaysCache: Cache<Relay[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
|
@ -28,10 +28,10 @@ export class RelayService {
|
||||||
private createSystemUserService: CreateSystemUserService,
|
private createSystemUserService: CreateSystemUserService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
) {
|
) {
|
||||||
this.#relaysCache = new Cache<Relay[]>(1000 * 60 * 10);
|
this.relaysCache = new Cache<Relay[]>(1000 * 60 * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getRelayActor(): Promise<ILocalUser> {
|
private async getRelayActor(): Promise<ILocalUser> {
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
username: ACTOR_USERNAME,
|
username: ACTOR_USERNAME,
|
||||||
|
@ -50,7 +50,7 @@ export class RelayService {
|
||||||
status: 'requesting',
|
status: 'requesting',
|
||||||
}).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
const relayActor = await this.#getRelayActor();
|
const relayActor = await this.getRelayActor();
|
||||||
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
const activity = this.apRendererService.renderActivity(follow);
|
const activity = this.apRendererService.renderActivity(follow);
|
||||||
this.queueService.deliver(relayActor, activity, relay.inbox);
|
this.queueService.deliver(relayActor, activity, relay.inbox);
|
||||||
|
@ -67,7 +67,7 @@ export class RelayService {
|
||||||
throw new Error('relay not found');
|
throw new Error('relay not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const relayActor = await this.#getRelayActor();
|
const relayActor = await this.getRelayActor();
|
||||||
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||||
const undo = this.apRendererService.renderUndo(follow, relayActor);
|
const undo = this.apRendererService.renderUndo(follow, relayActor);
|
||||||
const activity = this.apRendererService.renderActivity(undo);
|
const activity = this.apRendererService.renderActivity(undo);
|
||||||
|
@ -100,7 +100,7 @@ export class RelayService {
|
||||||
public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
|
public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
|
||||||
if (activity == null) return;
|
if (activity == null) return;
|
||||||
|
|
||||||
const relays = await this.#relaysCache.fetch(null, () => this.relaysRepository.findBy({
|
const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({
|
||||||
status: 'accepted',
|
status: 'accepted',
|
||||||
}));
|
}));
|
||||||
if (relays.length === 0) return;
|
if (relays.length === 0) return;
|
||||||
|
|
|
@ -44,11 +44,11 @@ export class UserBlockingService {
|
||||||
|
|
||||||
public async block(blocker: User, blockee: User) {
|
public async block(blocker: User, blockee: User) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.#cancelRequest(blocker, blockee),
|
this.cancelRequest(blocker, blockee),
|
||||||
this.#cancelRequest(blockee, blocker),
|
this.cancelRequest(blockee, blocker),
|
||||||
this.#unFollow(blocker, blockee),
|
this.unFollow(blocker, blockee),
|
||||||
this.#unFollow(blockee, blocker),
|
this.unFollow(blockee, blocker),
|
||||||
this.#removeFromList(blockee, blocker),
|
this.removeFromList(blockee, blocker),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const blocking = {
|
const blocking = {
|
||||||
|
@ -68,7 +68,7 @@ export class UserBlockingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #cancelRequest(follower: User, followee: User) {
|
private async cancelRequest(follower: User, followee: User) {
|
||||||
const request = await this.followRequestsRepository.findOneBy({
|
const request = await this.followRequestsRepository.findOneBy({
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
|
@ -118,7 +118,7 @@ export class UserBlockingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #unFollow(follower: User, followee: User) {
|
private async unFollow(follower: User, followee: User) {
|
||||||
const following = await this.followingsRepository.findOneBy({
|
const following = await this.followingsRepository.findOneBy({
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
@ -159,7 +159,7 @@ export class UserBlockingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #removeFromList(listOwner: User, user: User) {
|
private async removeFromList(listOwner: User, user: User) {
|
||||||
const userLists = await this.userListsRepository.findBy({
|
const userLists = await this.userListsRepository.findBy({
|
||||||
userId: listOwner.id,
|
userId: listOwner.id,
|
||||||
});
|
});
|
||||||
|
|
|
@ -131,7 +131,7 @@ export class UserFollowingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.#insertFollowingDoc(followee, follower);
|
await this.insertFollowingDoc(followee, follower);
|
||||||
|
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
|
const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
|
||||||
|
@ -139,7 +139,7 @@ export class UserFollowingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #insertFollowingDoc(
|
private async insertFollowingDoc(
|
||||||
followee: {
|
followee: {
|
||||||
id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']
|
id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']
|
||||||
},
|
},
|
||||||
|
@ -273,7 +273,7 @@ export class UserFollowingService {
|
||||||
|
|
||||||
await this.followingsRepository.delete(following.id);
|
await this.followingsRepository.delete(following.id);
|
||||||
|
|
||||||
this.#decrementFollowing(follower, followee);
|
this.decrementFollowing(follower, followee);
|
||||||
|
|
||||||
// Publish unfollow event
|
// Publish unfollow event
|
||||||
if (!silent && this.userEntityService.isLocalUser(follower)) {
|
if (!silent && this.userEntityService.isLocalUser(follower)) {
|
||||||
|
@ -304,7 +304,7 @@ export class UserFollowingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #decrementFollowing(
|
private async decrementFollowing(
|
||||||
follower: {id: User['id']; host: User['host']; },
|
follower: {id: User['id']; host: User['host']; },
|
||||||
followee: { id: User['id']; host: User['host']; },
|
followee: { id: User['id']; host: User['host']; },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -445,7 +445,7 @@ export class UserFollowingService {
|
||||||
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
|
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.#insertFollowingDoc(followee, follower);
|
await this.insertFollowingDoc(followee, follower);
|
||||||
|
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee));
|
const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee));
|
||||||
|
@ -477,13 +477,13 @@ export class UserFollowingService {
|
||||||
*/
|
*/
|
||||||
public async rejectFollowRequest(user: Local, follower: Both): Promise<void> {
|
public async rejectFollowRequest(user: Local, follower: Both): Promise<void> {
|
||||||
if (this.userEntityService.isRemoteUser(follower)) {
|
if (this.userEntityService.isRemoteUser(follower)) {
|
||||||
this.#deliverReject(user, follower);
|
this.deliverReject(user, follower);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.#removeFollowRequest(user, follower);
|
await this.removeFollowRequest(user, follower);
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(follower)) {
|
if (this.userEntityService.isLocalUser(follower)) {
|
||||||
this.#publishUnfollow(user, follower);
|
this.publishUnfollow(user, follower);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,13 +492,13 @@ export class UserFollowingService {
|
||||||
*/
|
*/
|
||||||
public async rejectFollow(user: Local, follower: Both): Promise<void> {
|
public async rejectFollow(user: Local, follower: Both): Promise<void> {
|
||||||
if (this.userEntityService.isRemoteUser(follower)) {
|
if (this.userEntityService.isRemoteUser(follower)) {
|
||||||
this.#deliverReject(user, follower);
|
this.deliverReject(user, follower);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.#removeFollow(user, follower);
|
await this.removeFollow(user, follower);
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(follower)) {
|
if (this.userEntityService.isLocalUser(follower)) {
|
||||||
this.#publishUnfollow(user, follower);
|
this.publishUnfollow(user, follower);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,15 +506,15 @@ export class UserFollowingService {
|
||||||
* AP Reject/Follow
|
* AP Reject/Follow
|
||||||
*/
|
*/
|
||||||
public async remoteReject(actor: Remote, follower: Local): Promise<void> {
|
public async remoteReject(actor: Remote, follower: Local): Promise<void> {
|
||||||
await this.#removeFollowRequest(actor, follower);
|
await this.removeFollowRequest(actor, follower);
|
||||||
await this.#removeFollow(actor, follower);
|
await this.removeFollow(actor, follower);
|
||||||
this.#publishUnfollow(actor, follower);
|
this.publishUnfollow(actor, follower);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove follow request record
|
* Remove follow request record
|
||||||
*/
|
*/
|
||||||
async #removeFollowRequest(followee: Both, follower: Both): Promise<void> {
|
private async removeFollowRequest(followee: Both, follower: Both): Promise<void> {
|
||||||
const request = await this.followRequestsRepository.findOneBy({
|
const request = await this.followRequestsRepository.findOneBy({
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
|
@ -528,7 +528,7 @@ export class UserFollowingService {
|
||||||
/**
|
/**
|
||||||
* Remove follow record
|
* Remove follow record
|
||||||
*/
|
*/
|
||||||
async #removeFollow(followee: Both, follower: Both): Promise<void> {
|
private async removeFollow(followee: Both, follower: Both): Promise<void> {
|
||||||
const following = await this.followingsRepository.findOneBy({
|
const following = await this.followingsRepository.findOneBy({
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
|
@ -537,13 +537,13 @@ export class UserFollowingService {
|
||||||
if (!following) return;
|
if (!following) return;
|
||||||
|
|
||||||
await this.followingsRepository.delete(following.id);
|
await this.followingsRepository.delete(following.id);
|
||||||
this.#decrementFollowing(follower, followee);
|
this.decrementFollowing(follower, followee);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deliver Reject to remote
|
* Deliver Reject to remote
|
||||||
*/
|
*/
|
||||||
async #deliverReject(followee: Local, follower: Remote): Promise<void> {
|
private async deliverReject(followee: Local, follower: Remote): Promise<void> {
|
||||||
const request = await this.followRequestsRepository.findOneBy({
|
const request = await this.followRequestsRepository.findOneBy({
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
|
@ -556,7 +556,7 @@ export class UserFollowingService {
|
||||||
/**
|
/**
|
||||||
* Publish unfollow to local
|
* Publish unfollow to local
|
||||||
*/
|
*/
|
||||||
async #publishUnfollow(followee: Both, follower: Local): Promise<void> {
|
private async publishUnfollow(followee: Both, follower: Local): Promise<void> {
|
||||||
const packedFollowee = await this.userEntityService.pack(followee.id, follower, {
|
const packedFollowee = await this.userEntityService.pack(followee.id, follower, {
|
||||||
detail: true,
|
detail: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,16 +7,16 @@ import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserKeypairStoreService {
|
export class UserKeypairStoreService {
|
||||||
#cache: Cache<UserKeypair>;
|
private cache: Cache<UserKeypair>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.userKeypairsRepository)
|
@Inject(DI.userKeypairsRepository)
|
||||||
private userKeypairsRepository: UserKeypairsRepository,
|
private userKeypairsRepository: UserKeypairsRepository,
|
||||||
) {
|
) {
|
||||||
this.#cache = new Cache<UserKeypair>(Infinity);
|
this.cache = new Cache<UserKeypair>(Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserKeypair(userId: User['id']): Promise<UserKeypair> {
|
public async getUserKeypair(userId: User['id']): Promise<UserKeypair> {
|
||||||
return await this.#cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId }));
|
return await this.cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId: userId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebhookService implements OnApplicationShutdown {
|
export class WebhookService implements OnApplicationShutdown {
|
||||||
#webhooksFetched = false;
|
private webhooksFetched = false;
|
||||||
#webhooks: Webhook[] = [];
|
private webhooks: Webhook[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redisSubscriber)
|
@Inject(DI.redisSubscriber)
|
||||||
|
@ -22,14 +22,14 @@ export class WebhookService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getActiveWebhooks() {
|
public async getActiveWebhooks() {
|
||||||
if (!this.#webhooksFetched) {
|
if (!this.webhooksFetched) {
|
||||||
this.#webhooks = await this.webhooksRepository.findBy({
|
this.webhooks = await this.webhooksRepository.findBy({
|
||||||
active: true,
|
active: true,
|
||||||
});
|
});
|
||||||
this.#webhooksFetched = true;
|
this.webhooksFetched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.#webhooks;
|
return this.webhooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessage(_, data) {
|
private async onMessage(_, data) {
|
||||||
|
@ -40,23 +40,23 @@ export class WebhookService implements OnApplicationShutdown {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'webhookCreated':
|
case 'webhookCreated':
|
||||||
if (body.active) {
|
if (body.active) {
|
||||||
this.#webhooks.push(body);
|
this.webhooks.push(body);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'webhookUpdated':
|
case 'webhookUpdated':
|
||||||
if (body.active) {
|
if (body.active) {
|
||||||
const i = this.#webhooks.findIndex(a => a.id === body.id);
|
const i = this.webhooks.findIndex(a => a.id === body.id);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
this.#webhooks[i] = body;
|
this.webhooks[i] = body;
|
||||||
} else {
|
} else {
|
||||||
this.#webhooks.push(body);
|
this.webhooks.push(body);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.#webhooks = this.#webhooks.filter(a => a.id !== body.id);
|
this.webhooks = this.webhooks.filter(a => a.id !== body.id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'webhookDeleted':
|
case 'webhookDeleted':
|
||||||
this.#webhooks = this.#webhooks.filter(a => a.id !== body.id);
|
this.webhooks = this.webhooks.filter(a => a.id !== body.id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -109,7 +109,7 @@ export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatt
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default abstract class Chart<T extends Schema> {
|
export default abstract class Chart<T extends Schema> {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
public schema: T;
|
public schema: T;
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.schema = schema;
|
this.schema = schema;
|
||||||
this.lock = lock;
|
this.lock = lock;
|
||||||
this.#logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
||||||
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour);
|
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour);
|
||||||
|
@ -333,7 +333,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
// 初期ログデータを作成
|
// 初期ログデータを作成
|
||||||
data = this.getNewLog(null);
|
data = this.getNewLog(null);
|
||||||
|
|
||||||
this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`);
|
this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = Chart.dateToTimestamp(current);
|
const date = Chart.dateToTimestamp(current);
|
||||||
|
@ -363,7 +363,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
...columns,
|
...columns,
|
||||||
}).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord<T>;
|
}).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord<T>;
|
||||||
|
|
||||||
this.#logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
|
this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
|
||||||
|
|
||||||
return log;
|
return log;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -382,7 +382,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
|
|
||||||
public async save(): Promise<void> {
|
public async save(): Promise<void> {
|
||||||
if (this.buffer.length === 0) {
|
if (this.buffer.length === 0) {
|
||||||
this.#logger.info(`${this.name}: Write skipped`);
|
this.logger.info(`${this.name}: Write skipped`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +481,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
.execute(),
|
.execute(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.#logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`);
|
this.logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`);
|
||||||
|
|
||||||
// TODO: この一連の処理が始まった後に新たにbufferに入ったものは消さないようにする
|
// TODO: この一連の処理が始まった後に新たにbufferに入ったものは消さないようにする
|
||||||
this.buffer = this.buffer.filter(q => q.group != null && (q.group !== logHour.group));
|
this.buffer = this.buffer.filter(q => q.group != null && (q.group !== logHour.group));
|
||||||
|
|
|
@ -68,7 +68,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
this.reactionService = this.moduleRef.get('ReactionService');
|
this.reactionService = this.moduleRef.get('ReactionService');
|
||||||
}
|
}
|
||||||
|
|
||||||
async #hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
|
private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
|
||||||
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
|
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
|
||||||
let hide = false;
|
let hide = false;
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #populatePoll(note: Note, meId: User['id'] | null) {
|
private async populatePoll(note: Note, meId: User['id'] | null) {
|
||||||
const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id });
|
const poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id });
|
||||||
const choices = poll.choices.map(c => ({
|
const choices = poll.choices.map(c => ({
|
||||||
text: c,
|
text: c,
|
||||||
|
@ -166,7 +166,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async #populateMyReaction(note: Note, meId: User['id'], _hint_?: {
|
private async populateMyReaction(note: Note, meId: User['id'], _hint_?: {
|
||||||
myReactions: Map<Note['id'], NoteReaction | null>;
|
myReactions: Map<Note['id'], NoteReaction | null>;
|
||||||
}) {
|
}) {
|
||||||
if (_hint_?.myReactions) {
|
if (_hint_?.myReactions) {
|
||||||
|
@ -319,10 +319,10 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
_hint_: options?._hint_,
|
_hint_: options?._hint_,
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
|
|
||||||
poll: note.hasPoll ? this.#populatePoll(note, meId) : undefined,
|
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
||||||
|
|
||||||
...(meId ? {
|
...(meId ? {
|
||||||
myReaction: this.#populateMyReaction(note, meId, options?._hint_),
|
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
||||||
} : {}),
|
} : {}),
|
||||||
} : {}),
|
} : {}),
|
||||||
});
|
});
|
||||||
|
@ -339,7 +339,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.skipHide) {
|
if (!opts.skipHide) {
|
||||||
await this.#hideNote(packed, meId);
|
await this.hideNote(packed, meId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return packed;
|
return packed;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
private pageEntityService: PageEntityService;
|
private pageEntityService: PageEntityService;
|
||||||
private customEmojiService: CustomEmojiService;
|
private customEmojiService: CustomEmojiService;
|
||||||
private antennaService: AntennaService;
|
private antennaService: AntennaService;
|
||||||
#userInstanceCache: Cache<Instance | null>;
|
private userInstanceCache: Cache<Instance | null>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
@ -119,7 +119,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
//private customEmojiService: CustomEmojiService,
|
//private customEmojiService: CustomEmojiService,
|
||||||
//private antennaService: AntennaService,
|
//private antennaService: AntennaService,
|
||||||
) {
|
) {
|
||||||
this.#userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
|
this.userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
|
@ -384,7 +384,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
isModerator: user.isModerator ?? falsy,
|
isModerator: user.isModerator ?? falsy,
|
||||||
isBot: user.isBot ?? falsy,
|
isBot: user.isBot ?? falsy,
|
||||||
isCat: user.isCat ?? falsy,
|
isCat: user.isCat ?? falsy,
|
||||||
instance: user.host ? this.#userInstanceCache.fetch(user.host,
|
instance: user.host ? this.userInstanceCache.fetch(user.host,
|
||||||
() => this.instancesRepository.findOneBy({ host: user.host! }),
|
() => this.instancesRepository.findOneBy({ host: user.host! }),
|
||||||
v => v != null,
|
v => v != null,
|
||||||
).then(instance => instance ? {
|
).then(instance => instance ? {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { ApPersonService } from './activitypub/models/ApPersonService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResolveUserService {
|
export class ResolveUserService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -28,14 +28,14 @@ export class ResolveUserService {
|
||||||
private remoteLoggerService: RemoteLoggerService,
|
private remoteLoggerService: RemoteLoggerService,
|
||||||
private apPersonService: ApPersonService,
|
private apPersonService: ApPersonService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.remoteLoggerService.logger.createSubLogger('resolve-user');
|
this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resolveUser(username: string, host: string | null): Promise<User> {
|
public async resolveUser(username: string, host: string | null): Promise<User> {
|
||||||
const usernameLower = username.toLowerCase();
|
const usernameLower = username.toLowerCase();
|
||||||
|
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
this.#logger.info(`return local user: ${usernameLower}`);
|
this.logger.info(`return local user: ${usernameLower}`);
|
||||||
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
|
@ -48,7 +48,7 @@ export class ResolveUserService {
|
||||||
host = this.utilityService.toPuny(host);
|
host = this.utilityService.toPuny(host);
|
||||||
|
|
||||||
if (this.config.host === host) {
|
if (this.config.host === host) {
|
||||||
this.#logger.info(`return local user: ${usernameLower}`);
|
this.logger.info(`return local user: ${usernameLower}`);
|
||||||
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
|
@ -63,9 +63,9 @@ export class ResolveUserService {
|
||||||
const acctLower = `${usernameLower}@${host}`;
|
const acctLower = `${usernameLower}@${host}`;
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
const self = await this.#resolveSelf(acctLower);
|
const self = await this.resolveSelf(acctLower);
|
||||||
|
|
||||||
this.#logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
|
this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
|
||||||
return await this.apPersonService.createPerson(self.href);
|
return await this.apPersonService.createPerson(self.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +76,13 @@ export class ResolveUserService {
|
||||||
lastFetchedAt: new Date(),
|
lastFetchedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#logger.info(`try resync: ${acctLower}`);
|
this.logger.info(`try resync: ${acctLower}`);
|
||||||
const self = await this.#resolveSelf(acctLower);
|
const self = await this.resolveSelf(acctLower);
|
||||||
|
|
||||||
if (user.uri !== self.href) {
|
if (user.uri !== self.href) {
|
||||||
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
|
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
|
||||||
this.#logger.info(`uri missmatch: ${acctLower}`);
|
this.logger.info(`uri missmatch: ${acctLower}`);
|
||||||
this.#logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
|
this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
|
||||||
|
|
||||||
// validate uri
|
// validate uri
|
||||||
const uri = new URL(self.href);
|
const uri = new URL(self.href);
|
||||||
|
@ -97,12 +97,12 @@ export class ResolveUserService {
|
||||||
uri: self.href,
|
uri: self.href,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.#logger.info(`uri is fine: ${acctLower}`);
|
this.logger.info(`uri is fine: ${acctLower}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.apPersonService.updatePerson(self.href);
|
await this.apPersonService.updatePerson(self.href);
|
||||||
|
|
||||||
this.#logger.info(`return resynced remote user: ${acctLower}`);
|
this.logger.info(`return resynced remote user: ${acctLower}`);
|
||||||
return await this.usersRepository.findOneBy({ uri: self.href }).then(u => {
|
return await this.usersRepository.findOneBy({ uri: self.href }).then(u => {
|
||||||
if (u == null) {
|
if (u == null) {
|
||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
|
@ -112,19 +112,19 @@ export class ResolveUserService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.info(`return existing remote user: ${acctLower}`);
|
this.logger.info(`return existing remote user: ${acctLower}`);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #resolveSelf(acctLower: string) {
|
private async resolveSelf(acctLower: string) {
|
||||||
this.#logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
||||||
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
|
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
|
||||||
this.#logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`);
|
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`);
|
||||||
throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`);
|
throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`);
|
||||||
});
|
});
|
||||||
const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self');
|
const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self');
|
||||||
if (!self) {
|
if (!self) {
|
||||||
this.#logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
|
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
|
||||||
throw new Error('self link not found');
|
throw new Error('self link not found');
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
|
|
|
@ -26,12 +26,12 @@ export class WebfingerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async webfinger(query: string): Promise<IWebFinger> {
|
public async webfinger(query: string): Promise<IWebFinger> {
|
||||||
const url = this.#genUrl(query);
|
const url = this.genUrl(query);
|
||||||
|
|
||||||
return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger;
|
return await this.httpRequestService.getJson(url, 'application/jrd+json, application/json') as IWebFinger;
|
||||||
}
|
}
|
||||||
|
|
||||||
#genUrl(query: string): string {
|
private genUrl(query: string): string {
|
||||||
if (query.match(/^https?:\/\//)) {
|
if (query.match(/^https?:\/\//)) {
|
||||||
const u = new URL(query);
|
const u = new URL(query);
|
||||||
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
|
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
|
||||||
|
|
|
@ -25,8 +25,8 @@ export class ApAudienceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
|
public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
|
||||||
const toGroups = this.#groupingAudience(getApIds(to), actor);
|
const toGroups = this.groupingAudience(getApIds(to), actor);
|
||||||
const ccGroups = this.#groupingAudience(getApIds(cc), actor);
|
const ccGroups = this.groupingAudience(getApIds(cc), actor);
|
||||||
|
|
||||||
const others = unique(concat([toGroups.other, ccGroups.other]));
|
const others = unique(concat([toGroups.other, ccGroups.other]));
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export class ApAudienceService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#groupingAudience(ids: string[], actor: CacheableRemoteUser) {
|
private groupingAudience(ids: string[], actor: CacheableRemoteUser) {
|
||||||
const groups = {
|
const groups = {
|
||||||
public: [] as string[],
|
public: [] as string[],
|
||||||
followers: [] as string[],
|
followers: [] as string[],
|
||||||
|
@ -74,9 +74,9 @@ export class ApAudienceService {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
if (this.#isPublic(id)) {
|
if (this.isPublic(id)) {
|
||||||
groups.public.push(id);
|
groups.public.push(id);
|
||||||
} else if (this.#isFollowers(id, actor)) {
|
} else if (this.isFollowers(id, actor)) {
|
||||||
groups.followers.push(id);
|
groups.followers.push(id);
|
||||||
} else {
|
} else {
|
||||||
groups.other.push(id);
|
groups.other.push(id);
|
||||||
|
@ -88,7 +88,7 @@ export class ApAudienceService {
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
#isPublic(id: string) {
|
private isPublic(id: string) {
|
||||||
return [
|
return [
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'as#Public',
|
'as#Public',
|
||||||
|
@ -96,7 +96,7 @@ export class ApAudienceService {
|
||||||
].includes(id);
|
].includes(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#isFollowers(id: string, actor: CacheableRemoteUser) {
|
private isFollowers(id: string, actor: CacheableRemoteUser) {
|
||||||
return (
|
return (
|
||||||
id === (actor.followersUri ?? `${actor.uri}/followers`)
|
id === (actor.followersUri ?? `${actor.uri}/followers`)
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,8 +31,8 @@ export type UriParseResult = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApDbResolverService {
|
export class ApDbResolverService {
|
||||||
#publicKeyCache: Cache<UserPublickey | null>;
|
private publicKeyCache: Cache<UserPublickey | null>;
|
||||||
#publicKeyByUserIdCache: Cache<UserPublickey | null>;
|
private publicKeyByUserIdCache: Cache<UserPublickey | null>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -53,8 +53,8 @@ export class ApDbResolverService {
|
||||||
private userCacheService: UserCacheService,
|
private userCacheService: UserCacheService,
|
||||||
private apPersonService: ApPersonService,
|
private apPersonService: ApPersonService,
|
||||||
) {
|
) {
|
||||||
this.#publicKeyCache = new Cache<UserPublickey | null>(Infinity);
|
this.publicKeyCache = new Cache<UserPublickey | null>(Infinity);
|
||||||
this.#publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
|
this.publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseUri(value: string | IObject): UriParseResult {
|
public parseUri(value: string | IObject): UriParseResult {
|
||||||
|
@ -140,7 +140,7 @@ export class ApDbResolverService {
|
||||||
user: CacheableRemoteUser;
|
user: CacheableRemoteUser;
|
||||||
key: UserPublickey;
|
key: UserPublickey;
|
||||||
} | null> {
|
} | null> {
|
||||||
const key = await this.#publicKeyCache.fetch(keyId, async () => {
|
const key = await this.publicKeyCache.fetch(keyId, async () => {
|
||||||
const key = await this.userPublickeysRepository.findOneBy({
|
const key = await this.userPublickeysRepository.findOneBy({
|
||||||
keyId,
|
keyId,
|
||||||
});
|
});
|
||||||
|
@ -169,7 +169,7 @@ export class ApDbResolverService {
|
||||||
|
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
|
|
||||||
const key = await this.#publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null);
|
const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
|
|
@ -34,7 +34,7 @@ import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApInboxService {
|
export class ApInboxService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -81,7 +81,7 @@ export class ApInboxService {
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async performActivity(actor: CacheableRemoteUser, activity: IObject) {
|
public async performActivity(actor: CacheableRemoteUser, activity: IObject) {
|
||||||
|
@ -93,7 +93,7 @@ export class ApInboxService {
|
||||||
await this.performOneActivity(actor, act);
|
await this.performOneActivity(actor, act);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error || typeof err === 'string') {
|
if (err instanceof Error || typeof err === 'string') {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,39 +115,39 @@ export class ApInboxService {
|
||||||
if (actor.isSuspended) return;
|
if (actor.isSuspended) return;
|
||||||
|
|
||||||
if (isCreate(activity)) {
|
if (isCreate(activity)) {
|
||||||
await this.#create(actor, activity);
|
await this.create(actor, activity);
|
||||||
} else if (isDelete(activity)) {
|
} else if (isDelete(activity)) {
|
||||||
await this.#delete(actor, activity);
|
await this.delete(actor, activity);
|
||||||
} else if (isUpdate(activity)) {
|
} else if (isUpdate(activity)) {
|
||||||
await this.#update(actor, activity);
|
await this.update(actor, activity);
|
||||||
} else if (isRead(activity)) {
|
} else if (isRead(activity)) {
|
||||||
await this.#read(actor, activity);
|
await this.read(actor, activity);
|
||||||
} else if (isFollow(activity)) {
|
} else if (isFollow(activity)) {
|
||||||
await this.#follow(actor, activity);
|
await this.follow(actor, activity);
|
||||||
} else if (isAccept(activity)) {
|
} else if (isAccept(activity)) {
|
||||||
await this.#accept(actor, activity);
|
await this.accept(actor, activity);
|
||||||
} else if (isReject(activity)) {
|
} else if (isReject(activity)) {
|
||||||
await this.#reject(actor, activity);
|
await this.reject(actor, activity);
|
||||||
} else if (isAdd(activity)) {
|
} else if (isAdd(activity)) {
|
||||||
await this.#add(actor, activity).catch(err => this.#logger.error(err));
|
await this.add(actor, activity).catch(err => this.logger.error(err));
|
||||||
} else if (isRemove(activity)) {
|
} else if (isRemove(activity)) {
|
||||||
await this.#remove(actor, activity).catch(err => this.#logger.error(err));
|
await this.remove(actor, activity).catch(err => this.logger.error(err));
|
||||||
} else if (isAnnounce(activity)) {
|
} else if (isAnnounce(activity)) {
|
||||||
await this.#announce(actor, activity);
|
await this.announce(actor, activity);
|
||||||
} else if (isLike(activity)) {
|
} else if (isLike(activity)) {
|
||||||
await this.#like(actor, activity);
|
await this.like(actor, activity);
|
||||||
} else if (isUndo(activity)) {
|
} else if (isUndo(activity)) {
|
||||||
await this.#undo(actor, activity);
|
await this.undo(actor, activity);
|
||||||
} else if (isBlock(activity)) {
|
} else if (isBlock(activity)) {
|
||||||
await this.#block(actor, activity);
|
await this.block(actor, activity);
|
||||||
} else if (isFlag(activity)) {
|
} else if (isFlag(activity)) {
|
||||||
await this.#flag(actor, activity);
|
await this.flag(actor, activity);
|
||||||
} else {
|
} else {
|
||||||
this.#logger.warn(`unrecognized activity type: ${(activity as any).type}`);
|
this.logger.warn(`unrecognized activity type: ${(activity as any).type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #follow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
||||||
const followee = await this.apDbResolverService.getUserFromApId(activity.object);
|
const followee = await this.apDbResolverService.getUserFromApId(activity.object);
|
||||||
|
|
||||||
if (followee == null) {
|
if (followee == null) {
|
||||||
|
@ -162,7 +162,7 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #like(actor: CacheableRemoteUser, activity: ILike): Promise<string> {
|
private async like(actor: CacheableRemoteUser, activity: ILike): Promise<string> {
|
||||||
const targetUri = getApId(activity.object);
|
const targetUri = getApId(activity.object);
|
||||||
|
|
||||||
const note = await this.apNoteService.fetchNote(targetUri);
|
const note = await this.apNoteService.fetchNote(targetUri);
|
||||||
|
@ -179,7 +179,7 @@ export class ApInboxService {
|
||||||
}).then(() => 'ok');
|
}).then(() => 'ok');
|
||||||
}
|
}
|
||||||
|
|
||||||
async #read(actor: CacheableRemoteUser, activity: IRead): Promise<string> {
|
private async read(actor: CacheableRemoteUser, activity: IRead): Promise<string> {
|
||||||
const id = await getApId(activity.object);
|
const id = await getApId(activity.object);
|
||||||
|
|
||||||
if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) {
|
if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) {
|
||||||
|
@ -201,24 +201,24 @@ export class ApInboxService {
|
||||||
return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
|
return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #accept(actor: CacheableRemoteUser, activity: IAccept): Promise<string> {
|
private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise<string> {
|
||||||
const uri = activity.id ?? activity;
|
const uri = activity.id ?? activity;
|
||||||
|
|
||||||
this.#logger.info(`Accept: ${uri}`);
|
this.logger.info(`Accept: ${uri}`);
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
const object = await resolver.resolve(activity.object).catch(err => {
|
const object = await resolver.resolve(activity.object).catch(err => {
|
||||||
this.#logger.error(`Resolution failed: ${err}`);
|
this.logger.error(`Resolution failed: ${err}`);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isFollow(object)) return await this.#acceptFollow(actor, object);
|
if (isFollow(object)) return await this.acceptFollow(actor, object);
|
||||||
|
|
||||||
return `skip: Unknown Accept type: ${getApType(object)}`;
|
return `skip: Unknown Accept type: ${getApType(object)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
||||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||||
|
|
||||||
const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
|
const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
|
||||||
|
@ -241,7 +241,7 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #add(actor: CacheableRemoteUser, activity: IAdd): Promise<void> {
|
private async add(actor: CacheableRemoteUser, activity: IAdd): Promise<void> {
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
@ -260,17 +260,17 @@ export class ApInboxService {
|
||||||
throw new Error(`unknown target: ${activity.target}`);
|
throw new Error(`unknown target: ${activity.target}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> {
|
private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
this.#logger.info(`Announce: ${uri}`);
|
this.logger.info(`Announce: ${uri}`);
|
||||||
|
|
||||||
const targetUri = getApId(activity.object);
|
const targetUri = getApId(activity.object);
|
||||||
|
|
||||||
this.#announceNote(actor, activity, targetUri);
|
this.announceNote(actor, activity, targetUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
|
@ -298,18 +298,18 @@ export class ApInboxService {
|
||||||
// 対象が4xxならスキップ
|
// 対象が4xxならスキップ
|
||||||
if (err instanceof StatusError) {
|
if (err instanceof StatusError) {
|
||||||
if (err.isClientError) {
|
if (err.isClientError) {
|
||||||
this.#logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
|
this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`);
|
this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity';
|
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity';
|
||||||
|
|
||||||
this.#logger.info(`Creating the (Re)Note: ${uri}`);
|
this.logger.info(`Creating the (Re)Note: ${uri}`);
|
||||||
|
|
||||||
const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc);
|
const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc);
|
||||||
|
|
||||||
|
@ -325,7 +325,7 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #block(actor: CacheableRemoteUser, activity: IBlock): Promise<string> {
|
private async block(actor: CacheableRemoteUser, activity: IBlock): Promise<string> {
|
||||||
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
|
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
|
||||||
|
|
||||||
const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
|
const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
|
||||||
|
@ -342,10 +342,10 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #create(actor: CacheableRemoteUser, activity: ICreate): Promise<void> {
|
private async create(actor: CacheableRemoteUser, activity: ICreate): Promise<void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
this.#logger.info(`Create: ${uri}`);
|
this.logger.info(`Create: ${uri}`);
|
||||||
|
|
||||||
// copy audiences between activity <=> object.
|
// copy audiences between activity <=> object.
|
||||||
if (typeof activity.object === 'object') {
|
if (typeof activity.object === 'object') {
|
||||||
|
@ -366,18 +366,18 @@ export class ApInboxService {
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
const object = await resolver.resolve(activity.object).catch(e => {
|
const object = await resolver.resolve(activity.object).catch(e => {
|
||||||
this.#logger.error(`Resolution failed: ${e}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPost(object)) {
|
if (isPost(object)) {
|
||||||
this.#createNote(resolver, actor, object, false, activity);
|
this.createNote(resolver, actor, object, false, activity);
|
||||||
} else {
|
} else {
|
||||||
this.#logger.warn(`Unknown type: ${getApType(object)}`);
|
this.logger.warn(`Unknown type: ${getApType(object)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
|
private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
|
||||||
const uri = getApId(note);
|
const uri = getApId(note);
|
||||||
|
|
||||||
if (typeof note === 'object') {
|
if (typeof note === 'object') {
|
||||||
|
@ -411,7 +411,7 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #delete(actor: CacheableRemoteUser, activity: IDelete): Promise<string> {
|
private async delete(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');
|
||||||
}
|
}
|
||||||
|
@ -444,16 +444,16 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validPost.includes(formerType)) {
|
if (validPost.includes(formerType)) {
|
||||||
return await this.#deleteNote(actor, uri);
|
return await this.deleteNote(actor, uri);
|
||||||
} else if (validActor.includes(formerType)) {
|
} else if (validActor.includes(formerType)) {
|
||||||
return await this.#deleteActor(actor, uri);
|
return await this.deleteActor(actor, uri);
|
||||||
} else {
|
} else {
|
||||||
return `Unknown type ${formerType}`;
|
return `Unknown type ${formerType}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> {
|
private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> {
|
||||||
this.#logger.info(`Deleting the Actor: ${uri}`);
|
this.logger.info(`Deleting the Actor: ${uri}`);
|
||||||
|
|
||||||
if (actor.uri !== uri) {
|
if (actor.uri !== uri) {
|
||||||
return `skip: delete actor ${actor.uri} !== ${uri}`;
|
return `skip: delete actor ${actor.uri} !== ${uri}`;
|
||||||
|
@ -461,7 +461,7 @@ export class ApInboxService {
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: actor.id });
|
const user = await this.usersRepository.findOneByOrFail({ id: actor.id });
|
||||||
if (user.isDeleted) {
|
if (user.isDeleted) {
|
||||||
this.#logger.info('skip: already deleted');
|
this.logger.info('skip: already deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = await this.queueService.createDeleteAccountJob(actor);
|
const job = await this.queueService.createDeleteAccountJob(actor);
|
||||||
|
@ -473,8 +473,8 @@ export class ApInboxService {
|
||||||
return `ok: queued ${job.name} ${job.id}`;
|
return `ok: queued ${job.name} ${job.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #deleteNote(actor: CacheableRemoteUser, uri: string): Promise<string> {
|
private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise<string> {
|
||||||
this.#logger.info(`Deleting the Note: ${uri}`);
|
this.logger.info(`Deleting the Note: ${uri}`);
|
||||||
|
|
||||||
const unlock = await this.appLockService.getApLock(uri);
|
const unlock = await this.appLockService.getApLock(uri);
|
||||||
|
|
||||||
|
@ -505,7 +505,7 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #flag(actor: CacheableRemoteUser, activity: IFlag): Promise<string> {
|
private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise<string> {
|
||||||
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
|
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
|
||||||
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
||||||
const uris = getApIds(activity.object);
|
const uris = getApIds(activity.object);
|
||||||
|
@ -529,24 +529,24 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #reject(actor: CacheableRemoteUser, activity: IReject): Promise<string> {
|
private async reject(actor: CacheableRemoteUser, activity: IReject): Promise<string> {
|
||||||
const uri = activity.id ?? activity;
|
const uri = activity.id ?? activity;
|
||||||
|
|
||||||
this.#logger.info(`Reject: ${uri}`);
|
this.logger.info(`Reject: ${uri}`);
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
const object = await resolver.resolve(activity.object).catch(e => {
|
const object = await resolver.resolve(activity.object).catch(e => {
|
||||||
this.#logger.error(`Resolution failed: ${e}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isFollow(object)) return await this.#rejectFollow(actor, object);
|
if (isFollow(object)) return await this.rejectFollow(actor, object);
|
||||||
|
|
||||||
return `skip: Unknown Reject type: ${getApType(object)}`;
|
return `skip: Unknown Reject type: ${getApType(object)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
||||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||||
|
|
||||||
const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
|
const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
|
||||||
|
@ -569,7 +569,7 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #remove(actor: CacheableRemoteUser, activity: IRemove): Promise<void> {
|
private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise<void> {
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
@ -588,32 +588,32 @@ export class ApInboxService {
|
||||||
throw new Error(`unknown target: ${activity.target}`);
|
throw new Error(`unknown target: ${activity.target}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #undo(actor: CacheableRemoteUser, activity: IUndo): Promise<string> {
|
private async undo(actor: CacheableRemoteUser, activity: IUndo): 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
const uri = activity.id ?? activity;
|
const uri = activity.id ?? activity;
|
||||||
|
|
||||||
this.#logger.info(`Undo: ${uri}`);
|
this.logger.info(`Undo: ${uri}`);
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
const object = await resolver.resolve(activity.object).catch(e => {
|
const object = await resolver.resolve(activity.object).catch(e => {
|
||||||
this.#logger.error(`Resolution failed: ${e}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isFollow(object)) return await this.#undoFollow(actor, object);
|
if (isFollow(object)) return await this.undoFollow(actor, object);
|
||||||
if (isBlock(object)) return await this.#undoBlock(actor, object);
|
if (isBlock(object)) return await this.undoBlock(actor, object);
|
||||||
if (isLike(object)) return await this.#undoLike(actor, object);
|
if (isLike(object)) return await this.undoLike(actor, object);
|
||||||
if (isAnnounce(object)) return await this.#undoAnnounce(actor, object);
|
if (isAnnounce(object)) return await this.undoAnnounce(actor, object);
|
||||||
if (isAccept(object)) return await this.#undoAccept(actor, object);
|
if (isAccept(object)) return await this.undoAccept(actor, object);
|
||||||
|
|
||||||
return `skip: unknown object type ${getApType(object)}`;
|
return `skip: unknown object type ${getApType(object)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise<string> {
|
private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise<string> {
|
||||||
const follower = await this.apDbResolverService.getUserFromApId(activity.object);
|
const follower = await this.apDbResolverService.getUserFromApId(activity.object);
|
||||||
if (follower == null) {
|
if (follower == null) {
|
||||||
return 'skip: follower not found';
|
return 'skip: follower not found';
|
||||||
|
@ -632,7 +632,7 @@ export class ApInboxService {
|
||||||
return 'skip: フォローされていない';
|
return 'skip: フォローされていない';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> {
|
private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
const note = await this.notesRepository.findOneBy({
|
const note = await this.notesRepository.findOneBy({
|
||||||
|
@ -646,7 +646,7 @@ export class ApInboxService {
|
||||||
return 'ok: deleted';
|
return 'ok: deleted';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise<string> {
|
private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise<string> {
|
||||||
const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
|
const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
|
||||||
|
|
||||||
if (blockee == null) {
|
if (blockee == null) {
|
||||||
|
@ -661,7 +661,7 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
||||||
const followee = await this.apDbResolverService.getUserFromApId(activity.object);
|
const followee = await this.apDbResolverService.getUserFromApId(activity.object);
|
||||||
if (followee == null) {
|
if (followee == null) {
|
||||||
return 'skip: followee not found';
|
return 'skip: followee not found';
|
||||||
|
@ -694,7 +694,7 @@ export class ApInboxService {
|
||||||
return 'skip: リクエストもフォローもされていない';
|
return 'skip: リクエストもフォローもされていない';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #undoLike(actor: CacheableRemoteUser, activity: ILike): Promise<string> {
|
private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise<string> {
|
||||||
const targetUri = getApId(activity.object);
|
const targetUri = getApId(activity.object);
|
||||||
|
|
||||||
const note = await this.apNoteService.fetchNote(targetUri);
|
const note = await this.apNoteService.fetchNote(targetUri);
|
||||||
|
@ -708,17 +708,17 @@ export class ApInboxService {
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
async #update(actor: CacheableRemoteUser, activity: IUpdate): Promise<string> {
|
private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise<string> {
|
||||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
return 'skip: invalid actor';
|
return 'skip: invalid actor';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.debug('Update');
|
this.logger.debug('Update');
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
const object = await resolver.resolve(activity.object).catch(e => {
|
const object = await resolver.resolve(activity.object).catch(e => {
|
||||||
this.#logger.error(`Resolution failed: ${e}`);
|
this.logger.error(`Resolution failed: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -365,7 +365,7 @@ export class ApRendererService {
|
||||||
text: apText,
|
text: apText,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const emojis = await this.#getEmojis(note.emojis);
|
const emojis = await this.getEmojis(note.emojis);
|
||||||
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
|
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
|
||||||
|
|
||||||
const tag = [
|
const tag = [
|
||||||
|
@ -448,7 +448,7 @@ export class ApRendererService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = await this.#getEmojis(user.emojis);
|
const emojis = await this.getEmojis(user.emojis);
|
||||||
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
|
const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
|
||||||
|
|
||||||
const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));
|
const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));
|
||||||
|
@ -687,7 +687,7 @@ export class ApRendererService {
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getEmojis(names: string[]): Promise<Emoji[]> {
|
private async getEmojis(names: string[]): Promise<Emoji[]> {
|
||||||
if (names == null || names.length === 0) return [];
|
if (names == null || names.length === 0) return [];
|
||||||
|
|
||||||
const emojis = await Promise.all(
|
const emojis = await Promise.all(
|
||||||
|
|
|
@ -36,14 +36,14 @@ export class ApRequestService {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }): Signed {
|
private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }): Signed {
|
||||||
const u = new URL(args.url);
|
const u = new URL(args.url);
|
||||||
const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`;
|
const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`;
|
||||||
|
|
||||||
const request: Request = {
|
const request: Request = {
|
||||||
url: u.href,
|
url: u.href,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: this.#objectAssignWithLcKey({
|
headers: this.objectAssignWithLcKey({
|
||||||
'Date': new Date().toUTCString(),
|
'Date': new Date().toUTCString(),
|
||||||
'Host': u.hostname,
|
'Host': u.hostname,
|
||||||
'Content-Type': 'application/activity+json',
|
'Content-Type': 'application/activity+json',
|
||||||
|
@ -51,7 +51,7 @@ export class ApRequestService {
|
||||||
}, args.additionalHeaders),
|
}, args.additionalHeaders),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']);
|
const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request,
|
request,
|
||||||
|
@ -61,20 +61,20 @@ export class ApRequestService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
|
private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
|
||||||
const u = new URL(args.url);
|
const u = new URL(args.url);
|
||||||
|
|
||||||
const request: Request = {
|
const request: Request = {
|
||||||
url: u.href,
|
url: u.href,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: this.#objectAssignWithLcKey({
|
headers: this.objectAssignWithLcKey({
|
||||||
'Accept': 'application/activity+json, application/ld+json',
|
'Accept': 'application/activity+json, application/ld+json',
|
||||||
'Date': new Date().toUTCString(),
|
'Date': new Date().toUTCString(),
|
||||||
'Host': new URL(args.url).hostname,
|
'Host': new URL(args.url).hostname,
|
||||||
}, args.additionalHeaders),
|
}, args.additionalHeaders),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
|
const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request,
|
request,
|
||||||
|
@ -84,12 +84,12 @@ export class ApRequestService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed {
|
private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed {
|
||||||
const signingString = this.#genSigningString(request, includeHeaders);
|
const signingString = this.genSigningString(request, includeHeaders);
|
||||||
const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64');
|
const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64');
|
||||||
const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`;
|
const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`;
|
||||||
|
|
||||||
request.headers = this.#objectAssignWithLcKey(request.headers, {
|
request.headers = this.objectAssignWithLcKey(request.headers, {
|
||||||
Signature: signatureHeader,
|
Signature: signatureHeader,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,8 +101,8 @@ export class ApRequestService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#genSigningString(request: Request, includeHeaders: string[]): string {
|
private genSigningString(request: Request, includeHeaders: string[]): string {
|
||||||
request.headers = this.#lcObjectKey(request.headers);
|
request.headers = this.lcObjectKey(request.headers);
|
||||||
|
|
||||||
const results: string[] = [];
|
const results: string[] = [];
|
||||||
|
|
||||||
|
@ -117,14 +117,14 @@ export class ApRequestService {
|
||||||
return results.join('\n');
|
return results.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
#lcObjectKey(src: Record<string, string>): Record<string, string> {
|
private lcObjectKey(src: Record<string, string>): Record<string, string> {
|
||||||
const dst: Record<string, string> = {};
|
const dst: Record<string, string> = {};
|
||||||
for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
|
for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
|
||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
#objectAssignWithLcKey(a: Record<string, string>, b: Record<string, string>): Record<string, string> {
|
private objectAssignWithLcKey(a: Record<string, string>, b: Record<string, string>): Record<string, string> {
|
||||||
return Object.assign(this.#lcObjectKey(a), this.#lcObjectKey(b));
|
return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async signedPost(user: { id: User['id'] }, url: string, object: any) {
|
public async signedPost(user: { id: User['id'] }, url: string, object: any) {
|
||||||
|
@ -132,7 +132,7 @@ export class ApRequestService {
|
||||||
|
|
||||||
const keypair = await this.userKeypairStoreService.getUserKeypair(user.id);
|
const keypair = await this.userKeypairStoreService.getUserKeypair(user.id);
|
||||||
|
|
||||||
const req = this.#createSignedPost({
|
const req = this.createSignedPost({
|
||||||
key: {
|
key: {
|
||||||
privateKeyPem: keypair.privateKey,
|
privateKeyPem: keypair.privateKey,
|
||||||
keyId: `${this.config.url}/users/${user.id}#main-key`,
|
keyId: `${this.config.url}/users/${user.id}#main-key`,
|
||||||
|
@ -160,7 +160,7 @@ export class ApRequestService {
|
||||||
public async signedGet(url: string, user: { id: User['id'] }) {
|
public async signedGet(url: string, user: { id: User['id'] }) {
|
||||||
const keypair = await this.userKeypairStoreService.getUserKeypair(user.id);
|
const keypair = await this.userKeypairStoreService.getUserKeypair(user.id);
|
||||||
|
|
||||||
const req = this.#createSignedGet({
|
const req = this.createSignedGet({
|
||||||
key: {
|
key: {
|
||||||
privateKeyPem: keypair.privateKey,
|
privateKeyPem: keypair.privateKey,
|
||||||
keyId: `${this.config.url}/users/${user.id}#main-key`,
|
keyId: `${this.config.url}/users/${user.id}#main-key`,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { ApLoggerService } from '../ApLoggerService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApImageService {
|
export class ApImageService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -28,7 +28,7 @@ export class ApImageService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private apLoggerService: ApLoggerService,
|
private apLoggerService: ApLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +46,7 @@ export class ApImageService {
|
||||||
throw new Error('invalid image: url not privided');
|
throw new Error('invalid image: url not privided');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.info(`Creating the Image: ${image.url}`);
|
this.logger.info(`Creating the Image: ${image.url}`);
|
||||||
|
|
||||||
const instance = await this.metaService.fetch();
|
const instance = await this.metaService.fetch();
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ import type { IObject, IPost } from '../type.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApNoteService {
|
export class ApNoteService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -71,7 +71,7 @@ export class ApNoteService {
|
||||||
private apDbResolverService: ApDbResolverService,
|
private apDbResolverService: ApDbResolverService,
|
||||||
private apLoggerService: ApLoggerService,
|
private apLoggerService: ApLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public validateNote(object: any, uri: string) {
|
public validateNote(object: any, uri: string) {
|
||||||
|
@ -116,7 +116,7 @@ export class ApNoteService {
|
||||||
const entryUri = getApId(value);
|
const entryUri = getApId(value);
|
||||||
const err = this.validateNote(object, entryUri);
|
const err = this.validateNote(object, entryUri);
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(`${err.message}`, {
|
this.logger.error(`${err.message}`, {
|
||||||
resolver: {
|
resolver: {
|
||||||
history: resolver.getHistory(),
|
history: resolver.getHistory(),
|
||||||
},
|
},
|
||||||
|
@ -128,9 +128,9 @@ export class ApNoteService {
|
||||||
|
|
||||||
const note: IPost = object;
|
const note: IPost = object;
|
||||||
|
|
||||||
this.#logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
||||||
|
|
||||||
this.#logger.info(`Creating the Note: ${note.id}`);
|
this.logger.info(`Creating the Note: ${note.id}`);
|
||||||
|
|
||||||
// 投稿者をフェッチ
|
// 投稿者をフェッチ
|
||||||
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
|
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
|
||||||
|
@ -174,7 +174,7 @@ export class ApNoteService {
|
||||||
const reply: Note | null = note.inReplyTo
|
const reply: Note | null = note.inReplyTo
|
||||||
? await this.resolveNote(note.inReplyTo, resolver).then(x => {
|
? await this.resolveNote(note.inReplyTo, resolver).then(x => {
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
this.#logger.warn('Specified inReplyTo, but nout found');
|
this.logger.warn('Specified inReplyTo, but nout found');
|
||||||
throw new Error('inReplyTo not found');
|
throw new Error('inReplyTo not found');
|
||||||
} else {
|
} else {
|
||||||
return x;
|
return x;
|
||||||
|
@ -191,7 +191,7 @@ export class ApNoteService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`);
|
this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`);
|
||||||
throw err;
|
throw err;
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
@ -255,9 +255,9 @@ export class ApNoteService {
|
||||||
|
|
||||||
const tryCreateVote = async (name: string, index: number): Promise<null> => {
|
const tryCreateVote = async (name: string, index: number): Promise<null> => {
|
||||||
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
|
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
|
||||||
this.#logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
||||||
} else if (index >= 0) {
|
} else if (index >= 0) {
|
||||||
this.#logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
||||||
await this.pollService.vote(actor, reply, index);
|
await this.pollService.vote(actor, reply, index);
|
||||||
|
|
||||||
// リモートフォロワーにUpdate配信
|
// リモートフォロワーにUpdate配信
|
||||||
|
@ -272,7 +272,7 @@ export class ApNoteService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => {
|
const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => {
|
||||||
this.#logger.info(`extractEmojis: ${e}`);
|
this.logger.info(`extractEmojis: ${e}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -386,7 +386,7 @@ export class ApNoteService {
|
||||||
return exists;
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.info(`register emoji host=${host}, name=${name}`);
|
this.logger.info(`register emoji host=${host}, name=${name}`);
|
||||||
|
|
||||||
return await this.emojisRepository.insert({
|
return await this.emojisRepository.insert({
|
||||||
id: this.idService.genId(),
|
id: this.idService.genId(),
|
||||||
|
|
|
@ -91,7 +91,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
private usersChart: UsersChart;
|
private usersChart: UsersChart;
|
||||||
private instanceChart: InstanceChart;
|
private instanceChart: InstanceChart;
|
||||||
private apLoggerService: ApLoggerService;
|
private apLoggerService: ApLoggerService;
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
@ -153,7 +153,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.usersChart = this.moduleRef.get('UsersChart');
|
this.usersChart = this.moduleRef.get('UsersChart');
|
||||||
this.instanceChart = this.moduleRef.get('InstanceChart');
|
this.instanceChart = this.moduleRef.get('InstanceChart');
|
||||||
this.apLoggerService = this.moduleRef.get('ApLoggerService');
|
this.apLoggerService = this.moduleRef.get('ApLoggerService');
|
||||||
this.#logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,7 +161,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
* @param x Fetched object
|
* @param x Fetched object
|
||||||
* @param uri Fetch target URI
|
* @param uri Fetch target URI
|
||||||
*/
|
*/
|
||||||
#validateActor(x: IObject, uri: string): IActor {
|
private validateActor(x: IObject, uri: string): IActor {
|
||||||
const expectHost = this.utilityService.toPuny(new URL(uri).hostname);
|
const expectHost = this.utilityService.toPuny(new URL(uri).hostname);
|
||||||
|
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
|
@ -264,9 +264,9 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
const object = await resolver.resolve(uri) as any;
|
const object = await resolver.resolve(uri) as any;
|
||||||
|
|
||||||
const person = this.#validateActor(object, uri);
|
const person = this.validateActor(object, uri);
|
||||||
|
|
||||||
this.#logger.info(`Creating the Person: ${person.id}`);
|
this.logger.info(`Creating the Person: ${person.id}`);
|
||||||
|
|
||||||
const host = this.utilityService.toPuny(new URL(object.id).hostname);
|
const host = this.utilityService.toPuny(new URL(object.id).hostname);
|
||||||
|
|
||||||
|
@ -338,7 +338,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
throw new Error('already registered');
|
throw new Error('already registered');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.#logger.error(e instanceof Error ? e : new Error(e as string));
|
this.logger.error(e instanceof Error ? e : new Error(e as string));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -379,7 +379,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
//#region カスタム絵文字取得
|
//#region カスタム絵文字取得
|
||||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => {
|
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => {
|
||||||
this.#logger.info(`extractEmojis: ${err}`);
|
this.logger.info(`extractEmojis: ${err}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await this.updateFeatured(user!.id).catch(err => this.#logger.error(err));
|
await this.updateFeatured(user!.id).catch(err => this.logger.error(err));
|
||||||
|
|
||||||
return user!;
|
return user!;
|
||||||
}
|
}
|
||||||
|
@ -422,9 +422,9 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
const object = hint ?? await resolver.resolve(uri);
|
const object = hint ?? await resolver.resolve(uri);
|
||||||
|
|
||||||
const person = this.#validateActor(object, uri);
|
const person = this.validateActor(object, uri);
|
||||||
|
|
||||||
this.#logger.info(`Updating the Person: ${person.id}`);
|
this.logger.info(`Updating the Person: ${person.id}`);
|
||||||
|
|
||||||
// アバターとヘッダー画像をフェッチ
|
// アバターとヘッダー画像をフェッチ
|
||||||
const [avatar, banner] = await Promise.all([
|
const [avatar, banner] = await Promise.all([
|
||||||
|
@ -438,7 +438,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
|
|
||||||
// カスタム絵文字取得
|
// カスタム絵文字取得
|
||||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
|
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
|
||||||
this.#logger.info(`extractEmojis: ${e}`);
|
this.logger.info(`extractEmojis: ${e}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.updateFeatured(exist.id).catch(err => this.#logger.error(err));
|
await this.updateFeatured(exist.id).catch(err => this.logger.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -556,7 +556,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
if (!this.userEntityService.isRemoteUser(user)) return;
|
if (!this.userEntityService.isRemoteUser(user)) return;
|
||||||
if (!user.featured) return;
|
if (!user.featured) return;
|
||||||
|
|
||||||
this.#logger.info(`Updating the featured: ${user.uri}`);
|
this.logger.info(`Updating the featured: ${user.uri}`);
|
||||||
|
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type { IObject, IQuestion } from '../type.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApQuestionService {
|
export class ApQuestionService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -27,7 +27,7 @@ export class ApQuestionService {
|
||||||
private apResolverService: ApResolverService,
|
private apResolverService: ApResolverService,
|
||||||
private apLoggerService: ApLoggerService,
|
private apLoggerService: ApLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.apLoggerService.logger;
|
this.logger = this.apLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> {
|
public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> {
|
||||||
|
@ -82,7 +82,7 @@ export class ApQuestionService {
|
||||||
// resolve new Question object
|
// resolve new Question object
|
||||||
const resolver = this.apResolverService.createResolver();
|
const resolver = this.apResolverService.createResolver();
|
||||||
const question = await resolver.resolve(value) as IQuestion;
|
const question = await resolver.resolve(value) as IQuestion;
|
||||||
this.#logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
||||||
|
|
||||||
if (question.type !== 'Question') throw new Error('object is not a Question');
|
if (question.type !== 'Question') throw new Error('object is not a Question');
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ const interval = 30 * 60 * 1000;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JanitorService implements OnApplicationShutdown {
|
export class JanitorService implements OnApplicationShutdown {
|
||||||
#intervalId: NodeJS.Timer;
|
private intervalId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.attestationChallengesRepository)
|
@Inject(DI.attestationChallengesRepository)
|
||||||
|
@ -28,10 +28,10 @@ export class JanitorService implements OnApplicationShutdown {
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
this.#intervalId = setInterval(tick, interval);
|
this.intervalId = setInterval(tick, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public onApplicationShutdown(signal?: string | undefined) {
|
||||||
clearInterval(this.#intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ const interval = 10000;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueStatsService implements OnApplicationShutdown {
|
export class QueueStatsService implements OnApplicationShutdown {
|
||||||
#intervalId: NodeJS.Timer;
|
private intervalId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
@ -68,10 +68,10 @@ export class QueueStatsService implements OnApplicationShutdown {
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
this.#intervalId = setInterval(tick, interval);
|
this.intervalId = setInterval(tick, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public onApplicationShutdown(signal?: string | undefined) {
|
||||||
clearInterval(this.#intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const round = (num: number) => Math.round(num * 10) / 10;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerStatsService implements OnApplicationShutdown {
|
export class ServerStatsService implements OnApplicationShutdown {
|
||||||
#intervalId: NodeJS.Timer;
|
private intervalId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
) {
|
) {
|
||||||
|
@ -58,11 +58,11 @@ export class ServerStatsService implements OnApplicationShutdown {
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
this.#intervalId = setInterval(tick, interval);
|
this.intervalId = setInterval(tick, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public onApplicationShutdown(signal?: string | undefined) {
|
||||||
clearInterval(this.#intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueProcessorService {
|
export class QueueProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -32,7 +32,7 @@ export class QueueProcessorService {
|
||||||
private deliverProcessorService: DeliverProcessorService,
|
private deliverProcessorService: DeliverProcessorService,
|
||||||
private inboxProcessorService: InboxProcessorService,
|
private inboxProcessorService: InboxProcessorService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger;
|
this.logger = this.queueLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
|
@ -44,12 +44,12 @@ export class QueueProcessorService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemLogger = this.#logger.createSubLogger('system');
|
const systemLogger = this.logger.createSubLogger('system');
|
||||||
const deliverLogger = this.#logger.createSubLogger('deliver');
|
const deliverLogger = this.logger.createSubLogger('deliver');
|
||||||
const webhookLogger = this.#logger.createSubLogger('webhook');
|
const webhookLogger = this.logger.createSubLogger('webhook');
|
||||||
const inboxLogger = this.#logger.createSubLogger('inbox');
|
const inboxLogger = this.logger.createSubLogger('inbox');
|
||||||
const dbLogger = this.#logger.createSubLogger('db');
|
const dbLogger = this.logger.createSubLogger('db');
|
||||||
const objectStorageLogger = this.#logger.createSubLogger('objectStorage');
|
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
|
||||||
|
|
||||||
this.queueService.systemQueue
|
this.queueService.systemQueue
|
||||||
.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
|
.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CheckExpiredMutingsProcessorService {
|
export class CheckExpiredMutingsProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -22,11 +22,11 @@ export class CheckExpiredMutingsProcessorService {
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings');
|
this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Checking expired mutings...');
|
this.logger.info('Checking expired mutings...');
|
||||||
|
|
||||||
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
||||||
.where('muting.expiresAt IS NOT NULL')
|
.where('muting.expiresAt IS NOT NULL')
|
||||||
|
@ -44,7 +44,7 @@ export class CheckExpiredMutingsProcessorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('All expired mutings checked.');
|
this.logger.succ('All expired mutings checked.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CleanChartsProcessorService {
|
export class CleanChartsProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -41,11 +41,11 @@ export class CleanChartsProcessorService {
|
||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('clean-charts');
|
this.logger = this.queueLoggerService.logger.createSubLogger('clean-charts');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Clean charts...');
|
this.logger.info('Clean charts...');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.federationChart.clean(),
|
this.federationChart.clean(),
|
||||||
|
@ -62,7 +62,7 @@ export class CleanChartsProcessorService {
|
||||||
this.apRequestChart.clean(),
|
this.apRequestChart.clean(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.#logger.succ('All charts successfully cleaned.');
|
this.logger.succ('All charts successfully cleaned.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CleanProcessorService {
|
export class CleanProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -20,17 +20,17 @@ export class CleanProcessorService {
|
||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('clean');
|
this.logger = this.queueLoggerService.logger.createSubLogger('clean');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Cleaning...');
|
this.logger.info('Cleaning...');
|
||||||
|
|
||||||
this.userIpsRepository.delete({
|
this.userIpsRepository.delete({
|
||||||
createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
|
createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#logger.succ('Cleaned.');
|
this.logger.succ('Cleaned.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CleanRemoteFilesProcessorService {
|
export class CleanRemoteFilesProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -22,11 +22,11 @@ export class CleanRemoteFilesProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files');
|
this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Deleting cached remote files...');
|
this.logger.info('Deleting cached remote files...');
|
||||||
|
|
||||||
let deletedCount = 0;
|
let deletedCount = 0;
|
||||||
let cursor: any = null;
|
let cursor: any = null;
|
||||||
|
@ -63,7 +63,7 @@ export class CleanRemoteFilesProcessorService {
|
||||||
job.progress(deletedCount / total);
|
job.progress(deletedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('All cahced remote files has been deleted.');
|
this.logger.succ('All cahced remote files has been deleted.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type { DbUserDeleteJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountProcessorService {
|
export class DeleteAccountProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -36,11 +36,11 @@ export class DeleteAccountProcessorService {
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('delete-account');
|
this.logger = this.queueLoggerService.logger.createSubLogger('delete-account');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
|
public async process(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
|
||||||
this.#logger.info(`Deleting account of ${job.data.user.id} ...`);
|
this.logger.info(`Deleting account of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -71,7 +71,7 @@ export class DeleteAccountProcessorService {
|
||||||
await this.notesRepository.delete(notes.map(note => note.id));
|
await this.notesRepository.delete(notes.map(note => note.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('All of notes deleted');
|
this.logger.succ('All of notes deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Delete files
|
{ // Delete files
|
||||||
|
@ -100,7 +100,7 @@ export class DeleteAccountProcessorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('All of files deleted');
|
this.logger.succ('All of files deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Send email notification
|
{ // Send email notification
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { DbUserJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteDriveFilesProcessorService {
|
export class DeleteDriveFilesProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -26,11 +26,11 @@ export class DeleteDriveFilesProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files');
|
this.logger = this.queueLoggerService.logger.createSubLogger('delete-drive-files');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -72,7 +72,7 @@ export class DeleteDriveFilesProcessorService {
|
||||||
job.progress(deletedCount / total);
|
job.progress(deletedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
|
this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type { ObjectStorageFileJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteFileProcessorService {
|
export class DeleteFileProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -18,7 +18,7 @@ export class DeleteFileProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('delete-file');
|
this.logger = this.queueLoggerService.logger.createSubLogger('delete-file');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<ObjectStorageFileJobData>): Promise<string> {
|
public async process(job: Bull.Job<ObjectStorageFileJobData>): Promise<string> {
|
||||||
|
|
|
@ -21,9 +21,9 @@ import type { DeliverJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeliverProcessorService {
|
export class DeliverProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
#suspendedHostsCache: Cache<Instance[]>;
|
private suspendedHostsCache: Cache<Instance[]>;
|
||||||
#latest: string | null;
|
private latest: string | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -45,9 +45,9 @@ export class DeliverProcessorService {
|
||||||
private federationChart: FederationChart,
|
private federationChart: FederationChart,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('deliver');
|
this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
|
||||||
this.#suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
|
this.suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
|
||||||
this.#latest = null;
|
this.latest = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DeliverJobData>): Promise<string> {
|
public async process(job: Bull.Job<DeliverJobData>): Promise<string> {
|
||||||
|
@ -60,22 +60,22 @@ export class DeliverProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSuspendedなら中断
|
// isSuspendedなら中断
|
||||||
let suspendedHosts = this.#suspendedHostsCache.get(null);
|
let suspendedHosts = this.suspendedHostsCache.get(null);
|
||||||
if (suspendedHosts == null) {
|
if (suspendedHosts == null) {
|
||||||
suspendedHosts = await this.instancesRepository.find({
|
suspendedHosts = await this.instancesRepository.find({
|
||||||
where: {
|
where: {
|
||||||
isSuspended: true,
|
isSuspended: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.#suspendedHostsCache.set(null, suspendedHosts);
|
this.suspendedHostsCache.set(null, suspendedHosts);
|
||||||
}
|
}
|
||||||
if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
|
if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
|
||||||
return 'skip (suspended)';
|
return 'skip (suspended)';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.#latest !== (this.#latest = JSON.stringify(job.data.content, null, 2))) {
|
if (this.latest !== (this.latest = JSON.stringify(job.data.content, null, 2))) {
|
||||||
this.#logger.debug(`delivering ${this.#latest}`);
|
this.logger.debug(`delivering ${this.latest}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
|
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { EndedPollNotificationJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EndedPollNotificationProcessorService {
|
export class EndedPollNotificationProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -26,7 +26,7 @@ export class EndedPollNotificationProcessorService {
|
||||||
private createNotificationService: CreateNotificationService,
|
private createNotificationService: CreateNotificationService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification');
|
this.logger = this.queueLoggerService.logger.createSubLogger('ended-poll-notification');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<EndedPollNotificationJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<EndedPollNotificationJobData>, done: () => void): Promise<void> {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportBlockingProcessorService {
|
export class ExportBlockingProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -32,11 +32,11 @@ export class ExportBlockingProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('export-blocking');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-blocking');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Exporting blocking of ${job.data.user.id} ...`);
|
this.logger.info(`Exporting blocking of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -47,7 +47,7 @@ export class ExportBlockingProcessorService {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
this.#logger.info(`Temp file is ${path}`);
|
this.logger.info(`Temp file is ${path}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(path, { flags: 'a' });
|
const stream = fs.createWriteStream(path, { flags: 'a' });
|
||||||
|
@ -84,7 +84,7 @@ export class ExportBlockingProcessorService {
|
||||||
await new Promise<void>((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
res();
|
res();
|
||||||
|
@ -102,12 +102,12 @@ export class ExportBlockingProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
this.#logger.succ(`Exported to: ${path}`);
|
this.logger.succ(`Exported to: ${path}`);
|
||||||
|
|
||||||
const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
||||||
|
|
||||||
this.#logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportCustomEmojisProcessorService {
|
export class ExportCustomEmojisProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -33,11 +33,11 @@ export class ExportCustomEmojisProcessorService {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job, done: () => void): Promise<void> {
|
public async process(job: Bull.Job, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Exporting custom emojis ...');
|
this.logger.info('Exporting custom emojis ...');
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -47,7 +47,7 @@ export class ExportCustomEmojisProcessorService {
|
||||||
|
|
||||||
const [path, cleanup] = await createTempDir();
|
const [path, cleanup] = await createTempDir();
|
||||||
|
|
||||||
this.#logger.info(`Temp dir is ${path}`);
|
this.logger.info(`Temp dir is ${path}`);
|
||||||
|
|
||||||
const metaPath = path + '/meta.json';
|
const metaPath = path + '/meta.json';
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ export class ExportCustomEmojisProcessorService {
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
metaStream.write(text, err => {
|
metaStream.write(text, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
res();
|
res();
|
||||||
|
@ -90,7 +90,7 @@ export class ExportCustomEmojisProcessorService {
|
||||||
await this.downloadService.downloadUrl(emoji.originalUrl, emojiPath);
|
await this.downloadService.downloadUrl(emoji.originalUrl, emojiPath);
|
||||||
downloaded = true;
|
downloaded = true;
|
||||||
} catch (e) { // TODO: 何度か再試行
|
} catch (e) { // TODO: 何度か再試行
|
||||||
this.#logger.error(e instanceof Error ? e : new Error(e as string));
|
this.logger.error(e instanceof Error ? e : new Error(e as string));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!downloaded) {
|
if (!downloaded) {
|
||||||
|
@ -118,12 +118,12 @@ export class ExportCustomEmojisProcessorService {
|
||||||
zlib: { level: 0 },
|
zlib: { level: 0 },
|
||||||
});
|
});
|
||||||
archiveStream.on('close', async () => {
|
archiveStream.on('close', async () => {
|
||||||
this.#logger.succ(`Exported to: ${archivePath}`);
|
this.logger.succ(`Exported to: ${archivePath}`);
|
||||||
|
|
||||||
const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip';
|
const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip';
|
||||||
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
||||||
|
|
||||||
this.#logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
cleanup();
|
cleanup();
|
||||||
archiveCleanup();
|
archiveCleanup();
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportFollowingProcessorService {
|
export class ExportFollowingProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -35,11 +35,11 @@ export class ExportFollowingProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('export-following');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-following');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Exporting following of ${job.data.user.id} ...`);
|
this.logger.info(`Exporting following of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -50,7 +50,7 @@ export class ExportFollowingProcessorService {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
this.#logger.info(`Temp file is ${path}`);
|
this.logger.info(`Temp file is ${path}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(path, { flags: 'a' });
|
const stream = fs.createWriteStream(path, { flags: 'a' });
|
||||||
|
@ -94,7 +94,7 @@ export class ExportFollowingProcessorService {
|
||||||
await new Promise<void>((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
res();
|
res();
|
||||||
|
@ -105,12 +105,12 @@ export class ExportFollowingProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
this.#logger.succ(`Exported to: ${path}`);
|
this.logger.succ(`Exported to: ${path}`);
|
||||||
|
|
||||||
const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
||||||
|
|
||||||
this.#logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { DbUserJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportMutingProcessorService {
|
export class ExportMutingProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -34,11 +34,11 @@ export class ExportMutingProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('export-muting');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-muting');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Exporting muting of ${job.data.user.id} ...`);
|
this.logger.info(`Exporting muting of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -49,7 +49,7 @@ export class ExportMutingProcessorService {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
this.#logger.info(`Temp file is ${path}`);
|
this.logger.info(`Temp file is ${path}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(path, { flags: 'a' });
|
const stream = fs.createWriteStream(path, { flags: 'a' });
|
||||||
|
@ -87,7 +87,7 @@ export class ExportMutingProcessorService {
|
||||||
await new Promise<void>((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
res();
|
res();
|
||||||
|
@ -105,12 +105,12 @@ export class ExportMutingProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
this.#logger.succ(`Exported to: ${path}`);
|
this.logger.succ(`Exported to: ${path}`);
|
||||||
|
|
||||||
const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
||||||
|
|
||||||
this.#logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { DbUserJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportNotesProcessorService {
|
export class ExportNotesProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -34,11 +34,11 @@ export class ExportNotesProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('export-notes');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-notes');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Exporting notes of ${job.data.user.id} ...`);
|
this.logger.info(`Exporting notes of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -49,7 +49,7 @@ export class ExportNotesProcessorService {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
this.#logger.info(`Temp file is ${path}`);
|
this.logger.info(`Temp file is ${path}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(path, { flags: 'a' });
|
const stream = fs.createWriteStream(path, { flags: 'a' });
|
||||||
|
@ -58,7 +58,7 @@ export class ExportNotesProcessorService {
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
stream.write(text, err => {
|
stream.write(text, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
res();
|
res();
|
||||||
|
@ -112,12 +112,12 @@ export class ExportNotesProcessorService {
|
||||||
await write(']');
|
await write(']');
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
this.#logger.succ(`Exported to: ${path}`);
|
this.logger.succ(`Exported to: ${path}`);
|
||||||
|
|
||||||
const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
|
const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
||||||
|
|
||||||
this.#logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { DbUserJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportUserListsProcessorService {
|
export class ExportUserListsProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -34,11 +34,11 @@ export class ExportUserListsProcessorService {
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('export-user-lists');
|
this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Exporting user lists of ${job.data.user.id} ...`);
|
this.logger.info(`Exporting user lists of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -53,7 +53,7 @@ export class ExportUserListsProcessorService {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
this.#logger.info(`Temp file is ${path}`);
|
this.logger.info(`Temp file is ${path}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(path, { flags: 'a' });
|
const stream = fs.createWriteStream(path, { flags: 'a' });
|
||||||
|
@ -70,7 +70,7 @@ export class ExportUserListsProcessorService {
|
||||||
await new Promise<void>((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
rej(err);
|
rej(err);
|
||||||
} else {
|
} else {
|
||||||
res();
|
res();
|
||||||
|
@ -81,12 +81,12 @@ export class ExportUserListsProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
this.#logger.succ(`Exported to: ${path}`);
|
this.logger.succ(`Exported to: ${path}`);
|
||||||
|
|
||||||
const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
|
||||||
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
|
||||||
|
|
||||||
this.#logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportBlockingProcessorService {
|
export class ImportBlockingProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -36,11 +36,11 @@ export class ImportBlockingProcessorService {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('import-blocking');
|
this.logger = this.queueLoggerService.logger.createSubLogger('import-blocking');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Importing blocking of ${job.data.user.id} ...`);
|
this.logger.info(`Importing blocking of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -88,15 +88,15 @@ export class ImportBlockingProcessorService {
|
||||||
// skip myself
|
// skip myself
|
||||||
if (target.id === job.data.user.id) continue;
|
if (target.id === job.data.user.id) continue;
|
||||||
|
|
||||||
this.#logger.info(`Block[${linenum}] ${target.id} ...`);
|
this.logger.info(`Block[${linenum}] ${target.id} ...`);
|
||||||
|
|
||||||
await this.userBlockingService.block(user, target);
|
await this.userBlockingService.block(user, target);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#logger.warn(`Error in line:${linenum} ${e}`);
|
this.logger.warn(`Error in line:${linenum} ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { DbUserImportJobData } from '../types.js';
|
||||||
// TODO: 名前衝突時の動作を選べるようにする
|
// TODO: 名前衝突時の動作を選べるようにする
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportCustomEmojisProcessorService {
|
export class ImportCustomEmojisProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -40,11 +40,11 @@ export class ImportCustomEmojisProcessorService {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis');
|
this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Importing custom emojis ...');
|
this.logger.info('Importing custom emojis ...');
|
||||||
|
|
||||||
const file = await this.driveFilesRepository.findOneBy({
|
const file = await this.driveFilesRepository.findOneBy({
|
||||||
id: job.data.fileId,
|
id: job.data.fileId,
|
||||||
|
@ -56,7 +56,7 @@ export class ImportCustomEmojisProcessorService {
|
||||||
|
|
||||||
const [path, cleanup] = await createTempDir();
|
const [path, cleanup] = await createTempDir();
|
||||||
|
|
||||||
this.#logger.info(`Temp dir is ${path}`);
|
this.logger.info(`Temp dir is ${path}`);
|
||||||
|
|
||||||
const destPath = path + '/emojis.zip';
|
const destPath = path + '/emojis.zip';
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ export class ImportCustomEmojisProcessorService {
|
||||||
await this.downloadService.downloadUrl(file.url, destPath);
|
await this.downloadService.downloadUrl(file.url, destPath);
|
||||||
} catch (e) { // TODO: 何度か再試行
|
} catch (e) { // TODO: 何度か再試行
|
||||||
if (e instanceof Error || typeof e === 'string') {
|
if (e instanceof Error || typeof e === 'string') {
|
||||||
this.#logger.error(e);
|
this.logger.error(e);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -101,10 +101,10 @@ export class ImportCustomEmojisProcessorService {
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
this.#logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
unzipStream.pipe(extractor);
|
unzipStream.pipe(extractor);
|
||||||
this.#logger.succ(`Unzipping to ${outputPath}`);
|
this.logger.succ(`Unzipping to ${outputPath}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportFollowingProcessorService {
|
export class ImportFollowingProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -33,11 +33,11 @@ export class ImportFollowingProcessorService {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('import-following');
|
this.logger = this.queueLoggerService.logger.createSubLogger('import-following');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Importing following of ${job.data.user.id} ...`);
|
this.logger.info(`Importing following of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -85,15 +85,15 @@ export class ImportFollowingProcessorService {
|
||||||
// skip myself
|
// skip myself
|
||||||
if (target.id === job.data.user.id) continue;
|
if (target.id === job.data.user.id) continue;
|
||||||
|
|
||||||
this.#logger.info(`Follow[${linenum}] ${target.id} ...`);
|
this.logger.info(`Follow[${linenum}] ${target.id} ...`);
|
||||||
|
|
||||||
this.userFollowingService.follow(user, target);
|
this.userFollowingService.follow(user, target);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#logger.warn(`Error in line:${linenum} ${e}`);
|
this.logger.warn(`Error in line:${linenum} ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportMutingProcessorService {
|
export class ImportMutingProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -34,11 +34,11 @@ export class ImportMutingProcessorService {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('import-muting');
|
this.logger = this.queueLoggerService.logger.createSubLogger('import-muting');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Importing muting of ${job.data.user.id} ...`);
|
this.logger.info(`Importing muting of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -86,15 +86,15 @@ export class ImportMutingProcessorService {
|
||||||
// skip myself
|
// skip myself
|
||||||
if (target.id === job.data.user.id) continue;
|
if (target.id === job.data.user.id) continue;
|
||||||
|
|
||||||
this.#logger.info(`Mute[${linenum}] ${target.id} ...`);
|
this.logger.info(`Mute[${linenum}] ${target.id} ...`);
|
||||||
|
|
||||||
await this.userMutingService.mute(user, target);
|
await this.userMutingService.mute(user, target);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#logger.warn(`Error in line:${linenum} ${e}`);
|
this.logger.warn(`Error in line:${linenum} ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportUserListsProcessorService {
|
export class ImportUserListsProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -41,11 +41,11 @@ export class ImportUserListsProcessorService {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('import-user-lists');
|
this.logger = this.queueLoggerService.logger.createSubLogger('import-user-lists');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||||
this.#logger.info(`Importing user lists of ${job.data.user.id} ...`);
|
this.logger.info(`Importing user lists of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -102,11 +102,11 @@ export class ImportUserListsProcessorService {
|
||||||
|
|
||||||
this.userListService.push(target, list!);
|
this.userListService.push(target, list!);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#logger.warn(`Error in line:${linenum} ${e}`);
|
this.logger.warn(`Error in line:${linenum} ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import type { DeliverJobData, InboxJobData } from '../types.js';
|
||||||
// ユーザーのinboxにアクティビティが届いた時の処理
|
// ユーザーのinboxにアクティビティが届いた時の処理
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InboxProcessorService {
|
export class InboxProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -57,7 +57,7 @@ export class InboxProcessorService {
|
||||||
private federationChart: FederationChart,
|
private federationChart: FederationChart,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('inbox');
|
this.logger = this.queueLoggerService.logger.createSubLogger('inbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<InboxJobData>): Promise<string> {
|
public async process(job: Bull.Job<InboxJobData>): Promise<string> {
|
||||||
|
@ -67,7 +67,7 @@ export class InboxProcessorService {
|
||||||
//#region Log
|
//#region Log
|
||||||
const info = Object.assign({}, activity) as any;
|
const info = Object.assign({}, activity) as any;
|
||||||
delete info['@context'];
|
delete info['@context'];
|
||||||
this.#logger.debug(JSON.stringify(info, null, 2));
|
this.logger.debug(JSON.stringify(info, null, 2));
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const host = this.utilityService.toPuny(new URL(signature.keyId).hostname);
|
const host = this.utilityService.toPuny(new URL(signature.keyId).hostname);
|
||||||
|
|
|
@ -20,7 +20,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResyncChartsProcessorService {
|
export class ResyncChartsProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -41,11 +41,11 @@ export class ResyncChartsProcessorService {
|
||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('resync-charts');
|
this.logger = this.queueLoggerService.logger.createSubLogger('resync-charts');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Resync charts...');
|
this.logger.info('Resync charts...');
|
||||||
|
|
||||||
// TODO: ユーザーごとのチャートも更新する
|
// TODO: ユーザーごとのチャートも更新する
|
||||||
// TODO: インスタンスごとのチャートも更新する
|
// TODO: インスタンスごとのチャートも更新する
|
||||||
|
@ -55,7 +55,7 @@ export class ResyncChartsProcessorService {
|
||||||
this.usersChart.resync(),
|
this.usersChart.resync(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.#logger.succ('All charts successfully resynced.');
|
this.logger.succ('All charts successfully resynced.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import type Bull from 'bull';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TickChartsProcessorService {
|
export class TickChartsProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -41,11 +41,11 @@ export class TickChartsProcessorService {
|
||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('tick-charts');
|
this.logger = this.queueLoggerService.logger.createSubLogger('tick-charts');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||||
this.#logger.info('Tick charts...');
|
this.logger.info('Tick charts...');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.federationChart.tick(false),
|
this.federationChart.tick(false),
|
||||||
|
@ -62,7 +62,7 @@ export class TickChartsProcessorService {
|
||||||
this.apRequestChart.tick(false),
|
this.apRequestChart.tick(false),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.#logger.succ('All charts successfully ticked.');
|
this.logger.succ('All charts successfully ticked.');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type { WebhookDeliverJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebhookDeliverProcessorService {
|
export class WebhookDeliverProcessorService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -24,12 +24,12 @@ export class WebhookDeliverProcessorService {
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.queueLoggerService.logger.createSubLogger('webhook');
|
this.logger = this.queueLoggerService.logger.createSubLogger('webhook');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async process(job: Bull.Job<WebhookDeliverJobData>): Promise<string> {
|
public async process(job: Bull.Job<WebhookDeliverJobData>): Promise<string> {
|
||||||
try {
|
try {
|
||||||
this.#logger.debug(`delivering ${job.data.webhookId}`);
|
this.logger.debug(`delivering ${job.data.webhookId}`);
|
||||||
|
|
||||||
const res = await this.httpRequestService.getResponse({
|
const res = await this.httpRequestService.getResponse({
|
||||||
url: job.data.to,
|
url: job.data.to,
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class ActivityPubServerService {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#setResponseType(ctx: Router.RouterContext) {
|
private setResponseType(ctx: Router.RouterContext) {
|
||||||
const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
|
const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
|
||||||
if (accept === LD_JSON) {
|
if (accept === LD_JSON) {
|
||||||
ctx.response.type = LD_JSON;
|
ctx.response.type = LD_JSON;
|
||||||
|
@ -71,7 +71,7 @@ export class ActivityPubServerService {
|
||||||
* Pack Create<Note> or Announce Activity
|
* Pack Create<Note> or Announce Activity
|
||||||
* @param note Note
|
* @param note Note
|
||||||
*/
|
*/
|
||||||
async #packActivity(note: Note): Promise<any> {
|
private async packActivity(note: Note): Promise<any> {
|
||||||
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
|
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
|
||||||
const renote = await Notes.findOneByOrFail({ id: note.renoteId });
|
const renote = await Notes.findOneByOrFail({ id: note.renoteId });
|
||||||
return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note);
|
return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note);
|
||||||
|
@ -80,7 +80,7 @@ export class ActivityPubServerService {
|
||||||
return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||||
}
|
}
|
||||||
|
|
||||||
#inbox(ctx: Router.RouterContext) {
|
private inbox(ctx: Router.RouterContext) {
|
||||||
let signature;
|
let signature;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -95,7 +95,7 @@ export class ActivityPubServerService {
|
||||||
ctx.status = 202;
|
ctx.status = 202;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #followers(ctx: Router.RouterContext) {
|
private async followers(ctx: Router.RouterContext) {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const cursor = ctx.request.query.cursor;
|
const cursor = ctx.request.query.cursor;
|
||||||
|
@ -169,17 +169,17 @@ export class ActivityPubServerService {
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
} else {
|
} else {
|
||||||
// index page
|
// index page
|
||||||
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
|
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #following(ctx: Router.RouterContext) {
|
private async following(ctx: Router.RouterContext) {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const cursor = ctx.request.query.cursor;
|
const cursor = ctx.request.query.cursor;
|
||||||
|
@ -253,17 +253,17 @@ export class ActivityPubServerService {
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
} else {
|
} else {
|
||||||
// index page
|
// index page
|
||||||
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
|
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #featured(ctx: Router.RouterContext) {
|
private async featured(ctx: Router.RouterContext) {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
|
@ -293,10 +293,10 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #outbox(ctx: Router.RouterContext) {
|
private async outbox(ctx: Router.RouterContext) {
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const sinceId = ctx.request.query.since_id;
|
const sinceId = ctx.request.query.since_id;
|
||||||
|
@ -344,7 +344,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
if (sinceId) notes.reverse();
|
if (sinceId) notes.reverse();
|
||||||
|
|
||||||
const activities = await Promise.all(notes.map(note => this.#packActivity(note)));
|
const activities = await Promise.all(notes.map(note => this.packActivity(note)));
|
||||||
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
||||||
`${partOf}?${url.query({
|
`${partOf}?${url.query({
|
||||||
page: 'true',
|
page: 'true',
|
||||||
|
@ -363,7 +363,7 @@ export class ActivityPubServerService {
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
} else {
|
} else {
|
||||||
// index page
|
// index page
|
||||||
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount,
|
const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount,
|
||||||
|
@ -372,11 +372,11 @@ export class ActivityPubServerService {
|
||||||
);
|
);
|
||||||
ctx.body = this.apRendererService.renderActivity(rendered);
|
ctx.body = this.apRendererService.renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #userInfo(ctx: Router.RouterContext, user: User | null) {
|
private async userInfo(ctx: Router.RouterContext, user: User | null) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
return;
|
return;
|
||||||
|
@ -384,7 +384,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser));
|
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderPerson(user as ILocalUser));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createRouter() {
|
public createRouter() {
|
||||||
|
@ -399,8 +399,8 @@ export class ActivityPubServerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// inbox
|
// inbox
|
||||||
router.post('/inbox', json(), ctx => this.#inbox(ctx));
|
router.post('/inbox', json(), ctx => this.inbox(ctx));
|
||||||
router.post('/users/:user/inbox', json(), ctx => this.#inbox(ctx));
|
router.post('/users/:user/inbox', json(), ctx => this.inbox(ctx));
|
||||||
|
|
||||||
// note
|
// note
|
||||||
router.get('/notes/:note', async (ctx, next) => {
|
router.get('/notes/:note', async (ctx, next) => {
|
||||||
|
@ -429,7 +429,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false));
|
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderNote(note, false));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// note activity
|
// note activity
|
||||||
|
@ -446,22 +446,22 @@ export class ActivityPubServerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(await this.#packActivity(note));
|
ctx.body = this.apRendererService.renderActivity(await this.packActivity(note));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// outbox
|
// outbox
|
||||||
router.get('/users/:user/outbox', (ctx) => this.#outbox(ctx));
|
router.get('/users/:user/outbox', (ctx) => this.outbox(ctx));
|
||||||
|
|
||||||
// followers
|
// followers
|
||||||
router.get('/users/:user/followers', (ctx) => this.#followers(ctx));
|
router.get('/users/:user/followers', (ctx) => this.followers(ctx));
|
||||||
|
|
||||||
// following
|
// following
|
||||||
router.get('/users/:user/following', (ctx) => this.#following(ctx));
|
router.get('/users/:user/following', (ctx) => this.following(ctx));
|
||||||
|
|
||||||
// featured
|
// featured
|
||||||
router.get('/users/:user/collections/featured', (ctx) => this.#featured(ctx));
|
router.get('/users/:user/collections/featured', (ctx) => this.featured(ctx));
|
||||||
|
|
||||||
// publickey
|
// publickey
|
||||||
router.get('/users/:user/publickey', async ctx => {
|
router.get('/users/:user/publickey', async ctx => {
|
||||||
|
@ -482,7 +482,7 @@ export class ActivityPubServerService {
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair));
|
ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
}
|
}
|
||||||
|
@ -499,7 +499,7 @@ export class ActivityPubServerService {
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.#userInfo(ctx, user);
|
await this.userInfo(ctx, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/@:user', async (ctx, next) => {
|
router.get('/@:user', async (ctx, next) => {
|
||||||
|
@ -511,7 +511,7 @@ export class ActivityPubServerService {
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.#userInfo(ctx, user);
|
await this.userInfo(ctx, user);
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
@ -529,7 +529,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji));
|
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderEmoji(emoji));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// like
|
// like
|
||||||
|
@ -550,7 +550,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note));
|
ctx.body = this.apRendererService.renderActivity(await this.apRendererService.renderLike(reaction, note));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// follow
|
// follow
|
||||||
|
@ -576,7 +576,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee));
|
ctx.body = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
this.#setResponseType(ctx);
|
this.setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
|
|
@ -29,7 +29,7 @@ const assets = `${_dirname}/../../server/file/assets/`;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileServerService {
|
export class FileServerService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -45,12 +45,12 @@ export class FileServerService {
|
||||||
private internalStorageService: InternalStorageService,
|
private internalStorageService: InternalStorageService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('server', 'gray', false);
|
this.logger = this.loggerService.getLogger('server', 'gray', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public commonReadableHandlerGenerator(ctx: Koa.Context) {
|
public commonReadableHandlerGenerator(ctx: Koa.Context) {
|
||||||
return (e: Error): void => {
|
return (e: Error): void => {
|
||||||
this.#logger.error(e);
|
this.logger.error(e);
|
||||||
ctx.status = 500;
|
ctx.status = 500;
|
||||||
ctx.set('Cache-Control', 'max-age=300');
|
ctx.set('Cache-Control', 'max-age=300');
|
||||||
};
|
};
|
||||||
|
@ -74,8 +74,8 @@ export class FileServerService {
|
||||||
ctx.set('Cache-Control', 'max-age=31536000, immutable');
|
ctx.set('Cache-Control', 'max-age=31536000, immutable');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/:key', ctx => this.#sendDriveFile(ctx));
|
router.get('/:key', ctx => this.sendDriveFile(ctx));
|
||||||
router.get('/:key/(.*)', ctx => this.#sendDriveFile(ctx));
|
router.get('/:key/(.*)', ctx => this.sendDriveFile(ctx));
|
||||||
|
|
||||||
// Register router
|
// Register router
|
||||||
app.use(router.routes());
|
app.use(router.routes());
|
||||||
|
@ -83,7 +83,7 @@ export class FileServerService {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #sendDriveFile(ctx: Koa.Context) {
|
private async sendDriveFile(ctx: Koa.Context) {
|
||||||
const key = ctx.params.key;
|
const key = ctx.params.key;
|
||||||
|
|
||||||
// Fetch drive file
|
// Fetch drive file
|
||||||
|
@ -139,7 +139,7 @@ export class FileServerService {
|
||||||
ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
|
ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
|
||||||
ctx.set('Cache-Control', 'max-age=31536000, immutable');
|
ctx.set('Cache-Control', 'max-age=31536000, immutable');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#logger.error(`${err}`);
|
this.logger.error(`${err}`);
|
||||||
|
|
||||||
if (err instanceof StatusError && err.isClientError) {
|
if (err instanceof StatusError && err.isClientError) {
|
||||||
ctx.status = err.statusCode;
|
ctx.status = err.statusCode;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaProxyServerService {
|
export class MediaProxyServerService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -30,7 +30,7 @@ export class MediaProxyServerService {
|
||||||
private imageProcessingService: ImageProcessingService,
|
private imageProcessingService: ImageProcessingService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('server', 'gray', false);
|
this.logger = this.loggerService.getLogger('server', 'gray', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createServer() {
|
public createServer() {
|
||||||
|
@ -44,7 +44,7 @@ export class MediaProxyServerService {
|
||||||
// Init router
|
// Init router
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get('/:url*', ctx => this.#handler(ctx));
|
router.get('/:url*', ctx => this.handler(ctx));
|
||||||
|
|
||||||
// Register router
|
// Register router
|
||||||
app.use(router.routes());
|
app.use(router.routes());
|
||||||
|
@ -52,7 +52,7 @@ export class MediaProxyServerService {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #handler(ctx: Koa.Context) {
|
private async handler(ctx: Koa.Context) {
|
||||||
const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
|
const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
|
||||||
|
|
||||||
if (typeof url !== 'string') {
|
if (typeof url !== 'string') {
|
||||||
|
@ -126,7 +126,7 @@ export class MediaProxyServerService {
|
||||||
ctx.set('Cache-Control', 'max-age=31536000, immutable');
|
ctx.set('Cache-Control', 'max-age=31536000, immutable');
|
||||||
ctx.body = image.data;
|
ctx.body = image.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#logger.error(`${err}`);
|
this.logger.error(`${err}`);
|
||||||
|
|
||||||
if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) {
|
if (err instanceof StatusError && (err.statusCode === 302 || err.isClientError)) {
|
||||||
ctx.status = err.statusCode;
|
ctx.status = err.statusCode;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { ClientServerService } from './web/ClientServerService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerService {
|
export class ServerService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -54,7 +54,7 @@ export class ServerService {
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('server', 'gray', false);
|
this.logger = this.loggerService.getLogger('server', 'gray', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public launch() {
|
public launch() {
|
||||||
|
@ -65,7 +65,7 @@ export class ServerService {
|
||||||
if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) {
|
if (!['production', 'test'].includes(process.env.NODE_ENV ?? '')) {
|
||||||
// Logger
|
// Logger
|
||||||
koa.use(koaLogger(str => {
|
koa.use(koaLogger(str => {
|
||||||
this.#logger.info(str);
|
this.logger.info(str);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Delay
|
// Delay
|
||||||
|
@ -157,13 +157,13 @@ export class ServerService {
|
||||||
server.on('error', err => {
|
server.on('error', err => {
|
||||||
switch ((err as any).code) {
|
switch ((err as any).code) {
|
||||||
case 'EACCES':
|
case 'EACCES':
|
||||||
this.#logger.error(`You do not have permission to listen on port ${this.config.port}.`);
|
this.logger.error(`You do not have permission to listen on port ${this.config.port}.`);
|
||||||
break;
|
break;
|
||||||
case 'EADDRINUSE':
|
case 'EADDRINUSE':
|
||||||
this.#logger.error(`Port ${this.config.port} is already in use by another process.`);
|
this.logger.error(`Port ${this.config.port} is already in use by another process.`);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.#logger.error(err);
|
this.logger.error(err);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,9 @@ const accessDenied = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiCallService implements OnApplicationShutdown {
|
export class ApiCallService implements OnApplicationShutdown {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
#userIpHistories: Map<User['id'], Set<string>>;
|
private userIpHistories: Map<User['id'], Set<string>>;
|
||||||
#userIpHistoriesClearIntervalId: NodeJS.Timer;
|
private userIpHistoriesClearIntervalId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.userIpsRepository)
|
@Inject(DI.userIpsRepository)
|
||||||
|
@ -36,11 +36,11 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
private rateLimiterService: RateLimiterService,
|
private rateLimiterService: RateLimiterService,
|
||||||
private apiLoggerService: ApiLoggerService,
|
private apiLoggerService: ApiLoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.apiLoggerService.logger;
|
this.logger = this.apiLoggerService.logger;
|
||||||
this.#userIpHistories = new Map<User['id'], Set<string>>();
|
this.userIpHistories = new Map<User['id'], Set<string>>();
|
||||||
|
|
||||||
this.#userIpHistoriesClearIntervalId = setInterval(() => {
|
this.userIpHistoriesClearIntervalId = setInterval(() => {
|
||||||
this.#userIpHistories.clear();
|
this.userIpHistories.clear();
|
||||||
}, 1000 * 60 * 60);
|
}, 1000 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
// Authentication
|
// Authentication
|
||||||
this.authenticateService.authenticate(body['i']).then(([user, app]) => {
|
this.authenticateService.authenticate(body['i']).then(([user, app]) => {
|
||||||
// API invoking
|
// API invoking
|
||||||
this.#call(endpoint, exec, user, app, body, ctx).then((res: any) => {
|
this.call(endpoint, exec, user, app, body, ctx).then((res: any) => {
|
||||||
if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
|
if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
|
||||||
ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
|
ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
|
||||||
}
|
}
|
||||||
|
@ -90,10 +90,10 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
this.metaService.fetch().then(meta => {
|
this.metaService.fetch().then(meta => {
|
||||||
if (!meta.enableIpLogging) return;
|
if (!meta.enableIpLogging) return;
|
||||||
const ip = ctx.ip;
|
const ip = ctx.ip;
|
||||||
const ips = this.#userIpHistories.get(user.id);
|
const ips = this.userIpHistories.get(user.id);
|
||||||
if (ips == null || !ips.has(ip)) {
|
if (ips == null || !ips.has(ip)) {
|
||||||
if (ips == null) {
|
if (ips == null) {
|
||||||
this.#userIpHistories.set(user.id, new Set([ip]));
|
this.userIpHistories.set(user.id, new Set([ip]));
|
||||||
} else {
|
} else {
|
||||||
ips.add(ip);
|
ips.add(ip);
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #call(
|
private async call(
|
||||||
ep: IEndpoint,
|
ep: IEndpoint,
|
||||||
exec: any,
|
exec: any,
|
||||||
user: CacheableLocalUser | null | undefined,
|
user: CacheableLocalUser | null | undefined,
|
||||||
|
@ -225,7 +225,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
if (err instanceof ApiError) {
|
if (err instanceof ApiError) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
this.#logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
|
this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
|
||||||
ep: ep.name,
|
ep: ep.name,
|
||||||
ps: data,
|
ps: data,
|
||||||
e: {
|
e: {
|
||||||
|
@ -247,12 +247,12 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
const after = performance.now();
|
const after = performance.now();
|
||||||
const time = after - before;
|
const time = after - before;
|
||||||
if (time > 1000) {
|
if (time > 1000) {
|
||||||
this.#logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
|
this.logger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public onApplicationShutdown(signal?: string | undefined) {
|
||||||
clearInterval(this.#userIpHistoriesClearIntervalId);
|
clearInterval(this.userIpHistoriesClearIntervalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ export class AuthenticationError extends Error {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthenticateService {
|
export class AuthenticateService {
|
||||||
#appCache: Cache<App>;
|
private appCache: Cache<App>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
|
@ -31,7 +31,7 @@ export class AuthenticateService {
|
||||||
|
|
||||||
private userCacheService: UserCacheService,
|
private userCacheService: UserCacheService,
|
||||||
) {
|
) {
|
||||||
this.#appCache = new Cache<App>(Infinity);
|
this.appCache = new Cache<App>(Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> {
|
public async authenticate(token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> {
|
||||||
|
@ -71,7 +71,7 @@ export class AuthenticateService {
|
||||||
}) as Promise<ILocalUser>);
|
}) as Promise<ILocalUser>);
|
||||||
|
|
||||||
if (accessToken.appId) {
|
if (accessToken.appId) {
|
||||||
const app = await this.#appCache.fetch(accessToken.appId,
|
const app = await this.appCache.fetch(accessToken.appId,
|
||||||
() => this.appsRepository.findOneByOrFail({ id: accessToken.appId! }));
|
() => this.appsRepository.findOneByOrFail({ id: accessToken.appId! }));
|
||||||
|
|
||||||
return [user, {
|
return [user, {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { IEndpointMeta } from './endpoints.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RateLimiterService {
|
export class RateLimiterService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
|
@ -16,7 +16,7 @@ export class RateLimiterService {
|
||||||
|
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('limiter');
|
this.logger = this.loggerService.getLogger('limiter');
|
||||||
}
|
}
|
||||||
|
|
||||||
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) {
|
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) {
|
||||||
|
@ -37,7 +37,7 @@ export class RateLimiterService {
|
||||||
return reject('ERR');
|
return reject('ERR');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
if (info.remaining === 0) {
|
||||||
reject('BRIEF_REQUEST_INTERVAL');
|
reject('BRIEF_REQUEST_INTERVAL');
|
||||||
|
@ -65,7 +65,7 @@ export class RateLimiterService {
|
||||||
return reject('ERR');
|
return reject('ERR');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
if (info.remaining === 0) {
|
||||||
reject('RATE_LIMIT_EXCEEDED');
|
reject('RATE_LIMIT_EXCEEDED');
|
||||||
|
|
|
@ -71,13 +71,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await this.userSuspendService.doPostSuspend(user).catch(e => {});
|
await this.userSuspendService.doPostSuspend(user).catch(e => {});
|
||||||
await this.#unFollowAll(user).catch(e => {});
|
await this.unFollowAll(user).catch(e => {});
|
||||||
await this.#readAllNotify(user).catch(e => {});
|
await this.readAllNotify(user).catch(e => {});
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #unFollowAll(follower: User) {
|
private async unFollowAll(follower: User) {
|
||||||
const followings = await this.followingsRepository.findBy({
|
const followings = await this.followingsRepository.findBy({
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
});
|
});
|
||||||
|
@ -95,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #readAllNotify(notifier: User) {
|
private async readAllNotify(notifier: User) {
|
||||||
await this.notificationsRepository.update({
|
await this.notificationsRepository.update({
|
||||||
notifierId: notifier.id,
|
notifierId: notifier.id,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
|
|
|
@ -100,7 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private apNoteService: ApNoteService,
|
private apNoteService: ApNoteService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const object = await this.#fetchAny(ps.uri, me);
|
const object = await this.fetchAny(ps.uri, me);
|
||||||
if (object) {
|
if (object) {
|
||||||
return object;
|
return object;
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,12 +112,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
/***
|
/***
|
||||||
* URIからUserかNoteを解決する
|
* URIからUserかNoteを解決する
|
||||||
*/
|
*/
|
||||||
async #fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
private async fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
const fetchedMeta = await this.metaService.fetch();
|
const fetchedMeta = await this.metaService.fetch();
|
||||||
if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null;
|
if (fetchedMeta.blockedHosts.includes(this.utilityService.extractDbHost(uri))) return null;
|
||||||
|
|
||||||
let local = await this.#mergePack(me, ...await Promise.all([
|
let local = await this.mergePack(me, ...await Promise.all([
|
||||||
this.apDbResolverService.getUserFromApId(uri),
|
this.apDbResolverService.getUserFromApId(uri),
|
||||||
this.apDbResolverService.getNoteFromApId(uri),
|
this.apDbResolverService.getNoteFromApId(uri),
|
||||||
]));
|
]));
|
||||||
|
@ -130,21 +130,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
|
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
|
||||||
// これはDBに存在する可能性があるため再度DB検索
|
// これはDBに存在する可能性があるため再度DB検索
|
||||||
if (uri !== object.id) {
|
if (uri !== object.id) {
|
||||||
local = await this.#mergePack(me, ...await Promise.all([
|
local = await this.mergePack(me, ...await Promise.all([
|
||||||
this.apDbResolverService.getUserFromApId(object.id),
|
this.apDbResolverService.getUserFromApId(object.id),
|
||||||
this.apDbResolverService.getNoteFromApId(object.id),
|
this.apDbResolverService.getNoteFromApId(object.id),
|
||||||
]));
|
]));
|
||||||
if (local != null) return local;
|
if (local != null) return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.#mergePack(
|
return await this.mergePack(
|
||||||
me,
|
me,
|
||||||
isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null,
|
isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null,
|
||||||
isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null,
|
isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
|
private async mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
return {
|
return {
|
||||||
type: 'User',
|
type: 'User',
|
||||||
|
|
|
@ -42,12 +42,12 @@ export class DiscordServerService {
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get('/disconnect/discord', async ctx => {
|
router.get('/disconnect/discord', async ctx => {
|
||||||
if (!this.#compareOrigin(ctx)) {
|
if (!this.compareOrigin(ctx)) {
|
||||||
ctx.throw(400, 'invalid origin');
|
ctx.throw(400, 'invalid origin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
if (!userToken) {
|
if (!userToken) {
|
||||||
ctx.throw(400, 'signin required');
|
ctx.throw(400, 'signin required');
|
||||||
return;
|
return;
|
||||||
|
@ -91,12 +91,12 @@ export class DiscordServerService {
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get('/connect/discord', async ctx => {
|
router.get('/connect/discord', async ctx => {
|
||||||
if (!this.#compareOrigin(ctx)) {
|
if (!this.compareOrigin(ctx)) {
|
||||||
ctx.throw(400, 'invalid origin');
|
ctx.throw(400, 'invalid origin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
if (!userToken) {
|
if (!userToken) {
|
||||||
ctx.throw(400, 'signin required');
|
ctx.throw(400, 'signin required');
|
||||||
return;
|
return;
|
||||||
|
@ -138,7 +138,7 @@ export class DiscordServerService {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/dc/cb', async ctx => {
|
router.get('/dc/cb', async ctx => {
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
|
|
||||||
const oauth2 = await getOAuth2();
|
const oauth2 = await getOAuth2();
|
||||||
|
|
||||||
|
@ -299,11 +299,11 @@ export class DiscordServerService {
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
#getUserToken(ctx: Koa.BaseContext): string | null {
|
private getUserToken(ctx: Koa.BaseContext): string | null {
|
||||||
return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
#compareOrigin(ctx: Koa.BaseContext): boolean {
|
private compareOrigin(ctx: Koa.BaseContext): boolean {
|
||||||
function normalizeUrl(url?: string): string {
|
function normalizeUrl(url?: string): string {
|
||||||
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,12 +42,12 @@ export class GithubServerService {
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get('/disconnect/github', async ctx => {
|
router.get('/disconnect/github', async ctx => {
|
||||||
if (!this.#compareOrigin(ctx)) {
|
if (!this.compareOrigin(ctx)) {
|
||||||
ctx.throw(400, 'invalid origin');
|
ctx.throw(400, 'invalid origin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
if (!userToken) {
|
if (!userToken) {
|
||||||
ctx.throw(400, 'signin required');
|
ctx.throw(400, 'signin required');
|
||||||
return;
|
return;
|
||||||
|
@ -91,12 +91,12 @@ export class GithubServerService {
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get('/connect/github', async ctx => {
|
router.get('/connect/github', async ctx => {
|
||||||
if (!this.#compareOrigin(ctx)) {
|
if (!this.compareOrigin(ctx)) {
|
||||||
ctx.throw(400, 'invalid origin');
|
ctx.throw(400, 'invalid origin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
if (!userToken) {
|
if (!userToken) {
|
||||||
ctx.throw(400, 'signin required');
|
ctx.throw(400, 'signin required');
|
||||||
return;
|
return;
|
||||||
|
@ -136,7 +136,7 @@ export class GithubServerService {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/gh/cb', async ctx => {
|
router.get('/gh/cb', async ctx => {
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
|
|
||||||
const oauth2 = await getOath2();
|
const oauth2 = await getOath2();
|
||||||
|
|
||||||
|
@ -271,11 +271,11 @@ export class GithubServerService {
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
#getUserToken(ctx: Koa.BaseContext): string | null {
|
private getUserToken(ctx: Koa.BaseContext): string | null {
|
||||||
return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
#compareOrigin(ctx: Koa.BaseContext): boolean {
|
private compareOrigin(ctx: Koa.BaseContext): boolean {
|
||||||
function normalizeUrl(url?: string): string {
|
function normalizeUrl(url?: string): string {
|
||||||
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,12 +42,12 @@ export class TwitterServerService {
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get('/disconnect/twitter', async ctx => {
|
router.get('/disconnect/twitter', async ctx => {
|
||||||
if (!this.#compareOrigin(ctx)) {
|
if (!this.compareOrigin(ctx)) {
|
||||||
ctx.throw(400, 'invalid origin');
|
ctx.throw(400, 'invalid origin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
if (userToken == null) {
|
if (userToken == null) {
|
||||||
ctx.throw(400, 'signin required');
|
ctx.throw(400, 'signin required');
|
||||||
return;
|
return;
|
||||||
|
@ -90,12 +90,12 @@ export class TwitterServerService {
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get('/connect/twitter', async ctx => {
|
router.get('/connect/twitter', async ctx => {
|
||||||
if (!this.#compareOrigin(ctx)) {
|
if (!this.compareOrigin(ctx)) {
|
||||||
ctx.throw(400, 'invalid origin');
|
ctx.throw(400, 'invalid origin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
if (userToken == null) {
|
if (userToken == null) {
|
||||||
ctx.throw(400, 'signin required');
|
ctx.throw(400, 'signin required');
|
||||||
return;
|
return;
|
||||||
|
@ -125,7 +125,7 @@ export class TwitterServerService {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/tw/cb', async ctx => {
|
router.get('/tw/cb', async ctx => {
|
||||||
const userToken = this.#getUserToken(ctx);
|
const userToken = this.getUserToken(ctx);
|
||||||
|
|
||||||
const twAuth = await getTwAuth();
|
const twAuth = await getTwAuth();
|
||||||
|
|
||||||
|
@ -214,11 +214,11 @@ export class TwitterServerService {
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
#getUserToken(ctx: Koa.BaseContext): string | null {
|
private getUserToken(ctx: Koa.BaseContext): string | null {
|
||||||
return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
return ((ctx.headers['cookie'] ?? '').match(/igi=(\w+)/) ?? [null, null])[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
#compareOrigin(ctx: Koa.BaseContext): boolean {
|
private compareOrigin(ctx: Koa.BaseContext): boolean {
|
||||||
function normalizeUrl(url?: string): string {
|
function normalizeUrl(url?: string): string {
|
||||||
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ export class ClientServerService {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async #manifestHandler(ctx: Koa.Context) {
|
private async manifestHandler(ctx: Koa.Context) {
|
||||||
// TODO
|
// TODO
|
||||||
//const res = structuredClone(manifest);
|
//const res = structuredClone(manifest);
|
||||||
const res = JSON.parse(JSON.stringify(manifest));
|
const res = JSON.parse(JSON.stringify(manifest));
|
||||||
|
@ -264,7 +264,7 @@ export class ClientServerService {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Manifest
|
// Manifest
|
||||||
router.get('/manifest.json', ctx => this.#manifestHandler(ctx));
|
router.get('/manifest.json', ctx => this.manifestHandler(ctx));
|
||||||
|
|
||||||
router.get('/robots.txt', async ctx => {
|
router.get('/robots.txt', async ctx => {
|
||||||
await send(ctx as any, '/robots.txt', {
|
await send(ctx as any, '/robots.txt', {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type Koa from 'koa';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UrlPreviewService {
|
export class UrlPreviewService {
|
||||||
#logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -25,10 +25,10 @@ export class UrlPreviewService {
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.#logger = this.loggerService.getLogger('url-preview');
|
this.logger = this.loggerService.getLogger('url-preview');
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrap(url?: string): string | null {
|
private wrap(url?: string): string | null {
|
||||||
return url != null
|
return url != null
|
||||||
? url.match(/^https?:\/\//)
|
? url.match(/^https?:\/\//)
|
||||||
? `${this.config.url}/proxy/preview.webp?${query({
|
? `${this.config.url}/proxy/preview.webp?${query({
|
||||||
|
@ -54,7 +54,7 @@ export class UrlPreviewService {
|
||||||
|
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
|
|
||||||
this.#logger.info(meta.summalyProxy
|
this.logger.info(meta.summalyProxy
|
||||||
? `(Proxy) Getting preview of ${url}@${lang} ...`
|
? `(Proxy) Getting preview of ${url}@${lang} ...`
|
||||||
: `Getting preview of ${url}@${lang} ...`);
|
: `Getting preview of ${url}@${lang} ...`);
|
||||||
|
|
||||||
|
@ -67,17 +67,17 @@ export class UrlPreviewService {
|
||||||
lang: lang ?? 'ja-JP',
|
lang: lang ?? 'ja-JP',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#logger.succ(`Got preview of ${url}: ${summary.title}`);
|
this.logger.succ(`Got preview of ${url}: ${summary.title}`);
|
||||||
|
|
||||||
summary.icon = this.#wrap(summary.icon);
|
summary.icon = this.wrap(summary.icon);
|
||||||
summary.thumbnail = this.#wrap(summary.thumbnail);
|
summary.thumbnail = this.wrap(summary.thumbnail);
|
||||||
|
|
||||||
// Cache 7days
|
// Cache 7days
|
||||||
ctx.set('Cache-Control', 'max-age=604800, immutable');
|
ctx.set('Cache-Control', 'max-age=604800, immutable');
|
||||||
|
|
||||||
ctx.body = summary;
|
ctx.body = summary;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#logger.warn(`Failed to get preview of ${url}: ${err}`);
|
this.logger.warn(`Failed to get preview of ${url}: ${err}`);
|
||||||
ctx.status = 200;
|
ctx.status = 200;
|
||||||
ctx.set('Cache-Control', 'max-age=86400, immutable');
|
ctx.set('Cache-Control', 'max-age=86400, immutable');
|
||||||
ctx.body = '{}';
|
ctx.body = '{}';
|
||||||
|
|
Loading…
Reference in a new issue