summaryrefslogtreecommitdiff
path: root/src/remote/activitypub/models
diff options
context:
space:
mode:
authorrinsuki <428rinsuki+git@gmail.com>2018-05-17 07:52:24 +0900
committerrinsuki <428rinsuki+git@gmail.com>2018-05-17 07:52:24 +0900
commit829b4012e6dc14eb64a3d8f60826fe9b6a41b40d (patch)
tree42ac37f323db349dca9316e6fdb39fc33b860686 /src/remote/activitypub/models
parentadd yarn.lock to gitignore (diff)
parentUpdate deliver.ts (diff)
downloadmisskey-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.ts7
-rw-r--r--src/remote/activitypub/models/note.ts104
-rw-r--r--src/remote/activitypub/models/person.ts62
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
}
});
}