diff options
| author | okayurisotto <okayurisotto@proton.me> | 2023-07-08 07:08:16 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-08 07:08:16 +0900 |
| commit | d84796588c1472334ddaf696a817f015c245ce44 (patch) | |
| tree | 45caa2d8659d35b90bc3f36170e764aa5eb90b70 /packages/backend/src/core | |
| parent | perf(backend): Improve performance of FetchInstanceMetadata (#11128) (diff) | |
| download | sharkey-d84796588c1472334ddaf696a817f015c245ce44.tar.gz sharkey-d84796588c1472334ddaf696a817f015c245ce44.tar.bz2 sharkey-d84796588c1472334ddaf696a817f015c245ce44.zip | |
cleanup: trim trailing whitespace (#11136)
* cleanup: trim trailing whitespace
* update(`.editorconfig`)
---------
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages/backend/src/core')
44 files changed, 402 insertions, 400 deletions
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index ab11785e28..69d83b13b0 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -295,7 +295,7 @@ export class AccountMoveService { * dstユーザーのalsoKnownAsをfetchPersonしていき、本当にmovedToUrlをdstに指定するユーザーが存在するのかを調べる * * @param dst movedToUrlを指定するユーザー - * @param check + * @param check * @param instant checkがtrueであるユーザーが最初に見つかったら即座にreturnするかどうか * @returns Promise<LocalUser | RemoteUser | null> */ diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 059e335eff..02501b832b 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -31,16 +31,16 @@ export class AiService { const cpuFlags = await this.getCpuFlags(); isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); } - + if (!isSupportedCpu) { console.error('This CPU cannot use TensorFlow.'); return null; } - + const tf = await import('@tensorflow/tfjs-node'); - + if (this.model == null) this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); - + const buffer = await fs.promises.readFile(path); const image = await tf.node.decodeImage(buffer, 3) as any; try { diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index d8df371916..9310fd8b52 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -99,7 +99,7 @@ export class AntennaService implements OnApplicationShutdown { 'MAXLEN', '~', '200', '*', 'note', note.id); - + this.globalEventService.publishAntennaStream(antenna.id, 'note', note); } @@ -112,16 +112,16 @@ export class AntennaService implements OnApplicationShutdown { public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise<boolean> { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; - + if (!antenna.withReplies && note.replyId != null) return false; - + if (antenna.src === 'home') { // TODO } else if (antenna.src === 'list') { const listUsers = (await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId!, })).map(x => x.userId); - + if (!listUsers.includes(note.userId)) return false; } else if (antenna.src === 'users') { const accts = antenna.users.map(x => { @@ -130,32 +130,32 @@ export class AntennaService implements OnApplicationShutdown { }); if (!accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false; } - + const keywords = antenna.keywords // Clean up .map(xs => xs.filter(x => x !== '')) .filter(xs => xs.length > 0); - + if (keywords.length > 0) { if (note.text == null && note.cw == null) return false; const _text = (note.text ?? '') + '\n' + (note.cw ?? ''); - + const matched = keywords.some(and => and.every(keyword => antenna.caseSensitive ? _text.includes(keyword) : _text.toLowerCase().includes(keyword.toLowerCase()), )); - + if (!matched) return false; } - + const excludeKeywords = antenna.excludeKeywords // Clean up .map(xs => xs.filter(x => x !== '')) .filter(xs => xs.length > 0); - + if (excludeKeywords.length > 0) { if (note.text == null && note.cw == null) return false; @@ -167,16 +167,16 @@ export class AntennaService implements OnApplicationShutdown { ? _text.includes(keyword) : _text.toLowerCase().includes(keyword.toLowerCase()), )); - + if (matched) return false; } - + if (antenna.withFile) { if (note.fileIds && note.fileIds.length === 0) return false; } - + // TODO: eval expression - + return true; } @@ -188,7 +188,7 @@ export class AntennaService implements OnApplicationShutdown { }); this.antennasFetched = true; } - + return this.antennas; } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 1a52a229c5..10cfdba254 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -20,7 +20,7 @@ export class CaptchaService { secret, response, }); - + const res = await this.httpRequestService.send(url, { method: 'POST', body: params.toString(), @@ -28,14 +28,14 @@ export class CaptchaService { 'Content-Type': 'application/x-www-form-urlencoded', }, }, { throwErrorWhenResponseNotOk: false }); - + if (!res.ok) { throw new Error(`${res.status}`); } - + return await res.json() as CaptchaResponse; - } - + } + @bindThis public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> { if (response == null) { @@ -73,7 +73,7 @@ export class CaptchaService { if (response == null) { throw new Error('turnstile-failed: no response provided'); } - + const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => { throw new Error(`turnstile-request-failed: ${err}`); }); diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 8f887d90f9..0bfbe2b173 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -25,27 +25,27 @@ export class CreateSystemUserService { @bindThis public async createSystemUser(username: string): Promise<User> { const password = uuid(); - + // Generate hash of password const salt = await bcrypt.genSalt(8); const hash = await bcrypt.hash(password, salt); - + // Generate secret const secret = generateNativeUserToken(); - + const keyPair = await genRsaKeyPair(4096); - + let account!: User; - + // Start transaction await this.db.transaction(async transactionalEntityManager => { const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), host: IsNull(), }); - + if (exist) throw new Error('the user is already exists'); - + account = await transactionalEntityManager.insert(User, { id: this.idService.genId(), createdAt: new Date(), @@ -58,25 +58,25 @@ export class CreateSystemUserService { isExplorable: false, isBot: true, }).then(x => transactionalEntityManager.findOneByOrFail(User, x.identifiers[0])); - + await transactionalEntityManager.insert(UserKeypair, { publicKey: keyPair.publicKey, privateKey: keyPair.privateKey, userId: account.id, }); - + await transactionalEntityManager.insert(UserProfile, { userId: account.id, autoAcceptFollowed: false, password: hash, }); - + await transactionalEntityManager.insert(UsedUsername, { createdAt: new Date(), username: username.toLowerCase(), }); }); - + return account; } } diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 5f2ced77eb..661d956bd6 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -140,7 +140,7 @@ export class CustomEmojiService implements OnApplicationShutdown { this.globalEventService.publishBroadcastStream('emojiAdded', { emoji: updated, - }); + }); } } @@ -194,7 +194,7 @@ export class CustomEmojiService implements OnApplicationShutdown { } this.localEmojisCache.refresh(); - + this.globalEventService.publishBroadcastStream('emojiUpdated', { emojis: await this.emojiEntityService.packDetailedMany(ids), }); @@ -215,7 +215,7 @@ export class CustomEmojiService implements OnApplicationShutdown { emojis: await this.emojiEntityService.packDetailedMany(ids), }); } - + @bindThis public async setLicenseBulk(ids: Emoji['id'][], license: string | null) { await this.emojisRepository.update({ diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index 327283106f..3a0592441b 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -28,11 +28,11 @@ export class DeleteAccountService { // 物理削除する前にDelete activityを送信する await this.userSuspendService.doPostSuspend(user).catch(e => {}); - + this.queueService.createDeleteAccountJob(user, { soft: false, }); - + await this.usersRepository.update(user.id, { isDeleted: true, }); diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 59932a5b88..a04e9c1225 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -29,12 +29,12 @@ export class EmailService { @bindThis public async sendEmail(to: string, subject: string, html: string, text: string) { const meta = await this.metaService.fetch(true); - + const iconUrl = `${this.config.url}/static-assets/mi-white.png`; const emailSettingUrl = `${this.config.url}/settings/email`; - + const enableAuth = meta.smtpUser != null && meta.smtpUser !== ''; - + const transporter = nodemailer.createTransport({ host: meta.smtpHost, port: meta.smtpPort, @@ -46,7 +46,7 @@ export class EmailService { pass: meta.smtpPass, } : undefined, } as any); - + try { // TODO: htmlサニタイズ const info = await transporter.sendMail({ @@ -135,7 +135,7 @@ export class EmailService { </body> </html>`, }); - + this.logger.info(`Message sent: ${info.messageId}`); } catch (err) { this.logger.error(err as Error); @@ -149,12 +149,12 @@ export class EmailService { reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp'; }> { const meta = await this.metaService.fetch(); - + const exist = await this.userProfilesRepository.countBy({ emailVerified: true, email: emailAddress, }); - + const validated = meta.enableActiveEmailValidation ? await validateEmail({ email: emailAddress, validateRegex: true, @@ -163,9 +163,9 @@ export class EmailService { validateDisposable: true, // 捨てアドかどうかチェック validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので }) : { valid: true, reason: null }; - + const available = exist === 0 && validated.valid; - + return { available, reason: available ? null : diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 3603d59dcc..a762038942 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -43,19 +43,19 @@ export class FederatedInstanceService implements OnApplicationShutdown { @bindThis public async fetch(host: string): Promise<Instance> { host = this.utilityService.toPuny(host); - + const cached = await this.federatedInstanceCache.get(host); if (cached) return cached; - + const index = await this.instancesRepository.findOneBy({ host }); - + if (index == null) { const i = await this.instancesRepository.insert({ id: this.idService.genId(), host, firstRetrievedAt: new Date(), }).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0])); - + this.federatedInstanceCache.set(host, i); return i; } else { @@ -74,7 +74,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { .then((response) => { return response.raw[0]; }); - + this.federatedInstanceCache.set(result.host, result); } diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 96ee77d056..127c4c3155 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -58,6 +58,8 @@ export class FetchInstanceMetadataService { @bindThis public async fetchInstanceMetadata(instance: Instance, force = false): Promise<void> { + try { + const host = instance.host; // Acquire mutex to ensure no parallel runs if (!await this.tryLock(host)) return; @@ -72,13 +74,13 @@ export class FetchInstanceMetadataService { } this.logger.info(`Fetching metadata of ${instance.host} ...`); - + const [info, dom, manifest] = await Promise.all([ this.fetchNodeinfo(instance).catch(() => null), this.fetchDom(instance).catch(() => null), this.fetchManifest(instance).catch(() => null), ]); - + const [favicon, icon, themeColor, name, description] = await Promise.all([ this.fetchFaviconUrl(instance, dom).catch(() => null), this.fetchIconUrl(instance, dom, manifest).catch(() => null), @@ -86,13 +88,13 @@ export class FetchInstanceMetadataService { this.getSiteName(info, dom, manifest).catch(() => null), this.getDescription(info, dom, manifest).catch(() => null), ]); - + this.logger.succ(`Successfuly fetched metadata of ${instance.host}`); - + const updates = { infoUpdatedAt: new Date(), } as Record<string, any>; - + if (info) { updates.softwareName = typeof info.software?.name === 'string' ? info.software.name.toLowerCase() : '?'; updates.softwareVersion = info.software?.version; @@ -100,15 +102,15 @@ export class FetchInstanceMetadataService { updates.maintainerName = info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.name ?? null) : null : null; updates.maintainerEmail = info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.email ?? null) : null : null; } - + if (name) updates.name = name; if (description) updates.description = description; if (icon || favicon) updates.iconUrl = icon ?? favicon; if (favicon) updates.faviconUrl = favicon; if (themeColor) updates.themeColor = themeColor; - + await this.federatedInstanceService.update(instance.id, updates); - + this.logger.succ(`Successfuly updated metadata of ${instance.host}`); } catch (e) { this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`); @@ -120,7 +122,7 @@ export class FetchInstanceMetadataService { @bindThis private async fetchNodeinfo(instance: Instance): Promise<NodeInfo> { this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); - + try { const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo') .catch(err => { @@ -130,33 +132,33 @@ export class FetchInstanceMetadataService { throw err.statusCode ?? err.message; } }) as Record<string, unknown>; - + if (wellknown.links == null || !Array.isArray(wellknown.links)) { throw new Error('No wellknown links'); } - + const links = wellknown.links as any[]; - + const lnik1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0'); const lnik2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0'); const lnik2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1'); const link = lnik2_1 ?? lnik2_0 ?? lnik1_0; - + if (link == null) { throw new Error('No nodeinfo link provided'); } - + const info = await this.httpRequestService.getJson(link.href) .catch(err => { throw err.statusCode ?? err.message; }); - + this.logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); - + return info as NodeInfo; } catch (err) { this.logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${err}`); - + throw err; } } @@ -164,51 +166,51 @@ export class FetchInstanceMetadataService { @bindThis private async fetchDom(instance: Instance): Promise<DOMWindow['document']> { this.logger.info(`Fetching HTML of ${instance.host} ...`); - + const url = 'https://' + instance.host; - + const html = await this.httpRequestService.getHtml(url); - + const { window } = new JSDOM(html); const doc = window.document; - + return doc; } @bindThis private async fetchManifest(instance: Instance): Promise<Record<string, unknown> | null> { const url = 'https://' + instance.host; - + const manifestUrl = url + '/manifest.json'; - + const manifest = await this.httpRequestService.getJson(manifestUrl) as Record<string, unknown>; - + return manifest; } @bindThis private async fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise<string | null> { const url = 'https://' + instance.host; - + if (doc) { // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.relList.contains('icon'))?.href; - + if (href) { return (new URL(href, url)).href; } } - + const faviconUrl = url + '/favicon.ico'; - + const favicon = await this.httpRequestService.send(faviconUrl, { method: 'HEAD', }, { throwErrorWhenResponseNotOk: false }); - + if (favicon.ok) { return faviconUrl; } - + return null; } @@ -218,38 +220,38 @@ export class FetchInstanceMetadataService { const url = 'https://' + instance.host; return (new URL(manifest.icons[0].src, url)).href; } - + if (doc) { const url = 'https://' + instance.host; - + // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 const links = Array.from(doc.getElementsByTagName('link')).reverse(); // https://github.com/misskey-dev/misskey/pull/8220/files/0ec4eba22a914e31b86874f12448f88b3e58dd5a#r796487559 - const href = + const href = [ links.find(link => link.relList.contains('apple-touch-icon-precomposed'))?.href, links.find(link => link.relList.contains('apple-touch-icon'))?.href, links.find(link => link.relList.contains('icon'))?.href, ] .find(href => href); - + if (href) { return (new URL(href, url)).href; } } - + return null; } @bindThis 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; - + if (themeColor) { const color = new tinycolor(themeColor); if (color.isValid()) return color.toHexString(); } - + return null; } @@ -262,19 +264,19 @@ export class FetchInstanceMetadataService { return info.metadata.name; } } - + if (doc) { const og = doc.querySelector('meta[property="og:title"]')?.getAttribute('content'); - + if (og) { return og; } } - + if (manifest) { return manifest.name ?? manifest.short_name; } - + return null; } @@ -287,23 +289,23 @@ export class FetchInstanceMetadataService { return info.metadata.description; } } - + if (doc) { const meta = doc.querySelector('meta[name="description"]')?.getAttribute('content'); if (meta) { return meta; } - + const og = doc.querySelector('meta[property="og:description"]')?.getAttribute('content'); if (og) { return og; } } - + if (manifest) { return manifest.name ?? manifest.short_name; } - + return null; } } diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index d6c51ad559..d43575b336 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -161,20 +161,20 @@ export class FileInfoService { private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> { let sensitive = false; let porn = false; - + function judgePrediction(result: readonly predictionType[]): [sensitive: boolean, porn: boolean] { let sensitive = false; let porn = false; - + if ((result.find(x => x.className === 'Sexy')?.probability ?? 0) > sensitiveThreshold) sensitive = true; if ((result.find(x => x.className === 'Hentai')?.probability ?? 0) > sensitiveThreshold) sensitive = true; if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThreshold) sensitive = true; - + if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThresholdForPorn) porn = true; - + return [sensitive, porn]; } - + if ([ 'image/jpeg', 'image/png', @@ -253,10 +253,10 @@ export class FileInfoService { disposeOutDir(); } } - + return [sensitive, porn]; } - + private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator<string, void> { const watcher = new FSWatcher({ cwd, @@ -295,7 +295,7 @@ export class FileInfoService { } } } - + @bindThis private exists(path: string): Promise<boolean> { return fs.promises.access(path).then(() => true, () => false); diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 375aa846cb..4f2c261140 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -42,21 +42,21 @@ export class HttpRequestService { errorTtl: 30, // 30secs lookup: false, // nativeのdns.lookupにfallbackしない }); - + this.http = new http.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup, } as http.AgentOptions); - + this.https = new https.Agent({ keepAlive: true, keepAliveMsecs: 30 * 1000, lookup: cache.lookup, } as https.AgentOptions); - + const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128); - + this.httpAgent = config.proxy ? new HttpProxyAgent({ keepAlive: true, diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 60098bc81c..4d129407cb 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -23,7 +23,7 @@ export class IdService { @bindThis public genId(date?: Date): string { if (!date || (date > new Date())) date = new Date(); - + switch (this.method) { case 'aid': return genAid(date); case 'meid': return genMeid(date); diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index 4fb3fc5b4f..2e047dc5c1 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -26,12 +26,12 @@ export class InstanceActorService { public async getInstanceActor(): Promise<LocalUser> { const cached = this.cache.get(); if (cached) return cached; - + const user = await this.usersRepository.findOneBy({ host: IsNull(), username: ACTOR_USERNAME, }) as LocalUser | undefined; - + if (user) { this.cache.set(user); return user; diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 5acc9ad9ad..aae0a9134b 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -56,7 +56,7 @@ export class MetaService implements OnApplicationShutdown { @bindThis public async fetch(noCache = false): Promise<Meta> { if (!noCache && this.cache) return this.cache; - + return await this.db.transaction(async transactionalEntityManager => { // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する const metas = await transactionalEntityManager.find(Meta, { @@ -64,9 +64,9 @@ export class MetaService implements OnApplicationShutdown { id: 'DESC', }, }); - + const meta = metas[0]; - + if (meta) { this.cache = meta; return meta; @@ -81,7 +81,7 @@ export class MetaService implements OnApplicationShutdown { ['id'], ) .then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0])); - + this.cache = saved; return saved; } diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index dffee16e08..38aaa84524 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -27,29 +27,29 @@ export class MfmService { public fromHtml(html: string, hashtagNames?: string[]): string { // some AP servers like Pixelfed use br tags as well as newlines html = html.replace(/<br\s?\/?>\r?\n/gi, '\n'); - + const dom = parse5.parseFragment(html); - + let text = ''; - + for (const n of dom.childNodes) { analyze(n); } - + return text.trim(); - + function getText(node: TreeAdapter.Node): string { if (treeAdapter.isTextNode(node)) return node.value; if (!treeAdapter.isElementNode(node)) return ''; if (node.nodeName === 'br') return '\n'; - + if (node.childNodes) { return node.childNodes.map(n => getText(n)).join(''); } - + return ''; } - + function appendChildren(childNodes: TreeAdapter.ChildNode[]): void { if (childNodes) { for (const n of childNodes) { @@ -57,35 +57,35 @@ export class MfmService { } } } - + function analyze(node: TreeAdapter.Node) { if (treeAdapter.isTextNode(node)) { text += node.value; return; } - + // Skip comment or document type node if (!treeAdapter.isElementNode(node)) return; - + switch (node.nodeName) { case 'br': { text += '\n'; break; } - + case 'a': { const txt = getText(node); const rel = node.attrs.find(x => x.name === 'rel'); const href = node.attrs.find(x => x.name === 'href'); - + // ハッシュタグ if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) { text += txt; // メンション } else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) { const part = txt.split('@'); - + if (part.length === 2 && href) { //#region ホスト名部分が省略されているので復元する const acct = `${txt}@${(new URL(href.value)).hostname}`; @@ -116,12 +116,12 @@ export class MfmService { return `[${txt}](${href.value})`; } }; - + text += generateLink(); } break; } - + case 'h1': { text += '【'; @@ -129,7 +129,7 @@ export class MfmService { text += '】\n'; break; } - + case 'b': case 'strong': { @@ -138,7 +138,7 @@ export class MfmService { text += '**'; break; } - + case 'small': { text += '<small>'; @@ -146,7 +146,7 @@ export class MfmService { text += '</small>'; break; } - + case 's': case 'del': { @@ -155,7 +155,7 @@ export class MfmService { text += '~~'; break; } - + case 'i': case 'em': { @@ -164,7 +164,7 @@ export class MfmService { text += '</i>'; break; } - + // block code (<pre><code>) case 'pre': { if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') { @@ -176,7 +176,7 @@ export class MfmService { } break; } - + // inline code (<code>) case 'code': { text += '`'; @@ -184,7 +184,7 @@ export class MfmService { text += '`'; break; } - + case 'blockquote': { const t = getText(node); if (t) { @@ -193,7 +193,7 @@ export class MfmService { } break; } - + case 'p': case 'h2': case 'h3': @@ -205,7 +205,7 @@ export class MfmService { appendChildren(node.childNodes); break; } - + // other block elements case 'div': case 'header': @@ -219,7 +219,7 @@ export class MfmService { appendChildren(node.childNodes); break; } - + default: // includes inline elements { appendChildren(node.childNodes); @@ -234,48 +234,48 @@ export class MfmService { if (nodes == null) { return null; } - + const { window } = new Window(); - + const doc = window.document; - + function appendChildren(children: mfm.MfmNode[], targetElement: any): void { if (children) { for (const child of children.map(x => (handlers as any)[x.type](x))) targetElement.appendChild(child); } } - + const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any } = { bold: (node) => { const el = doc.createElement('b'); appendChildren(node.children, el); return el; }, - + small: (node) => { const el = doc.createElement('small'); appendChildren(node.children, el); return el; }, - + strike: (node) => { const el = doc.createElement('del'); appendChildren(node.children, el); return el; }, - + italic: (node) => { const el = doc.createElement('i'); appendChildren(node.children, el); return el; }, - + fn: (node) => { const el = doc.createElement('i'); appendChildren(node.children, el); return el; }, - + blockCode: (node) => { const pre = doc.createElement('pre'); const inner = doc.createElement('code'); @@ -283,21 +283,21 @@ export class MfmService { pre.appendChild(inner); return pre; }, - + center: (node) => { const el = doc.createElement('div'); appendChildren(node.children, el); return el; }, - + emojiCode: (node) => { return doc.createTextNode(`\u200B:${node.props.name}:\u200B`); }, - + unicodeEmoji: (node) => { return doc.createTextNode(node.props.emoji); }, - + hashtag: (node) => { const a = doc.createElement('a'); a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`); @@ -305,32 +305,32 @@ export class MfmService { a.setAttribute('rel', 'tag'); return a; }, - + inlineCode: (node) => { const el = doc.createElement('code'); el.textContent = node.props.code; return el; }, - + mathInline: (node) => { const el = doc.createElement('code'); el.textContent = node.props.formula; return el; }, - + mathBlock: (node) => { const el = doc.createElement('code'); el.textContent = node.props.formula; return el; }, - + link: (node) => { const a = doc.createElement('a'); a.setAttribute('href', node.props.url); appendChildren(node.children, a); return a; }, - + mention: (node) => { const a = doc.createElement('a'); const { username, host, acct } = node.props; @@ -340,47 +340,47 @@ export class MfmService { a.textContent = acct; return a; }, - + quote: (node) => { const el = doc.createElement('blockquote'); appendChildren(node.children, el); return el; }, - + text: (node) => { const el = doc.createElement('span'); const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x)); - + for (const x of intersperse<FIXME | 'br'>('br', nodes)) { el.appendChild(x === 'br' ? doc.createElement('br') : x); } - + return el; }, - + url: (node) => { const a = doc.createElement('a'); a.setAttribute('href', node.props.url); a.textContent = node.props.url; return a; }, - + search: (node) => { const a = doc.createElement('a'); a.setAttribute('href', `https://www.google.com/search?q=${node.props.query}`); a.textContent = node.props.content; return a; }, - + plain: (node) => { const el = doc.createElement('span'); appendChildren(node.children, el); return el; }, }; - + appendChildren(nodes, doc.body); - + return `<p>${doc.body.innerHTML}</p>`; - } + } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 1c8491bf57..fb36ca3b6a 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -672,7 +672,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Register to search database this.index(note); } - + @bindThis private isSensitive(note: Option, sensitiveWord: string[]): boolean { if (sensitiveWord.length > 0) { @@ -758,7 +758,7 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private index(note: Note) { if (note.text == null && note.cw == null) return; - + this.searchService.indexNote(note); } diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 3612ac806f..10381c7e65 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -45,7 +45,7 @@ export class NoteDeleteService { private perUserNotesChart: PerUserNotesChart, private instanceChart: InstanceChart, ) {} - + /** * 投稿を削除します。 * @param user 投稿者 diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index e57e57d310..b84591e26d 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -99,7 +99,7 @@ export class NoteReadService implements OnApplicationShutdown { }); // TODO: ↓まとめてクエリしたい - + this.noteUnreadsRepository.countBy({ userId: userId, isMentioned: true, @@ -109,7 +109,7 @@ export class NoteReadService implements OnApplicationShutdown { this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions'); } }); - + this.noteUnreadsRepository.countBy({ userId: userId, isSpecified: true, diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index ed47165f7b..8e25f82284 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -46,7 +46,7 @@ export class NotificationService implements OnApplicationShutdown { force = false, ) { const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`); - + const latestNotificationIdsRes = await this.redisClient.xrevrange( `notificationTimeline:${userId}`, '+', diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 368753d9a7..be19400052 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -39,12 +39,12 @@ export class PollService { @bindThis public async vote(user: User, note: Note, choice: number) { const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); - + if (poll == null) throw new Error('poll not found'); - + // Check whether is valid choice if (poll.choices[choice] == null) throw new Error('invalid choice param'); - + // Check blocking if (note.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); @@ -52,13 +52,13 @@ export class PollService { throw new Error('blocked'); } } - + // if already voted const exist = await this.pollVotesRepository.findBy({ noteId: note.id, userId: user.id, }); - + if (poll.multiple) { if (exist.some(x => x.choice === choice)) { throw new Error('already voted'); @@ -66,7 +66,7 @@ export class PollService { } else if (exist.length !== 0) { throw new Error('already voted'); } - + // Create vote await this.pollVotesRepository.insert({ id: this.idService.genId(), @@ -75,11 +75,11 @@ export class PollService { userId: user.id, choice: choice, }); - + // Increment votes count const index = choice + 1; // In SQL, array index is 1 based await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); - + this.globalEventService.publishNoteStream(note.id, 'pollVoted', { choice: choice, userId: user.id, @@ -90,10 +90,10 @@ export class PollService { public async deliverQuestionUpdate(noteId: Note['id']) { const note = await this.notesRepository.findOneBy({ id: noteId }); if (note == null) throw new Error('note not found'); - + const user = await this.usersRepository.findOneBy({ id: note.userId }); if (user == null) throw new Error('note not found'); - + if (this.userEntityService.isLocalUser(user)) { const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user)); this.apDeliverManagerService.deliverToFollowers(user, content); diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 9ee83df644..e1c3d3943c 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -31,7 +31,7 @@ function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: Pus ...body.note, // textをgetNoteSummaryしたものに置き換える text: getNoteSummary(('type' in body && body.type === 'renote') ? body.note.renote as Packed<'Note'> : body.note), - + cw: undefined, reply: undefined, renote: undefined, @@ -69,16 +69,16 @@ export class PushNotificationService implements OnApplicationShutdown { @bindThis public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) { const meta = await this.metaService.fetch(); - + if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; - + // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 push.setVapidDetails(this.config.url, meta.swPublicKey, meta.swPrivateKey); - + const subscriptions = await this.subscriptionsCache.fetch(userId); - + for (const subscription of subscriptions) { if ([ 'readAllNotifications', @@ -103,7 +103,7 @@ export class PushNotificationService implements OnApplicationShutdown { //swLogger.info(err.statusCode); //swLogger.info(err.headers); //swLogger.info(err.body); - + if (err.statusCode === 410) { this.swSubscriptionsRepository.delete({ userId: userId, diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index bf50a1cded..435d5d2389 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -60,8 +60,8 @@ export class QueryService { q.orderBy(`${q.alias}.id`, 'DESC'); } return q; - } - + } + // ここでいうBlockedは被Blockedの意 @bindThis public generateBlockedUserQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }): void { @@ -109,18 +109,18 @@ export class QueryService { q.andWhere('note.channelId IS NULL'); } else { q.leftJoinAndSelect('note.channel', 'channel'); - + const channelFollowingQuery = this.channelFollowingsRepository.createQueryBuilder('channelFollowing') .select('channelFollowing.followeeId') .where('channelFollowing.followerId = :followerId', { followerId: me.id }); - + q.andWhere(new Brackets(qb => { qb // チャンネルのノートではない .where('note.channelId IS NULL') // または自分がフォローしているチャンネルのノート .orWhere(`note.channelId IN (${ channelFollowingQuery.getQuery() })`); })); - + q.setParameters(channelFollowingQuery.getParameters()); } } @@ -130,9 +130,9 @@ export class QueryService { const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted') .select('muted.noteId') .where('muted.userId = :userId', { userId: me.id }); - + q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); - + q.setParameters(mutedQuery.getParameters()); } @@ -141,13 +141,13 @@ export class QueryService { const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted') .select('threadMuted.threadId') .where('threadMuted.userId = :userId', { userId: me.id }); - + q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); q.andWhere(new Brackets(qb => { qb .where('note.threadId IS NULL') .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`); })); - + q.setParameters(mutedQuery.getParameters()); } @@ -156,15 +156,15 @@ export class QueryService { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') .where('muting.muterId = :muterId', { muterId: me.id }); - + if (exclude) { mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id }); } - + const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile') .select('user_profile.mutedInstances') .where('user_profile.userId = :muterId', { muterId: me.id }); - + // 投稿の作者をミュートしていない かつ // 投稿の返信先の作者をミュートしていない かつ // 投稿の引用元の作者をミュートしていない @@ -191,7 +191,7 @@ export class QueryService { .where('note.renoteUserHost IS NULL') .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`); })); - + q.setParameters(mutingQuery.getParameters()); q.setParameters(mutingInstanceQuery.getParameters()); } @@ -201,9 +201,9 @@ export class QueryService { const mutingQuery = this.mutingsRepository.createQueryBuilder('muting') .select('muting.muteeId') .where('muting.muterId = :muterId', { muterId: me.id }); - + q.andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`); - + q.setParameters(mutingQuery.getParameters()); } @@ -245,7 +245,7 @@ export class QueryService { const followingQuery = this.followingsRepository.createQueryBuilder('following') .select('following.followeeId') .where('following.followerId = :meId'); - + q.andWhere(new Brackets(qb => { qb // 公開投稿である .where(new Brackets(qb => { qb @@ -268,7 +268,7 @@ export class QueryService { })); })); })); - + q.setParameters({ meId: me.id }); } } @@ -278,10 +278,10 @@ export class QueryService { const mutingQuery = this.renoteMutingsRepository.createQueryBuilder('renote_muting') .select('renote_muting.muteeId') .where('renote_muting.muterId = :muterId', { muterId: me.id }); - + q.andWhere(new Brackets(qb => { qb - .where(new Brackets(qb => { + .where(new Brackets(qb => { qb.where('note.renoteId IS NOT NULL'); qb.andWhere('note.text IS NULL'); qb.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`); @@ -289,7 +289,7 @@ export class QueryService { .orWhere('note.renoteId IS NULL') .orWhere('note.text IS NOT NULL'); })); - + q.setParameters(mutingQuery.getParameters()); } } diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 9d34d82be2..c0113a21d7 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -39,9 +39,9 @@ export class RelayService { host: IsNull(), username: ACTOR_USERNAME, }); - + if (user) return user as LocalUser; - + const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME); return created as LocalUser; } @@ -53,12 +53,12 @@ export class RelayService { inbox, status: 'requesting', }).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0])); - + const relayActor = await this.getRelayActor(); const follow = await this.apRendererService.renderFollowRelay(relay, relayActor); const activity = this.apRendererService.addContext(follow); this.queueService.deliver(relayActor, activity, relay.inbox, false); - + return relay; } @@ -67,17 +67,17 @@ export class RelayService { const relay = await this.relaysRepository.findOneBy({ inbox, }); - + if (relay == null) { throw new Error('relay not found'); } - + const relayActor = await this.getRelayActor(); const follow = this.apRendererService.renderFollowRelay(relay, relayActor); const undo = this.apRendererService.renderUndo(follow, relayActor); const activity = this.apRendererService.addContext(undo); this.queueService.deliver(relayActor, activity, relay.inbox, false); - + await this.relaysRepository.delete(relay.id); } @@ -86,13 +86,13 @@ export class RelayService { const relays = await this.relaysRepository.find(); return relays; } - + @bindThis public async relayAccepted(id: string): Promise<string> { const result = await this.relaysRepository.update(id, { status: 'accepted', }); - + return JSON.stringify(result); } @@ -101,24 +101,24 @@ export class RelayService { const result = await this.relaysRepository.update(id, { status: 'rejected', }); - + return JSON.stringify(result); } @bindThis public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> { if (activity == null) return; - + const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({ status: 'accepted', })); if (relays.length === 0) return; - + const copy = deepClone(activity); if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; - + const signed = await this.apRendererService.attachLdSignature(copy, user); - + for (const relay of relays) { this.queueService.deliver(user, signed, relay.inbox, false); } diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index ff68c24219..ed15a1f1ce 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -35,7 +35,7 @@ export class RemoteUserResolveService { @bindThis public async resolveUser(username: string, host: string | null): Promise<LocalUser | RemoteUser> { const usernameLower = username.toLowerCase(); - + if (host == null) { this.logger.info(`return local user: ${usernameLower}`); return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { @@ -46,9 +46,9 @@ export class RemoteUserResolveService { } }) as LocalUser; } - + host = this.utilityService.toPuny(host); - + if (this.config.host === host) { this.logger.info(`return local user: ${usernameLower}`); return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => { @@ -59,39 +59,39 @@ export class RemoteUserResolveService { } }) as LocalUser; } - + const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null; - + const acctLower = `${usernameLower}@${host}`; - + if (user == null) { const self = await this.resolveSelf(acctLower); - + this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); return await this.apPersonService.createPerson(self.href); } - + // ユーザー情報が古い場合は、WebFilgerからやりなおして返す if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する await this.usersRepository.update(user.id, { lastFetchedAt: new Date(), }); - + this.logger.info(`try resync: ${acctLower}`); const self = await this.resolveSelf(acctLower); - + if (user.uri !== self.href) { // if uri mismatch, Fix (user@host <=> AP's Person id(RemoteUser.uri)) mapping. this.logger.info(`uri missmatch: ${acctLower}`); this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); - + // validate uri const uri = new URL(self.href); if (uri.hostname !== host) { throw new Error('Invalid uri'); } - + await this.usersRepository.update({ usernameLower, host: host, @@ -101,9 +101,9 @@ export class RemoteUserResolveService { } else { this.logger.info(`uri is fine: ${acctLower}`); } - + await this.apPersonService.updatePerson(self.href); - + this.logger.info(`return resynced remote user: ${acctLower}`); return await this.usersRepository.findOneBy({ uri: self.href }).then(u => { if (u == null) { @@ -113,7 +113,7 @@ export class RemoteUserResolveService { } }); } - + this.logger.info(`return existing remote user: ${acctLower}`); return user; } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 23ecf0157d..b0bfb44dc2 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -392,7 +392,7 @@ export class RoleService implements OnApplicationShutdown { @bindThis public async unassign(userId: User['id'], roleId: Role['id']): Promise<void> { const now = new Date(); - + const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId }); if (existing == null) { throw new RoleService.NotAssignedError(); diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 29eb65fda4..e6b17a8741 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -50,31 +50,31 @@ export class SignupService { }) { const { username, password, passwordHash, host } = opts; let hash = passwordHash; - + // Validate username if (!this.userEntityService.validateLocalUsername(username)) { throw new Error('INVALID_USERNAME'); } - + if (password != null && passwordHash == null) { // Validate password if (!this.userEntityService.validatePassword(password)) { throw new Error('INVALID_PASSWORD'); } - + // Generate hash of password const salt = await bcrypt.genSalt(8); hash = await bcrypt.hash(password, salt); } - + // Generate secret const secret = generateUserToken(); - + // Check username duplication if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { throw new Error('DUPLICATED_USERNAME'); } - + // Check deleted username duplication if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) { throw new Error('USED_USERNAME'); @@ -106,18 +106,18 @@ export class SignupService { }, (err, publicKey, privateKey) => err ? rej(err) : res([publicKey, privateKey]), )); - + let account!: User; - + // Start transaction await this.db.transaction(async transactionalEntityManager => { const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), host: IsNull(), }); - + if (exist) throw new Error(' the username is already used'); - + account = await transactionalEntityManager.save(new User({ id: this.idService.genId(), createdAt: new Date(), @@ -127,27 +127,27 @@ export class SignupService { token: secret, isRoot: isTheFirstUser, })); - + await transactionalEntityManager.save(new UserKeypair({ publicKey: keyPair[0], privateKey: keyPair[1], userId: account.id, })); - + await transactionalEntityManager.save(new UserProfile({ userId: account.id, autoAcceptFollowed: true, password: hash, })); - + await transactionalEntityManager.save(new UsedUsername({ createdAt: new Date(), username: username.toLowerCase(), })); }); - + this.usersChart.update(account, true); - + return { account, secret }; } } diff --git a/packages/backend/src/core/TwoFactorAuthenticationService.ts b/packages/backend/src/core/TwoFactorAuthenticationService.ts index dda78236e9..d4cf186a24 100644 --- a/packages/backend/src/core/TwoFactorAuthenticationService.ts +++ b/packages/backend/src/core/TwoFactorAuthenticationService.ts @@ -69,7 +69,7 @@ function verifyCertificateChain(certificates: string[]) { const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]); if (certStruct == null) throw new Error('certStruct is null'); - + const algorithm = certificate.getSignatureAlgorithmField(); const signatureHex = certificate.getSignatureValueHex(); @@ -143,19 +143,19 @@ export class TwoFactorAuthenticationService { if (clientData.type !== 'webauthn.get') { throw new Error('type is not webauthn.get'); } - + if (this.hash(clientData.challenge).toString('hex') !== challenge) { throw new Error('challenge mismatch'); } if (clientData.origin !== this.config.scheme + '://' + this.config.host) { throw new Error('origin mismatch'); } - + const verificationData = Buffer.concat( [authenticatorData, this.hash(clientDataJSON)], 32 + authenticatorData.length, ); - + return crypto .createVerify('SHA256') .update(verificationData) @@ -168,7 +168,7 @@ export class TwoFactorAuthenticationService { none: { verify({ publicKey }: { publicKey: Map<number, Buffer> }) { const negTwo = publicKey.get(-2); - + if (!negTwo || negTwo.length !== 32) { throw new Error('invalid or no -2 key given'); } @@ -176,12 +176,12 @@ export class TwoFactorAuthenticationService { if (!negThree || negThree.length !== 32) { throw new Error('invalid or no -3 key given'); } - + const publicKeyU2F = Buffer.concat( [ECC_PRELUDE, negTwo, negThree], 1 + 32 + 32, ); - + return { publicKey: publicKeyU2F, valid: true, @@ -207,16 +207,16 @@ export class TwoFactorAuthenticationService { if (attStmt.alg !== -7) { throw new Error('alg mismatch'); } - + const verificationData = Buffer.concat([ authenticatorData, clientDataHash, ]); - + const attCert: Buffer = attStmt.x5c[0]; - + const negTwo = publicKey.get(-2); - + if (!negTwo || negTwo.length !== 32) { throw new Error('invalid or no -2 key given'); } @@ -224,23 +224,23 @@ export class TwoFactorAuthenticationService { if (!negThree || negThree.length !== 32) { throw new Error('invalid or no -3 key given'); } - + const publicKeyData = Buffer.concat( [ECC_PRELUDE, negTwo, negThree], 1 + 32 + 32, ); - + if (!attCert.equals(publicKeyData)) { throw new Error('public key mismatch'); } - + const isValid = crypto .createVerify('SHA256') .update(verificationData) .verify(PEMString(attCert), attStmt.sig); - + // TODO: Check 'attestationChallenge' field in extension of cert matches hash(clientDataJSON) - + return { valid: isValid, publicKey: publicKeyData, @@ -267,43 +267,43 @@ export class TwoFactorAuthenticationService { const verificationData = this.hash( Buffer.concat([authenticatorData, clientDataHash]), ); - + const jwsParts = attStmt.response.toString('utf-8').split('.'); - + const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8')); const response = JSON.parse( base64URLDecode(jwsParts[1]).toString('utf-8'), ); const signature = jwsParts[2]; - + if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) { throw new Error('invalid nonce'); } - + const certificateChain = header.x5c .map((key: any) => PEMString(key)) .concat([GSR2]); - + if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') { throw new Error('invalid common name'); } - + if (!verifyCertificateChain(certificateChain)) { throw new Error('Invalid certificate chain!'); } - + const signatureBase = Buffer.from( jwsParts[0] + '.' + jwsParts[1], 'utf-8', ); - + const valid = crypto .createVerify('sha256') .update(signatureBase) .verify(certificateChain[0], base64URLDecode(signature)); - + const negTwo = publicKey.get(-2); - + if (!negTwo || negTwo.length !== 32) { throw new Error('invalid or no -2 key given'); } @@ -311,7 +311,7 @@ export class TwoFactorAuthenticationService { if (!negThree || negThree.length !== 32) { throw new Error('invalid or no -3 key given'); } - + const publicKeyData = Buffer.concat( [ECC_PRELUDE, negTwo, negThree], 1 + 32 + 32, @@ -342,17 +342,17 @@ export class TwoFactorAuthenticationService { authenticatorData, clientDataHash, ]); - + if (attStmt.x5c) { const attCert = attStmt.x5c[0]; - + const validSignature = crypto .createVerify('SHA256') .update(verificationData) .verify(PEMString(attCert), attStmt.sig); - + const negTwo = publicKey.get(-2); - + if (!negTwo || negTwo.length !== 32) { throw new Error('invalid or no -2 key given'); } @@ -360,12 +360,12 @@ export class TwoFactorAuthenticationService { if (!negThree || negThree.length !== 32) { throw new Error('invalid or no -3 key given'); } - + const publicKeyData = Buffer.concat( [ECC_PRELUDE, negTwo, negThree], 1 + 32 + 32, ); - + return { valid: validSignature, publicKey: publicKeyData, @@ -375,12 +375,12 @@ export class TwoFactorAuthenticationService { throw new Error('ECDAA-Verify is not supported'); } else { if (attStmt.alg !== -7) throw new Error('alg mismatch'); - + throw new Error('self attestation is not supported'); } }, }, - + 'fido-u2f': { verify({ attStmt, @@ -401,13 +401,13 @@ export class TwoFactorAuthenticationService { if (x5c.length !== 1) { throw new Error('x5c length does not match expectation'); } - + const attCert = x5c[0]; - + // TODO: make sure attCert is an Elliptic Curve (EC) public key over the P-256 curve - + const negTwo: Buffer = publicKey.get(-2); - + if (!negTwo || negTwo.length !== 32) { throw new Error('invalid or no -2 key given'); } @@ -415,12 +415,12 @@ export class TwoFactorAuthenticationService { if (!negThree || negThree.length !== 32) { throw new Error('invalid or no -3 key given'); } - + const publicKeyU2F = Buffer.concat( [ECC_PRELUDE, negTwo, negThree], 1 + 32 + 32, ); - + const verificationData = Buffer.concat([ NULL_BYTE, rpIdHash, @@ -428,12 +428,12 @@ export class TwoFactorAuthenticationService { credentialId, publicKeyU2F, ]); - + const validSignature = crypto .createVerify('SHA256') .update(verificationData) .verify(PEMString(attCert), attStmt.sig); - + return { valid: validSignature, publicKey: publicKeyU2F, diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index b197d335d8..28ae32681d 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -32,13 +32,13 @@ export class UserSuspendService { @bindThis public async doPostSuspend(user: { id: User['id']; host: User['host'] }): Promise<void> { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); - + if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); - + const queue: string[] = []; - + const followings = await this.followingsRepository.find({ where: [ { followerSharedInbox: Not(IsNull()) }, @@ -46,13 +46,13 @@ export class UserSuspendService { ], select: ['followerSharedInbox', 'followeeSharedInbox'], }); - + const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox); - + for (const inbox of inboxes) { if (inbox != null && !queue.includes(inbox)) queue.push(inbox); } - + for (const inbox of queue) { this.queueService.deliver(user, content, inbox, true); } @@ -62,13 +62,13 @@ export class UserSuspendService { @bindThis public async doPostUnsuspend(user: User): Promise<void> { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); - + if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにUndo Delete配信 const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user)); - + const queue: string[] = []; - + const followings = await this.followingsRepository.find({ where: [ { followerSharedInbox: Not(IsNull()) }, @@ -76,13 +76,13 @@ export class UserSuspendService { ], select: ['followerSharedInbox', 'followeeSharedInbox'], }); - + const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox); - + for (const inbox of inboxes) { if (inbox != null && !queue.includes(inbox)) queue.push(inbox); } - + for (const inbox of queue) { this.queueService.deliver(user as any, content, inbox, true); } diff --git a/packages/backend/src/core/VideoProcessingService.ts b/packages/backend/src/core/VideoProcessingService.ts index 5869905db0..89681f3372 100644 --- a/packages/backend/src/core/VideoProcessingService.ts +++ b/packages/backend/src/core/VideoProcessingService.ts @@ -21,7 +21,7 @@ export class VideoProcessingService { @bindThis public async generateVideoThumbnail(source: string): Promise<IImage> { const [dir, cleanup] = await createTempDir(); - + try { await new Promise((res, rej) => { FFmpeg({ diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 467755a072..b6f5263901 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -31,7 +31,7 @@ export class WebhookService implements OnApplicationShutdown { }); this.webhooksFetched = true; } - + return this.webhooks; } diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts index 8282a6324c..0eab7fa335 100644 --- a/packages/backend/src/core/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -27,14 +27,14 @@ export class ApAudienceService { public async parseAudience(actor: RemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> { const toGroups = this.groupingAudience(getApIds(to), actor); const ccGroups = this.groupingAudience(getApIds(cc), actor); - + const others = unique(concat([toGroups.other, ccGroups.other])); - + const limit = promiseLimit<User | null>(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), )).filter((x): x is User => x != null); - + if (toGroups.public.length > 0) { return { visibility: 'public', @@ -42,7 +42,7 @@ export class ApAudienceService { visibleUsers: [], }; } - + if (ccGroups.public.length > 0) { return { visibility: 'home', @@ -50,7 +50,7 @@ export class ApAudienceService { visibleUsers: [], }; } - + if (toGroups.followers.length > 0) { return { visibility: 'followers', @@ -58,14 +58,14 @@ export class ApAudienceService { visibleUsers: [], }; } - + return { visibility: 'specified', mentionedUsers, visibleUsers: mentionedUsers, }; } - + @bindThis private groupingAudience(ids: string[], actor: RemoteUser) { const groups = { @@ -73,7 +73,7 @@ export class ApAudienceService { followers: [] as string[], other: [] as string[], }; - + for (const id of ids) { if (this.isPublic(id)) { groups.public.push(id); @@ -83,12 +83,12 @@ export class ApAudienceService { groups.other.push(id); } } - + groups.other = unique(groups.other); - + return groups; } - + @bindThis private isPublic(id: string) { return [ @@ -97,7 +97,7 @@ export class ApAudienceService { 'Public', ].includes(id); } - + @bindThis private isFollowers(id: string, actor: RemoteUser) { return ( diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index ca148916dc..20283a163c 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -121,7 +121,7 @@ export class ApDbResolverService implements OnApplicationShutdown { const key = await this.userPublickeysRepository.findOneBy({ keyId, }); - + if (key == null) return null; return key; @@ -147,7 +147,7 @@ export class ApDbResolverService implements OnApplicationShutdown { 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 { user, diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 6e910eb538..82c2c9f71f 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -90,7 +90,7 @@ export class ApDeliverManagerService { this.followingsRepository, this.queueService, - actor, + actor, activity, ); } diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index 6116822f7a..411cf17d19 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -21,7 +21,7 @@ export class ApMfmService { @bindThis public htmlToMfm(html: string, tag?: IObject | IObject[]) { const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); - + return this.mfmService.fromHtml(html, hashtagNames); } @@ -29,5 +29,5 @@ export class ApMfmService { public getNoteHtml(note: Note) { if (!note.text) return ''; return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); - } + } } diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 0043907c21..79c5ae958a 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -32,7 +32,7 @@ export class ApImageService { ) { this.logger = this.apLoggerService.logger; } - + /** * Imageを作成します。 */ diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts index c581840ca9..9beefc8fcb 100644 --- a/packages/backend/src/core/activitypub/models/ApMentionService.ts +++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts @@ -29,10 +29,10 @@ export class ApMentionService { const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), )).filter((x): x is User => x != null); - + return mentionedUsers; } - + @bindThis public extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { if (tags == null) return []; diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 76757f530a..60a3e56800 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -55,7 +55,7 @@ export class ApNoteService { // 循環参照のため / for circular dependency @Inject(forwardRef(() => ApPersonService)) private apPersonService: ApPersonService, - + private utilityService: UtilityService, private apAudienceService: ApAudienceService, private apMentionService: ApMentionService, @@ -74,15 +74,15 @@ export class ApNoteService { @bindThis public validateNote(object: IObject, uri: string) { const expectHost = this.utilityService.extractDbHost(uri); - + if (object == null) { return new Error('invalid Note: object is null'); } - + if (!validPost.includes(getApType(object))) { return new Error(`invalid Note: invalid object type ${getApType(object)}`); } - + 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)}`); } @@ -91,10 +91,10 @@ export class ApNoteService { if (object.attributedTo && actualHost !== expectHost) { return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`); } - + return null; } - + /** * Noteをフェッチします。 * @@ -104,16 +104,16 @@ export class ApNoteService { public async fetchNote(object: string | IObject): Promise<Note | null> { return await this.apDbResolverService.getNoteFromApId(object); } - + /** * Noteを作成します。 */ @bindThis public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> { if (resolver == null) resolver = this.apResolverService.createResolver(); - + const object = await resolver.resolve(value); - + const entryUri = getApId(value); const err = this.validateNote(object, entryUri); if (err) { @@ -126,9 +126,9 @@ export class ApNoteService { }); throw new Error('invalid note'); } - + const note = object as IPost; - + this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); if (note.id && !checkHttps(note.id)) { @@ -140,21 +140,21 @@ export class ApNoteService { if (url && !checkHttps(url)) { throw new Error('unexpected shcema of note url: ' + url); } - + this.logger.info(`Creating the Note: ${note.id}`); - + // 投稿者をフェッチ const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo!), resolver) as RemoteUser; - + // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); } - + const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); let visibility = noteAudience.visibility; const visibleUsers = noteAudience.visibleUsers; - + // Audience (to, cc) が指定されてなかった場合 if (visibility === 'specified' && visibleUsers.length === 0) { if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している @@ -162,23 +162,23 @@ export class ApNoteService { visibility = 'public'; } } - + const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); const apHashtags = await extractApHashtags(note.tag); - + // 添付ファイル // TODO: attachmentは必ずしもImageではない // TODO: attachmentは必ずしも配列ではない // Noteがsensitiveなら添付もsensitiveにする const limit = promiseLimit(2); - + note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; const files = note.attachment .map(attach => attach.sensitive = note.sensitive) ? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise<DriveFile>))) .filter(image => image != null) : []; - + // リプライ const reply: Note | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, resolver).then(x => { @@ -193,10 +193,10 @@ export class ApNoteService { throw err; }) : null; - + // 引用 let quote: Note | undefined | null; - + if (note._misskey_quote || note.quoteUrl) { const tryResolveNote = async (uri: string): Promise<{ status: 'ok'; @@ -223,10 +223,10 @@ export class ApNoteService { }; } }; - + 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))); - + quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); if (!quote) { if (results.some(x => x.status === 'temperror')) { @@ -234,9 +234,9 @@ export class ApNoteService { } } } - + const cw = note.summary === '' ? null : note.summary; - + // テキストのパース let text: string | null = null; if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { @@ -246,38 +246,38 @@ export class ApNoteService { } else if (typeof note.content === 'string') { text = this.apMfmService.htmlToMfm(note.content, note.tag); } - + // vote if (reply && reply.hasPoll) { const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); - + const tryCreateVote = async (name: string, index: number): Promise<null> => { 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}`); } else if (index >= 0) { this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); await this.pollService.vote(actor, reply, index); - + // リモートフォロワーにUpdate配信 this.pollService.deliverQuestionUpdate(reply.id); } return null; }; - + if (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 => { this.logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); - + const apEmojis = emojis.map(emoji => emoji.name); - + const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); - + return await this.noteCreateService.create(actor, { createdAt: note.published ? new Date(note.published) : null, files, @@ -297,7 +297,7 @@ export class ApNoteService { url: url, }, silent); } - + /** * Noteを解決します。 * @@ -308,26 +308,26 @@ export class ApNoteService { public async resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> { const uri = typeof value === 'string' ? value : value.id; if (uri == null) throw new Error('missing uri'); - + // ブロックしてたら中断 const meta = await this.metaService.fetch(); if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451); - + const unlock = await this.appLockService.getApLock(uri); - + try { //#region このサーバーに既に登録されていたらそれを返す const exist = await this.fetchNote(uri); - + if (exist) { return exist; } //#endregion - + if (uri.startsWith(this.config.url)) { throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); } - + // リモートサーバーからフェッチしてきて登録 // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 @@ -336,26 +336,26 @@ export class ApNoteService { unlock(); } } - + @bindThis public async extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> { host = this.utilityService.toPuny(host); - + if (!tags) return []; - + const eomjiTags = toArray(tags).filter(isEmoji); const existingEmojis = await this.emojisRepository.findBy({ host, name: In(eomjiTags.map(tag => tag.name!.replaceAll(':', ''))), }); - + return await Promise.all(eomjiTags.map(async tag => { const name = tag.name!.replaceAll(':', ''); tag.icon = toSingle(tag.icon); - + const exists = existingEmojis.find(x => x.name === name); - + if (exists) { if ((tag.updated != null && exists.updatedAt == null) || (tag.id != null && exists.uri == null) @@ -371,18 +371,18 @@ export class ApNoteService { publicUrl: tag.icon!.url, updatedAt: new Date(), }); - + return await this.emojisRepository.findOneBy({ host, name, }) as Emoji; } - + return exists; } - + this.logger.info(`register emoji host=${host}, name=${name}`); - + return await this.emojisRepository.insert({ id: this.idService.genId(), host, diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index f52ebed107..afe89e5a11 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -415,7 +415,7 @@ export class ApPersonService implements OnModuleInit { * Personの情報を更新します。 * Misskeyに対象のPersonが登録されていなければ無視します。 * もしアカウントの移行が確認された場合、アカウント移行処理を行います。 - * + * * @param uri URI of Person * @param resolver Resolver * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) @@ -688,7 +688,7 @@ export class ApPersonService implements OnModuleInit { // (uriが存在しなかったり応答がなかったりする場合resolvePersonはthrow Errorする) dst = await this.resolvePerson(src.movedToUri); } - + if (dst.movedToUri === dst.uri) return 'skip: movedTo itself (dst)'; // ??? if (src.movedToUri !== dst.uri) return 'skip: missmatch uri'; // ??? if (dst.movedToUri === src.uri) return 'skip: dst.movedToUri === src.uri'; diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index d82f36d971..80442af09b 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -47,7 +47,7 @@ export class DriveFileEntityService { private videoProcessingService: VideoProcessingService, ) { } - + @bindThis public validateFileName(name: string): boolean { return ( diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 32269a4101..e730c0eb8f 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -24,7 +24,7 @@ export class NoteEntityService implements OnModuleInit { private driveFileEntityService: DriveFileEntityService; private customEmojiService: CustomEmojiService; private reactionService: ReactionService; - + constructor( private moduleRef: ModuleRef, @@ -68,7 +68,7 @@ export class NoteEntityService implements OnModuleInit { this.customEmojiService = this.moduleRef.get('CustomEmojiService'); this.reactionService = this.moduleRef.get('ReactionService'); } - + @bindThis private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) @@ -457,12 +457,12 @@ export class NoteEntityService implements OnModuleInit { const query = this.notesRepository.createQueryBuilder('note') .where('note.userId = :userId', { userId }) .andWhere('note.renoteId = :renoteId', { renoteId }); - + // 指定した投稿を除く if (excludeNoteId) { query.andWhere('note.id != :excludeNoteId', { excludeNoteId }); } - + return await query.getCount(); - } + } } diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 8f943ba24c..d454ddb70a 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -17,7 +17,7 @@ export class NoteReactionEntityService implements OnModuleInit { private userEntityService: UserEntityService; private noteEntityService: NoteEntityService; private reactionService: ReactionService; - + constructor( private moduleRef: ModuleRef, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index d76b863957..02c6982847 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -59,7 +59,7 @@ export class NotificationEntityService implements OnModuleInit { meId: User['id'], // eslint-disable-next-line @typescript-eslint/ban-types options: { - + }, hint?: { packedNotes: Map<Note['id'], Packed<'Note'>>; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index f1a4e56c02..6dacaa1032 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -113,7 +113,7 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, - + @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, |