diff --git a/package.json b/package.json index 2e91c381bb..f51e36f1b2 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@types/ms": "0.7.30", "@types/node": "9.6.6", "@types/nopt": "3.0.29", + "@types/parse5": "^3.0.0", "@types/pug": "2.0.4", "@types/qrcode": "0.8.1", "@types/ratelimiter": "2.1.28", @@ -166,6 +167,7 @@ "object-assign-deep": "0.4.0", "on-build-webpack": "0.1.0", "os-utils": "0.0.14", + "parse5": "^4.0.0", "progress-bar-webpack-plugin": "1.11.0", "prominence": "0.2.0", "promise-sequential": "^1.1.1", diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index 9a0cc4e0c9..bef67751b7 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -1,5 +1,5 @@ import * as mongo from 'mongodb'; -import { JSDOM } from 'jsdom'; +import * as parse5 from 'parse5'; import * as debug from 'debug'; import config from '../../../config'; @@ -13,6 +13,72 @@ import { IRemoteUser } from '../../../models/user'; const log = debug('misskey:activitypub'); +function parse(tag, html: string): string { + const dom = parse5.parseFragment(html) as parse5.AST.Default.Document; + + let text = ''; + + dom.childNodes.forEach(n => analyze(n)); + + return text.trim(); + + function analyze(node) { + switch (node.nodeName) { + case '#text': + text += node.value; + break; + + case 'br': + text += '\n'; + break; + + case 'a': + const cls = node.attrs + ? (node.attrs.find(x => x.name == 'class') || { value: '' }).value.split(' ') + : []; + + // for Mastodon + if (cls.includes('mention')) { + //#region ホスト名部分が省略されているので復元する + + // Activityのtag情報に次のような形で省略前の情報が添付されているのでそこから持ってくる + // { + // "type": "Mention", + // "href": "https://misskey.xyz/users/57d01a501fdf2d07be417afe", + // "name": "@syuilo@misskey.xyz" + // } + + const href = node.attrs.find(x => x.name == 'href').value; + const acct = tag.find(t => t.type == 'Mention' && t.href == href).name; + + text += acct; + + break; + + //#endregion + } + + if (node.childNodes) { + node.childNodes.forEach(n => analyze(n)); + } + break; + + case 'p': + text += '\n'; + if (node.childNodes) { + node.childNodes.forEach(n => analyze(n)); + } + break; + + default: + if (node.childNodes) { + node.childNodes.forEach(n => analyze(n)); + } + break; + } + } +} + /** * Noteをフェッチします。 * @@ -87,9 +153,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // リプライ const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null; - // MastodonはHTMLを送り付けてくる - // そして改行は
で表現されている - const { window } = new JSDOM(note.content.replace(/
/g, '\n')); + // テキストのパース + const text = parse(note.tag, note.content); // ユーザーの情報が古かったらついでに更新しておく if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) { diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 6018ac29c4..10b29fe607 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -15,6 +15,7 @@ export interface IObject { icon?: any; image?: any; url?: string; + tag?: any[]; } export interface IActivity extends IObject {