diff options
| author | rinsuki <428rinsuki+git@gmail.com> | 2018-05-17 07:52:24 +0900 |
|---|---|---|
| committer | rinsuki <428rinsuki+git@gmail.com> | 2018-05-17 07:52:24 +0900 |
| commit | 829b4012e6dc14eb64a3d8f60826fe9b6a41b40d (patch) | |
| tree | 42ac37f323db349dca9316e6fdb39fc33b860686 /src/remote/activitypub/models | |
| parent | add yarn.lock to gitignore (diff) | |
| parent | Update deliver.ts (diff) | |
| download | misskey-829b4012e6dc14eb64a3d8f60826fe9b6a41b40d.tar.gz misskey-829b4012e6dc14eb64a3d8f60826fe9b6a41b40d.tar.bz2 misskey-829b4012e6dc14eb64a3d8f60826fe9b6a41b40d.zip | |
Merge branch 'master' into fix/yarn-lock-ignore
Diffstat (limited to 'src/remote/activitypub/models')
| -rw-r--r-- | src/remote/activitypub/models/image.ts | 7 | ||||
| -rw-r--r-- | src/remote/activitypub/models/note.ts | 104 | ||||
| -rw-r--r-- | src/remote/activitypub/models/person.ts | 62 |
3 files changed, 140 insertions, 33 deletions
diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index d7bc5aff2f..0d5a690c6c 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -11,6 +11,11 @@ const log = debug('misskey:activitypub'); * Imageを作成します。 */ export async function createImage(actor: IRemoteUser, value): Promise<IDriveFile> { + // 投稿者が凍結されていたらスキップ + if (actor.isSuspended) { + return null; + } + const image = await new Resolver().resolve(value); if (image.url == null) { @@ -19,7 +24,7 @@ export async function createImage(actor: IRemoteUser, value): Promise<IDriveFile log(`Creating the Image: ${image.url}`); - return await uploadFromUrl(image.url, actor); + return await uploadFromUrl(image.url, actor, null, image.url); } /** diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index ab6dd99a77..91e700ef6f 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -1,4 +1,5 @@ -import { JSDOM } from 'jsdom'; +import * as mongo from 'mongodb'; +import * as parse5 from 'parse5'; import * as debug from 'debug'; import config from '../../../config'; @@ -12,6 +13,76 @@ import { IRemoteUser } from '../../../models/user'; const log = debug('misskey:activitypub'); +function parse(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 getText(node) { + if (node.nodeName == '#text') return node.value; + + if (node.childNodes) { + return node.childNodes.map(n => getText(n)).join(''); + } + + return ''; + } + + function analyze(node) { + switch (node.nodeName) { + case '#text': + text += node.value; + break; + + case 'br': + text += '\n'; + break; + + case 'a': + const txt = getText(node); + + // メンション + if (txt.startsWith('@')) { + const part = txt.split('@'); + + if (part.length == 2) { + //#region ホスト名部分が省略されているので復元する + const href = new URL(node.attrs.find(x => x.name == 'href').value); + const acct = txt + '@' + href.hostname; + text += acct; + break; + //#endregion + } else if (part.length == 3) { + text += txt; + break; + } + } + + if (node.childNodes) { + node.childNodes.forEach(n => analyze(n)); + } + break; + + case 'p': + text += '\n\n'; + if (node.childNodes) { + node.childNodes.forEach(n => analyze(n)); + } + break; + + default: + if (node.childNodes) { + node.childNodes.forEach(n => analyze(n)); + } + break; + } + } +} + /** * Noteをフェッチします。 * @@ -22,7 +93,8 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { - return await Note.findOne({ _id: uri.split('/').pop() }); + const id = new mongo.ObjectID(uri.split('/').pop()); + return await Note.findOne({ _id: id }); } //#region このサーバーに既に登録されていたらそれを返す @@ -45,7 +117,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false const object = await resolver.resolve(value) as any; if (object == null || object.type !== 'Note') { - throw new Error('invalid note'); + log(`invalid note: ${object}`); + return null; } const note: INoteActivityStreamsObject = object; @@ -55,12 +128,23 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // 投稿者をフェッチ const actor = await resolvePerson(note.attributedTo) as IRemoteUser; + // 投稿者が凍結されていたらスキップ + if (actor.isSuspended) { + return null; + } + //#region Visibility let visibility = 'public'; - if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted'; - if (note.cc.length == 0) visibility = 'private'; - // TODO - if (visibility != 'public') throw new Error('unspported visibility'); + let visibleUsers = []; + if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) { + if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) { + visibility = 'home'; + } else { + visibility = 'specified'; + visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); + } + } + if (note.cc.length == 0) visibility = 'followers'; //#endergion // 添付メディア @@ -73,7 +157,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // リプライ const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null; - const { window } = new JSDOM(note.content); + // テキストのパース + const text = parse(note.content); // ユーザーの情報が古かったらついでに更新しておく if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) { @@ -85,10 +170,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false media, reply, renote: undefined, - text: window.document.body.textContent, + text: text, viaMobile: false, geo: undefined, visibility, + visibleUsers, uri: note.id }, silent); } diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index b755b2603a..33280f3d89 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -1,3 +1,4 @@ +import * as mongo from 'mongodb'; import { JSDOM } from 'jsdom'; import { toUnicode } from 'punycode'; import * as debug from 'debug'; @@ -21,7 +22,8 @@ export async function fetchPerson(value: string | IObject, resolver?: Resolver): // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { - return await User.findOne({ _id: uri.split('/').pop() }); + const id = new mongo.ObjectID(uri.split('/').pop()); + return await User.findOne({ _id: id }); } //#region このサーバーに既に登録されていたらそれを返す @@ -47,6 +49,7 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs object == null || object.type !== 'Person' || typeof object.preferredUsername !== 'string' || + typeof object.inbox !== 'string' || !validateUsername(object.preferredUsername) || !isValidName(object.name == '' ? null : object.name) ) { @@ -78,27 +81,39 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs const summaryDOM = JSDOM.fragment(person.summary); // Create user - const user = await User.insert({ - avatarId: null, - bannerId: null, - createdAt: Date.parse(person.published) || null, - description: summaryDOM.textContent, - followersCount, - followingCount, - notesCount, - name: person.name, - driveCapacity: 1024 * 1024 * 8, // 8MiB - username: person.preferredUsername, - usernameLower: person.preferredUsername.toLowerCase(), - host, - publicKey: { - id: person.publicKey.id, - publicKeyPem: person.publicKey.publicKeyPem - }, - inbox: person.inbox, - uri: person.id, - url: person.url - }) as IRemoteUser; + let user: IRemoteUser; + try { + user = await User.insert({ + avatarId: null, + bannerId: null, + createdAt: Date.parse(person.published) || null, + description: summaryDOM.textContent, + followersCount, + followingCount, + notesCount, + name: person.name, + driveCapacity: 1024 * 1024 * 8, // 8MiB + username: person.preferredUsername, + usernameLower: person.preferredUsername.toLowerCase(), + host, + publicKey: { + id: person.publicKey.id, + publicKeyPem: person.publicKey.publicKeyPem + }, + inbox: person.inbox, + endpoints: person.endpoints, + uri: person.id, + url: person.url + }) as IRemoteUser; + } catch (e) { + // duplicate key error + if (e.code === 11000) { + throw new Error('already registered'); + } + + console.error(e); + throw e; + } //#region アイコンとヘッダー画像をフェッチ const [avatarId, bannerId] = (await Promise.all([ @@ -194,7 +209,8 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver) followingCount, notesCount, name: person.name, - url: person.url + url: person.url, + endpoints: person.endpoints } }); } |