This commit is contained in:
syuilo 2025-11-29 09:43:55 +09:00
parent 17a4d4fad9
commit 2a112ffe5b
6 changed files with 37 additions and 30 deletions

View File

@ -7,7 +7,7 @@
- -
### Server ### Server
- - Enhance: メモリ使用量を削減しました
## 2025.11.1 ## 2025.11.1

View File

@ -130,7 +130,6 @@
"ipaddr.js": "2.2.0", "ipaddr.js": "2.2.0",
"is-svg": "5.1.0", "is-svg": "5.1.0",
"js-yaml": "4.1.1", "js-yaml": "4.1.1",
"jsdom": "26.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.3.3", "jsonld": "8.3.3",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
@ -145,6 +144,7 @@
"nanoid": "5.1.6", "nanoid": "5.1.6",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"node-html-parser": "7.0.1",
"nodemailer": "7.0.10", "nodemailer": "7.0.10",
"nsfwjs": "4.2.0", "nsfwjs": "4.2.0",
"oauth": "0.10.2", "oauth": "0.10.2",
@ -240,6 +240,7 @@
"fkill": "9.0.0", "fkill": "9.0.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-mock": "29.7.0", "jest-mock": "29.7.0",
"jsdom": "26.1.0",
"nodemon": "3.1.11", "nodemon": "3.1.11",
"pid-port": "1.0.2", "pid-port": "1.0.2",
"simple-oauth2": "5.1.0", "simple-oauth2": "5.1.0",

View File

@ -5,9 +5,9 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import * as htmlParser from 'node-html-parser';
import type { MiInstance } from '@/models/Instance.js'; import type { MiInstance } from '@/models/Instance.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -15,7 +15,6 @@ import { LoggerService } from '@/core/LoggerService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import type { DOMWindow } from 'jsdom';
type NodeInfo = { type NodeInfo = {
openRegistrations?: unknown; openRegistrations?: unknown;
@ -59,7 +58,7 @@ export class FetchInstanceMetadataService {
return await this.redisClient.set( return await this.redisClient.set(
`fetchInstanceMetadata:mutex:v2:${host}`, '1', `fetchInstanceMetadata:mutex:v2:${host}`, '1',
'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395 'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395
'GET' // 古い値を返すなかったらnull 'GET', // 古い値を返すなかったらnull
); );
} }
@ -181,15 +180,14 @@ export class FetchInstanceMetadataService {
} }
@bindThis @bindThis
private async fetchDom(instance: MiInstance): Promise<Document> { private async fetchDom(instance: MiInstance): Promise<htmlParser.HTMLElement> {
this.logger.info(`Fetching HTML of ${instance.host} ...`); this.logger.info(`Fetching HTML of ${instance.host} ...`);
const url = 'https://' + instance.host; const url = 'https://' + instance.host;
const html = await this.httpRequestService.getHtml(url); const html = await this.httpRequestService.getHtml(url);
const { window } = new JSDOM(html); const doc = htmlParser.parse(html);
const doc = window.document;
return doc; return doc;
} }
@ -206,12 +204,12 @@ export class FetchInstanceMetadataService {
} }
@bindThis @bindThis
private async fetchFaviconUrl(instance: MiInstance, doc: Document | null): Promise<string | null> { private async fetchFaviconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null): Promise<string | null> {
const url = 'https://' + instance.host; const url = 'https://' + instance.host;
if (doc) { if (doc) {
// https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 // 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; const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.attributes.rel === 'icon')?.attributes.href;
if (href) { if (href) {
return (new URL(href, url)).href; return (new URL(href, url)).href;
@ -232,7 +230,7 @@ export class FetchInstanceMetadataService {
} }
@bindThis @bindThis
private async fetchIconUrl(instance: MiInstance, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> { private async fetchIconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
const url = 'https://' + instance.host; const url = 'https://' + instance.host;
return (new URL(manifest.icons[0].src, url)).href; return (new URL(manifest.icons[0].src, url)).href;
@ -246,9 +244,9 @@ export class FetchInstanceMetadataService {
// https://github.com/misskey-dev/misskey/pull/8220/files/0ec4eba22a914e31b86874f12448f88b3e58dd5a#r796487559 // 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.attributes.rel === 'apple-touch-icon-precomposed')?.attributes.href,
links.find(link => link.relList.contains('apple-touch-icon'))?.href, links.find(link => link.attributes.rel === 'apple-touch-icon')?.attributes.href,
links.find(link => link.relList.contains('icon'))?.href, links.find(link => link.attributes.rel === 'icon')?.attributes.href,
] ]
.find(href => href); .find(href => href);
@ -261,7 +259,7 @@ export class FetchInstanceMetadataService {
} }
@bindThis @bindThis
private async getThemeColor(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> { private async getThemeColor(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color; const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
if (themeColor) { if (themeColor) {
@ -273,7 +271,7 @@ export class FetchInstanceMetadataService {
} }
@bindThis @bindThis
private async getSiteName(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> { private async getSiteName(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) { if (info && info.metadata) {
if (typeof info.metadata.nodeName === 'string') { if (typeof info.metadata.nodeName === 'string') {
return info.metadata.nodeName; return info.metadata.nodeName;
@ -298,7 +296,7 @@ export class FetchInstanceMetadataService {
} }
@bindThis @bindThis
private async getDescription(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> { private async getDescription(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) { if (info && info.metadata) {
if (typeof info.metadata.nodeDescription === 'string') { if (typeof info.metadata.nodeDescription === 'string') {
return info.metadata.nodeDescription; return info.metadata.nodeDescription;

View File

@ -7,7 +7,7 @@ import RE2 from 're2';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms'; import ms from 'ms';
import { JSDOM } from 'jsdom'; import * as htmlParser from 'node-html-parser';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js'; import { extractHashtags } from '@/misc/extract-hashtags.js';
import * as Acct from '@/misc/acct.js'; import * as Acct from '@/misc/acct.js';
@ -569,16 +569,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
try { try {
const html = await this.httpRequestService.getHtml(url); const html = await this.httpRequestService.getHtml(url);
const { window } = new JSDOM(html); const doc = htmlParser.parse(html);
const doc: Document = window.document;
const myLink = `${this.config.url}/@${user.username}`; const myLink = `${this.config.url}/@${user.username}`;
const aEls = Array.from(doc.getElementsByTagName('a')); const aEls = Array.from(doc.getElementsByTagName('a'));
const linkEls = Array.from(doc.getElementsByTagName('link')); const linkEls = Array.from(doc.getElementsByTagName('link'));
const includesMyLink = aEls.some(a => a.href === myLink); const includesMyLink = aEls.some(a => a.attributes.href === myLink);
const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink); const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.attributes.rel === 'me' && link.attributes.href === myLink);
if (includesMyLink || includesRelMeLinks) { if (includesMyLink || includesRelMeLinks) {
await this.userProfilesRepository.createQueryBuilder('profile').update() await this.userProfilesRepository.createQueryBuilder('profile').update()
@ -588,8 +587,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}) })
.execute(); .execute();
} }
window.close();
} catch (err) { } catch (err) {
// なにもしない // なにもしない
} }

View File

@ -6,7 +6,7 @@
import dns from 'node:dns/promises'; import dns from 'node:dns/promises';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom'; import * as htmlParser from 'node-html-parser';
import httpLinkHeader from 'http-link-header'; import httpLinkHeader from 'http-link-header';
import ipaddr from 'ipaddr.js'; import ipaddr from 'ipaddr.js';
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize'; import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
@ -120,9 +120,9 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
} }
const text = await res.text(); const text = await res.text();
const fragment = JSDOM.fragment(text); const fragment = htmlParser.parse(text);
redirectUris.push(...[...fragment.querySelectorAll<HTMLLinkElement>('link[rel=redirect_uri][href]')].map(el => el.href)); redirectUris.push(...[...fragment.querySelectorAll('link[rel=redirect_uri][href]')].map(el => el.attributes.href));
let name = id; let name = id;
let logo: string | null = null; let logo: string | null = null;

View File

@ -273,9 +273,6 @@ importers:
js-yaml: js-yaml:
specifier: 4.1.1 specifier: 4.1.1
version: 4.1.1 version: 4.1.1
jsdom:
specifier: 26.1.0
version: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
json5: json5:
specifier: 2.2.3 specifier: 2.2.3
version: 2.2.3 version: 2.2.3
@ -318,6 +315,9 @@ importers:
node-fetch: node-fetch:
specifier: 3.3.2 specifier: 3.3.2
version: 3.3.2 version: 3.3.2
node-html-parser:
specifier: 7.0.1
version: 7.0.1
nodemailer: nodemailer:
specifier: 7.0.10 specifier: 7.0.10
version: 7.0.10 version: 7.0.10
@ -598,6 +598,9 @@ importers:
jest-mock: jest-mock:
specifier: 29.7.0 specifier: 29.7.0
version: 29.7.0 version: 29.7.0
jsdom:
specifier: 26.1.0
version: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
nodemon: nodemon:
specifier: 3.1.11 specifier: 3.1.11
version: 3.1.11 version: 3.1.11
@ -8669,6 +8672,9 @@ packages:
engines: {node: ^18.17.0 || >=20.5.0} engines: {node: ^18.17.0 || >=20.5.0}
hasBin: true hasBin: true
node-html-parser@7.0.1:
resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==}
node-int64@0.4.0: node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@ -20199,6 +20205,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
node-html-parser@7.0.1:
dependencies:
css-select: 5.2.2
he: 1.2.0
node-int64@0.4.0: {} node-int64@0.4.0: {}
node-releases@2.0.27: {} node-releases@2.0.27: {}