/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ import { URL } from 'node:url'; import { toASCII } from 'punycode'; import { Inject, Injectable } from '@nestjs/common'; import RE2 from 're2'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; @Injectable() export class UtilityService { constructor( @Inject(DI.config) private config: Config, ) { } @bindThis public getFullApAccount(username: string, host: string | null): string { return host ? `${username}@${this.toPuny(host)}` : `${username}@${this.toPuny(this.config.host)}`; } @bindThis public isSelfHost(host: string | null): boolean { if (host == null) return true; return this.toPuny(this.config.host) === this.toPuny(host); } @bindThis public isBlockedHost(blockedHosts: string[], host: string | null): boolean { if (host == null) return false; return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } @bindThis public isSilencedHost(silencedHosts: string[] | undefined, host: string | null): boolean { if (!silencedHosts || host == null) return false; return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } @bindThis public concatNoteContentsForKeyWordCheck(content: { cw?: string | null; text?: string | null; pollChoices?: string[] | null; others?: string[] | null; }): string { /** * ノートの内容を結合してキーワードチェック用の文字列を生成する * cwとtextは内容が繋がっているかもしれないので間に何も入れずにチェックする */ return `${content.cw ?? ''}${content.text ?? ''}\n${(content.pollChoices ?? []).join('\n')}\n${(content.others ?? []).join('\n')}`; } @bindThis public isKeyWordIncluded(text: string, keyWords: string[]): boolean { if (keyWords.length === 0) return false; if (text === '') return false; const regexpregexp = /^\/(.+)\/(.*)$/; const matched = keyWords.some(filter => { // represents RegExp const regexp = filter.match(regexpregexp); // This should never happen due to input sanitisation. if (!regexp) { const words = filter.split(' '); return words.every(keyword => text.includes(keyword)); } try { // TODO: RE2インスタンスをキャッシュ return new RE2(regexp[1], regexp[2]).test(text); } catch (err) { // This should never happen due to input sanitisation. return false; } }); return matched; } @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); return this.toPuny(url.host); } @bindThis public toPuny(host: string): string { return toASCII(host.toLowerCase()); } @bindThis public toPunyNullable(host: string | null | undefined): string | null { if (host == null) return null; return toASCII(host.toLowerCase()); } @bindThis public punyHost(url: string): string { const urlObj = new URL(url); const host = `${this.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`; return host; } }