cleanup(ApNoteService.ts)
				
					
				
			This commit is contained in:
		| @@ -54,7 +54,7 @@ export class ApNoteService { | |||||||
| 		// 循環参照のため / for circular dependency | 		// 循環参照のため / for circular dependency | ||||||
| 		@Inject(forwardRef(() => ApPersonService)) | 		@Inject(forwardRef(() => ApPersonService)) | ||||||
| 		private apPersonService: ApPersonService, | 		private apPersonService: ApPersonService, | ||||||
| 	 |  | ||||||
| 		private utilityService: UtilityService, | 		private utilityService: UtilityService, | ||||||
| 		private apAudienceService: ApAudienceService, | 		private apAudienceService: ApAudienceService, | ||||||
| 		private apMentionService: ApMentionService, | 		private apMentionService: ApMentionService, | ||||||
| @@ -73,15 +73,15 @@ export class ApNoteService { | |||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public validateNote(object: IObject, uri: string) { | 	public validateNote(object: IObject, uri: string) { | ||||||
| 		const expectHost = this.utilityService.extractDbHost(uri); | 		const expectHost = this.utilityService.extractDbHost(uri); | ||||||
| 	 |  | ||||||
| 		if (object == null) { | 		if (object == null) { | ||||||
| 			return new Error('invalid Note: object is null'); | 			return new Error('invalid Note: object is null'); | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		if (!validPost.includes(getApType(object))) { | 		if (!validPost.includes(getApType(object))) { | ||||||
| 			return new Error(`invalid Note: invalid object type ${getApType(object)}`); | 			return new Error(`invalid Note: invalid object type ${getApType(object)}`); | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { | 		if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { | ||||||
| 			return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); | 			return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); | ||||||
| 		} | 		} | ||||||
| @@ -90,10 +90,10 @@ export class ApNoteService { | |||||||
| 		if (object.attributedTo && actualHost !== expectHost) { | 		if (object.attributedTo && actualHost !== expectHost) { | ||||||
| 			return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`); | 			return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`); | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		return null; | 		return null; | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Noteをフェッチします。 | 	 * Noteをフェッチします。 | ||||||
| 	 * | 	 * | ||||||
| @@ -103,16 +103,16 @@ export class ApNoteService { | |||||||
| 	public async fetchNote(object: string | IObject): Promise<Note | null> { | 	public async fetchNote(object: string | IObject): Promise<Note | null> { | ||||||
| 		return await this.apDbResolverService.getNoteFromApId(object); | 		return await this.apDbResolverService.getNoteFromApId(object); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Noteを作成します。 | 	 * Noteを作成します。 | ||||||
| 	 */ | 	 */ | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> { | 	public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> { | ||||||
| 		if (resolver == null) resolver = this.apResolverService.createResolver(); | 		if (resolver == null) resolver = this.apResolverService.createResolver(); | ||||||
| 	 |  | ||||||
| 		const object = await resolver.resolve(value); | 		const object = await resolver.resolve(value); | ||||||
| 	 |  | ||||||
| 		const entryUri = getApId(value); | 		const entryUri = getApId(value); | ||||||
| 		const err = this.validateNote(object, entryUri); | 		const err = this.validateNote(object, entryUri); | ||||||
| 		if (err) { | 		if (err) { | ||||||
| @@ -125,9 +125,9 @@ export class ApNoteService { | |||||||
| 			}); | 			}); | ||||||
| 			throw new Error('invalid note'); | 			throw new Error('invalid note'); | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		const note = object as IPost; | 		const note = object as IPost; | ||||||
| 	 |  | ||||||
| 		this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); | 		this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); | ||||||
|  |  | ||||||
| 		if (note.id && !checkHttps(note.id)) { | 		if (note.id && !checkHttps(note.id)) { | ||||||
| @@ -139,21 +139,21 @@ export class ApNoteService { | |||||||
| 		if (url && !checkHttps(url)) { | 		if (url && !checkHttps(url)) { | ||||||
| 			throw new Error('unexpected shcema of note url: ' + url); | 			throw new Error('unexpected shcema of note url: ' + url); | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		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 RemoteUser; | 		const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo!), resolver) as RemoteUser; | ||||||
| 	 |  | ||||||
| 		// 投稿者が凍結されていたらスキップ | 		// 投稿者が凍結されていたらスキップ | ||||||
| 		if (actor.isSuspended) { | 		if (actor.isSuspended) { | ||||||
| 			throw new Error('actor has been suspended'); | 			throw new Error('actor has been suspended'); | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); | 		const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); | ||||||
| 		let visibility = noteAudience.visibility; | 		let visibility = noteAudience.visibility; | ||||||
| 		const visibleUsers = noteAudience.visibleUsers; | 		const visibleUsers = noteAudience.visibleUsers; | ||||||
| 	 |  | ||||||
| 		// Audience (to, cc) が指定されてなかった場合 | 		// Audience (to, cc) が指定されてなかった場合 | ||||||
| 		if (visibility === 'specified' && visibleUsers.length === 0) { | 		if (visibility === 'specified' && visibleUsers.length === 0) { | ||||||
| 			if (typeof value === 'string') {	// 入力がstringならばresolverでGETが発生している | 			if (typeof value === 'string') {	// 入力がstringならばresolverでGETが発生している | ||||||
| @@ -161,23 +161,23 @@ export class ApNoteService { | |||||||
| 				visibility = 'public'; | 				visibility = 'public'; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); | 		const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); | ||||||
| 		const apHashtags = await extractApHashtags(note.tag); | 		const apHashtags = await extractApHashtags(note.tag); | ||||||
| 	 |  | ||||||
| 		// 添付ファイル | 		// 添付ファイル | ||||||
| 		// TODO: attachmentは必ずしもImageではない | 		// TODO: attachmentは必ずしもImageではない | ||||||
| 		// TODO: attachmentは必ずしも配列ではない | 		// TODO: attachmentは必ずしも配列ではない | ||||||
| 		// Noteがsensitiveなら添付もsensitiveにする | 		// Noteがsensitiveなら添付もsensitiveにする | ||||||
| 		const limit = promiseLimit(2); | 		const limit = promiseLimit(2); | ||||||
| 	 |  | ||||||
| 		note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; | 		note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; | ||||||
| 		const files = note.attachment | 		const files = note.attachment | ||||||
| 			.map(attach => attach.sensitive = note.sensitive) | 			.map(attach => attach.sensitive = note.sensitive) | ||||||
| 			? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise<DriveFile>))) | 			? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise<DriveFile>))) | ||||||
| 				.filter(image => image != null) | 				.filter(image => image != null) | ||||||
| 			: []; | 			: []; | ||||||
| 	 |  | ||||||
| 		// リプライ | 		// リプライ | ||||||
| 		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 => { | ||||||
| @@ -192,10 +192,10 @@ export class ApNoteService { | |||||||
| 				throw err; | 				throw err; | ||||||
| 			}) | 			}) | ||||||
| 			: null; | 			: null; | ||||||
| 	 |  | ||||||
| 		// 引用 | 		// 引用 | ||||||
| 		let quote: Note | undefined | null; | 		let quote: Note | undefined | null; | ||||||
| 	 |  | ||||||
| 		if (note._misskey_quote || note.quoteUrl) { | 		if (note._misskey_quote || note.quoteUrl) { | ||||||
| 			const tryResolveNote = async (uri: string): Promise<{ | 			const tryResolveNote = async (uri: string): Promise<{ | ||||||
| 				status: 'ok'; | 				status: 'ok'; | ||||||
| @@ -222,10 +222,10 @@ export class ApNoteService { | |||||||
| 					}; | 					}; | ||||||
| 				} | 				} | ||||||
| 			}; | 			}; | ||||||
| 	 |  | ||||||
| 			const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); | 			const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string')); | ||||||
| 			const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); | 			const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); | ||||||
| 	 |  | ||||||
| 			quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); | 			quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); | ||||||
| 			if (!quote) { | 			if (!quote) { | ||||||
| 				if (results.some(x => x.status === 'temperror')) { | 				if (results.some(x => x.status === 'temperror')) { | ||||||
| @@ -233,9 +233,9 @@ export class ApNoteService { | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		const cw = note.summary === '' ? null : note.summary; | 		const cw = note.summary === '' ? null : note.summary; | ||||||
| 	 |  | ||||||
| 		// テキストのパース | 		// テキストのパース | ||||||
| 		let text: string | null = null; | 		let text: string | null = null; | ||||||
| 		if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { | 		if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { | ||||||
| @@ -245,38 +245,38 @@ export class ApNoteService { | |||||||
| 		} else if (typeof note.content === 'string') { | 		} else if (typeof note.content === 'string') { | ||||||
| 			text = this.apMfmService.htmlToMfm(note.content, note.tag); | 			text = this.apMfmService.htmlToMfm(note.content, note.tag); | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		// vote | 		// vote | ||||||
| 		if (reply && reply.hasPoll) { | 		if (reply && reply.hasPoll) { | ||||||
| 			const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); | 			const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); | ||||||
| 	 |  | ||||||
| 			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配信 | ||||||
| 					this.pollService.deliverQuestionUpdate(reply.id); | 					this.pollService.deliverQuestionUpdate(reply.id); | ||||||
| 				} | 				} | ||||||
| 				return null; | 				return null; | ||||||
| 			}; | 			}; | ||||||
| 	 |  | ||||||
| 			if (note.name) { | 			if (note.name) { | ||||||
| 				return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); | 				return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	 |  | ||||||
| 		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[]; | ||||||
| 		}); | 		}); | ||||||
| 	 |  | ||||||
| 		const apEmojis = emojis.map(emoji => emoji.name); | 		const apEmojis = emojis.map(emoji => emoji.name); | ||||||
| 	 |  | ||||||
| 		const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); | 		const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); | ||||||
| 		 |  | ||||||
| 		return await this.noteCreateService.create(actor, { | 		return await this.noteCreateService.create(actor, { | ||||||
| 			createdAt: note.published ? new Date(note.published) : null, | 			createdAt: note.published ? new Date(note.published) : null, | ||||||
| 			files, | 			files, | ||||||
| @@ -296,7 +296,7 @@ export class ApNoteService { | |||||||
| 			url: url, | 			url: url, | ||||||
| 		}, silent); | 		}, silent); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Noteを解決します。 | 	 * Noteを解決します。 | ||||||
| 	 * | 	 * | ||||||
| @@ -307,26 +307,26 @@ export class ApNoteService { | |||||||
| 	public async resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> { | 	public async resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> { | ||||||
| 		const uri = typeof value === 'string' ? value : value.id; | 		const uri = typeof value === 'string' ? value : value.id; | ||||||
| 		if (uri == null) throw new Error('missing uri'); | 		if (uri == null) throw new Error('missing uri'); | ||||||
| 	 |  | ||||||
| 		// ブロックしてたら中断 | 		// ブロックしてたら中断 | ||||||
| 		const meta = await this.metaService.fetch(); | 		const meta = await this.metaService.fetch(); | ||||||
| 		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451); | 		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451); | ||||||
| 	 |  | ||||||
| 		const unlock = await this.appLockService.getApLock(uri); | 		const unlock = await this.appLockService.getApLock(uri); | ||||||
| 	 |  | ||||||
| 		try { | 		try { | ||||||
| 			//#region このサーバーに既に登録されていたらそれを返す | 			//#region このサーバーに既に登録されていたらそれを返す | ||||||
| 			const exist = await this.fetchNote(uri); | 			const exist = await this.fetchNote(uri); | ||||||
| 	 |  | ||||||
| 			if (exist) { | 			if (exist) { | ||||||
| 				return exist; | 				return exist; | ||||||
| 			} | 			} | ||||||
| 			//#endregion | 			//#endregion | ||||||
| 	 |  | ||||||
| 			if (uri.startsWith(this.config.url)) { | 			if (uri.startsWith(this.config.url)) { | ||||||
| 				throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); | 				throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); | ||||||
| 			} | 			} | ||||||
| 	 |  | ||||||
| 			// リモートサーバーからフェッチしてきて登録 | 			// リモートサーバーからフェッチしてきて登録 | ||||||
| 			// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが | 			// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが | ||||||
| 			// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 | 			// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 | ||||||
| @@ -335,26 +335,26 @@ export class ApNoteService { | |||||||
| 			unlock(); | 			unlock(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public async extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> { | 	public async extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> { | ||||||
| 		host = this.utilityService.toPuny(host); | 		host = this.utilityService.toPuny(host); | ||||||
| 	 |  | ||||||
| 		if (!tags) return []; | 		if (!tags) return []; | ||||||
| 	 |  | ||||||
| 		const eomjiTags = toArray(tags).filter(isEmoji); | 		const eomjiTags = toArray(tags).filter(isEmoji); | ||||||
|  |  | ||||||
| 		const existingEmojis = await this.emojisRepository.findBy({ | 		const existingEmojis = await this.emojisRepository.findBy({ | ||||||
| 			host, | 			host, | ||||||
| 			name: In(eomjiTags.map(tag => tag.name!.replaceAll(':', ''))), | 			name: In(eomjiTags.map(tag => tag.name!.replaceAll(':', ''))), | ||||||
| 		}); | 		}); | ||||||
| 	 |  | ||||||
| 		return await Promise.all(eomjiTags.map(async tag => { | 		return await Promise.all(eomjiTags.map(async tag => { | ||||||
| 			const name = tag.name!.replaceAll(':', ''); | 			const name = tag.name!.replaceAll(':', ''); | ||||||
| 			tag.icon = toSingle(tag.icon); | 			tag.icon = toSingle(tag.icon); | ||||||
| 	 |  | ||||||
| 			const exists = existingEmojis.find(x => x.name === name); | 			const exists = existingEmojis.find(x => x.name === name); | ||||||
| 	 |  | ||||||
| 			if (exists) { | 			if (exists) { | ||||||
| 				if ((tag.updated != null && exists.updatedAt == null) | 				if ((tag.updated != null && exists.updatedAt == null) | ||||||
| 					|| (tag.id != null && exists.uri == null) | 					|| (tag.id != null && exists.uri == null) | ||||||
| @@ -370,18 +370,18 @@ export class ApNoteService { | |||||||
| 						publicUrl: tag.icon!.url, | 						publicUrl: tag.icon!.url, | ||||||
| 						updatedAt: new Date(), | 						updatedAt: new Date(), | ||||||
| 					}); | 					}); | ||||||
| 	 |  | ||||||
| 					return await this.emojisRepository.findOneBy({ | 					return await this.emojisRepository.findOneBy({ | ||||||
| 						host, | 						host, | ||||||
| 						name, | 						name, | ||||||
| 					}) as Emoji; | 					}) as Emoji; | ||||||
| 				} | 				} | ||||||
| 	 |  | ||||||
| 				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(), | ||||||
| 				host, | 				host, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 okayurisotto
					okayurisotto