Use PostgreSQL instead of MongoDB (#4572)
* wip * Update note.ts * Update timeline.ts * Update core.ts * wip * Update generate-visibility-query.ts * wip * wip * wip * wip * wip * Update global-timeline.ts * wip * wip * wip * Update vote.ts * wip * wip * Update create.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update files.ts * wip * wip * Update CONTRIBUTING.md * wip * wip * wip * wip * wip * wip * wip * wip * Update read-notification.ts * wip * wip * wip * wip * wip * wip * wip * Update cancel.ts * wip * wip * wip * Update show.ts * wip * wip * Update gen-id.ts * Update create.ts * Update id.ts * wip * wip * wip * wip * wip * wip * wip * Docker: Update files about Docker (#4599) * Docker: Use cache if files used by `yarn install` was not updated This patch reduces the number of times to installing node_modules. For example, `yarn install` step will be skipped when only ".config/default.yml" is updated. * Docker: Migrate MongoDB to Postgresql Misskey uses Postgresql as a database instead of Mongodb since version 11. * Docker: Uncomment about data persistence This patch will save a lot of databases. * wip * wip * wip * Update activitypub.ts * wip * wip * wip * Update logs.ts * wip * Update drive-file.ts * Update register.ts * wip * wip * Update mentions.ts * wip * wip * wip * Update recommendation.ts * wip * Update index.ts * wip * Update recommendation.ts * Doc: Update docker.ja.md and docker.en.md (#1) (#4608) Update how to set up misskey. * wip * ✌️ * wip * Update note.ts * Update postgre.ts * wip * wip * wip * wip * Update add-file.ts * wip * wip * wip * Clean up * Update logs.ts * wip * 🍕 * wip * Ad notes * wip * Update api-visibility.ts * Update note.ts * Update add-file.ts * tests * tests * Update postgre.ts * Update utils.ts * wip * wip * Refactor * wip * Refactor * wip * wip * Update show-users.ts * Update update-instance.ts * wip * Update feed.ts * Update outbox.ts * Update outbox.ts * Update user.ts * wip * Update list.ts * Update update-hashtag.ts * wip * Update update-hashtag.ts * Refactor * Update update.ts * wip * wip * ✌️ * clean up * docs * Update push.ts * wip * Update api.ts * wip * ✌️ * Update make-pagination-query.ts * ✌️ * Delete hashtags.ts * Update instances.ts * Update instances.ts * Update create.ts * Update search.ts * Update reversi-game.ts * Update signup.ts * Update user.ts * id * Update example.yml * 🎨 * objectid * fix * reversi * reversi * Fix bug of chart engine * Add test of chart engine * Improve test * Better testing * Improve chart engine * Refactor * Add test of chart engine * Refactor * Add chart test * Fix bug * コミットし忘れ * Refactoring * ✌️ * Add tests * Add test * Extarct note tests * Refactor * 存在しないユーザーにメンションできなくなっていた問題を修正 * Fix bug * Update update-meta.ts * Fix bug * Update mention.vue * Fix bug * Update meta.ts * Update CONTRIBUTING.md * Fix bug * Fix bug * Fix bug * Clean up * Clean up * Update notification.ts * Clean up * Add mute tests * Add test * Refactor * Add test * Fix test * Refactor * Refactor * Add tests * Update utils.ts * Update utils.ts * Fix test * Update package.json * Update update.ts * Update manifest.ts * Fix bug * Fix bug * Add test * 🎨 * Update endpoint permissions * Updaye permisison * Update person.ts #4299 * データベースと同期しないように * Fix bug * Fix bug * Update reversi-game.ts * Use a feature of Node v11.7.0 to extract a public key (#4644) * wip * wip * ✌️ * Refactoring #1540 * test * test * test * test * test * test * test * Fix bug * Fix test * 🍣 * wip * #4471 * Add test for #4335 * Refactor * Fix test * Add tests * 🕓 * Fix bug * Add test * Add test * rename * Fix bug
This commit is contained in:
parent
13caf37991
commit
f0a29721c9
592 changed files with 13463 additions and 14147 deletions
|
@ -1,8 +1,8 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import User, { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import config from '../../../../config';
|
||||
import accept from '../../../../services/following/requests/accept';
|
||||
import { IFollow } from '../../type';
|
||||
import { Users } from '../../../../models';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
||||
|
@ -11,11 +11,11 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const follower = await User.findOne({
|
||||
_id: new mongo.ObjectID(id.split('/').pop())
|
||||
const follower = await Users.findOne({
|
||||
id: id.split('/').pop()
|
||||
});
|
||||
|
||||
if (follower === null) {
|
||||
if (follower == null) {
|
||||
throw new Error('follower not found');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import acceptFollow from './follow';
|
||||
import { IAccept, IFollow } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IAdd } from '../../type';
|
||||
import { resolveNote } from '../../models/note';
|
||||
import { addPinned } from '../../../../services/i/pin';
|
||||
|
@ -14,7 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
|
|||
|
||||
if (activity.target === actor.featured) {
|
||||
const note = await resolveNote(activity.object);
|
||||
await addPinned(actor, note._id);
|
||||
await addPinned(actor, note.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import announceNote from './note';
|
||||
import { IAnnounce, INote } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Resolver from '../../resolver';
|
||||
import post from '../../../../services/note/create';
|
||||
import { IRemoteUser, IUser } from '../../../../models/user';
|
||||
import { IRemoteUser, User } from '../../../../models/entities/user';
|
||||
import { IAnnounce, INote } from '../../type';
|
||||
import { fetchNote, resolveNote } from '../../models/note';
|
||||
import { resolvePerson } from '../../models/person';
|
||||
import { apLogger } from '../../logger';
|
||||
import { extractDbHost } from '../../../../misc/convert-host';
|
||||
import Instance from '../../../../models/instance';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
|
@ -27,8 +27,8 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
|||
|
||||
// アナウンス先をブロックしてたら中断
|
||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||
const instance = await Instance.findOne({ host: extractDbHost(uri) });
|
||||
if (instance && instance.isBlocked) return;
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
||||
|
||||
// 既に同じURIを持つものが登録されていないかチェック
|
||||
const exist = await fetchNote(uri);
|
||||
|
@ -55,7 +55,7 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
|||
//#region Visibility
|
||||
const visibility = getVisibility(activity.to, activity.cc, actor);
|
||||
|
||||
let visibleUsers: IUser[] = [];
|
||||
let visibleUsers: User[] = [];
|
||||
if (visibility == 'specified') {
|
||||
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import User, { IRemoteUser } from '../../../../models/user';
|
||||
import config from '../../../../config';
|
||||
import { IBlock } from '../../type';
|
||||
import block from '../../../../services/blocking/create';
|
||||
import { apLogger } from '../../logger';
|
||||
import { Users } from '../../../../models';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
|
@ -18,11 +18,9 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const blockee = await User.findOne({
|
||||
_id: new mongo.ObjectID(id.split('/').pop())
|
||||
});
|
||||
const blockee = await Users.findOne(id.split('/').pop());
|
||||
|
||||
if (blockee === null) {
|
||||
if (blockee == null) {
|
||||
throw new Error('blockee not found');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { createImage } from '../../models/image';
|
||||
|
||||
export default async function(actor: IRemoteUser, image: any): Promise<void> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import createImage from './image';
|
||||
import createNote from './note';
|
||||
import { ICreate } from '../../type';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { createNote, fetchNote } from '../../models/note';
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Resolver from '../../resolver';
|
||||
import deleteNote from './note';
|
||||
import Note from '../../../../models/note';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IDelete } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
/**
|
||||
* 削除アクティビティを捌きます
|
||||
|
@ -27,7 +27,7 @@ export default async (actor: IRemoteUser, activity: IDelete): Promise<void> => {
|
|||
break;
|
||||
|
||||
case 'Tombstone':
|
||||
const note = await Note.findOne({ uri });
|
||||
const note = await Notes.findOne({ uri });
|
||||
if (note != null) {
|
||||
deleteNote(actor, uri);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import Note from '../../../../models/note';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import deleteNode from '../../../../services/note/delete';
|
||||
import { apLogger } from '../../logger';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async function(actor: IRemoteUser, uri: string): Promise<void> {
|
||||
logger.info(`Deleting the Note: ${uri}`);
|
||||
|
||||
const note = await Note.findOne({ uri });
|
||||
const note = await Notes.findOne({ uri });
|
||||
|
||||
if (note == null) {
|
||||
throw new Error('note not found');
|
||||
}
|
||||
|
||||
if (!note.userId.equals(actor._id)) {
|
||||
if (note.userId !== actor.id) {
|
||||
throw new Error('投稿を削除しようとしているユーザーは投稿の作成者ではありません');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import User, { IRemoteUser } from '../../../models/user';
|
||||
import { IRemoteUser } from '../../../models/entities/user';
|
||||
import config from '../../../config';
|
||||
import follow from '../../../services/following/create';
|
||||
import { IFollow } from '../type';
|
||||
import { Users } from '../../../models';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||
|
@ -11,11 +11,9 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const followee = await User.findOne({
|
||||
_id: new mongo.ObjectID(id.split('/').pop())
|
||||
});
|
||||
const followee = await Users.findOne(id.split('/').pop());
|
||||
|
||||
if (followee === null) {
|
||||
if (followee == null) {
|
||||
throw new Error('followee not found');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Object } from '../type';
|
||||
import { IRemoteUser } from '../../../models/user';
|
||||
import { IRemoteUser } from '../../../models/entities/user';
|
||||
import create from './create';
|
||||
import performDeleteActivity from './delete';
|
||||
import performUpdateActivity from './update';
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import Note from '../../../models/note';
|
||||
import { IRemoteUser } from '../../../models/user';
|
||||
import { IRemoteUser } from '../../../models/entities/user';
|
||||
import { ILike } from '../type';
|
||||
import create from '../../../services/note/reaction/create';
|
||||
import { Notes } from '../../../models';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||
|
@ -10,10 +9,10 @@ export default async (actor: IRemoteUser, activity: ILike) => {
|
|||
// Transform:
|
||||
// https://misskey.ex/notes/xxxx to
|
||||
// xxxx
|
||||
const noteId = new mongo.ObjectID(id.split('/').pop());
|
||||
const noteId = id.split('/').pop();
|
||||
|
||||
const note = await Note.findOne({ _id: noteId });
|
||||
if (note === null) {
|
||||
const note = await Notes.findOne(noteId);
|
||||
if (note == null) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import User, { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import config from '../../../../config';
|
||||
import reject from '../../../../services/following/requests/reject';
|
||||
import { IFollow } from '../../type';
|
||||
import { Users } from '../../../../models';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
||||
|
@ -11,11 +11,9 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const follower = await User.findOne({
|
||||
_id: new mongo.ObjectID(id.split('/').pop())
|
||||
});
|
||||
const follower = await Users.findOne(id.split('/').pop());
|
||||
|
||||
if (follower === null) {
|
||||
if (follower == null) {
|
||||
throw new Error('follower not found');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import rejectFollow from './follow';
|
||||
import { IReject, IFollow } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IRemove } from '../../type';
|
||||
import { resolveNote } from '../../models/note';
|
||||
import { removePinned } from '../../../../services/i/pin';
|
||||
|
@ -14,7 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
|
|||
|
||||
if (activity.target === actor.featured) {
|
||||
const note = await resolveNote(activity.object);
|
||||
await removePinned(actor, note._id);
|
||||
await removePinned(actor, note.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import User, { IRemoteUser } from '../../../../models/user';
|
||||
import config from '../../../../config';
|
||||
import { IBlock } from '../../type';
|
||||
import unblock from '../../../../services/blocking/delete';
|
||||
import { apLogger } from '../../logger';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { Users } from '../../../../models';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
|
@ -18,11 +18,9 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const blockee = await User.findOne({
|
||||
_id: new mongo.ObjectID(id.split('/').pop())
|
||||
});
|
||||
const blockee = await Users.findOne(id.split('/').pop());
|
||||
|
||||
if (blockee === null) {
|
||||
if (blockee == null) {
|
||||
throw new Error('blockee not found');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import User, { IRemoteUser } from '../../../../models/user';
|
||||
import config from '../../../../config';
|
||||
import unfollow from '../../../../services/following/delete';
|
||||
import cancelRequest from '../../../../services/following/requests/cancel';
|
||||
import { IFollow } from '../../type';
|
||||
import FollowRequest from '../../../../models/follow-request';
|
||||
import Following from '../../../../models/following';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { Users, FollowRequests, Followings } from '../../../../models';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||
|
@ -14,11 +12,9 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const followee = await User.findOne({
|
||||
_id: new mongo.ObjectID(id.split('/').pop())
|
||||
});
|
||||
const followee = await Users.findOne(id.split('/').pop());
|
||||
|
||||
if (followee === null) {
|
||||
if (followee == null) {
|
||||
throw new Error('followee not found');
|
||||
}
|
||||
|
||||
|
@ -26,14 +22,14 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
|||
throw new Error('フォロー解除しようとしているユーザーはローカルユーザーではありません');
|
||||
}
|
||||
|
||||
const req = await FollowRequest.findOne({
|
||||
followerId: actor._id,
|
||||
followeeId: followee._id
|
||||
const req = await FollowRequests.findOne({
|
||||
followerId: actor.id,
|
||||
followeeId: followee.id
|
||||
});
|
||||
|
||||
const following = await Following.findOne({
|
||||
followerId: actor._id,
|
||||
followeeId: followee._id
|
||||
const following = await Followings.findOne({
|
||||
followerId: actor.id,
|
||||
followeeId: followee.id
|
||||
});
|
||||
|
||||
if (req) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IUndo, IFollow, IBlock, ILike } from '../../type';
|
||||
import unfollow from './follow';
|
||||
import unblock from './block';
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { ILike } from '../../type';
|
||||
import Note from '../../../../models/note';
|
||||
import deleteReaction from '../../../../services/note/reaction/delete';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
/**
|
||||
* Process Undo.Like activity
|
||||
|
@ -10,10 +9,10 @@ import deleteReaction from '../../../../services/note/reaction/delete';
|
|||
export default async (actor: IRemoteUser, activity: ILike): Promise<void> => {
|
||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||
|
||||
const noteId = new mongo.ObjectID(id.split('/').pop());
|
||||
const noteId = id.split('/').pop();
|
||||
|
||||
const note = await Note.findOne({ _id: noteId });
|
||||
if (note === null) {
|
||||
const note = await Notes.findOne(noteId);
|
||||
if (note == null) {
|
||||
throw 'note not found';
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IUpdate, IObject } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
import { updateQuestion } from '../../models/question';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { INote } from '../../../models/note';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { toHtml } from '../../../mfm/toHtml';
|
||||
import { parse } from '../../../mfm/parse';
|
||||
|
||||
export default function(note: INote) {
|
||||
let html = toHtml(parse(note.text), note.mentionedRemoteUsers);
|
||||
export default function(note: Note) {
|
||||
let html = toHtml(parse(note.text), JSON.parse(note.mentionedRemoteUsers));
|
||||
if (html == null) html = '<p>.</p>';
|
||||
|
||||
return html;
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import uploadFromUrl from '../../../services/drive/upload-from-url';
|
||||
import { IRemoteUser } from '../../../models/user';
|
||||
import DriveFile, { IDriveFile } from '../../../models/drive-file';
|
||||
import { IRemoteUser } from '../../../models/entities/user';
|
||||
import Resolver from '../resolver';
|
||||
import fetchMeta from '../../../misc/fetch-meta';
|
||||
import { apLogger } from '../logger';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { DriveFiles } from '../../../models';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
/**
|
||||
* Imageを作成します。
|
||||
*/
|
||||
export async function createImage(actor: IRemoteUser, value: any): Promise<IDriveFile> {
|
||||
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
return null;
|
||||
|
@ -39,18 +40,16 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
|
|||
throw e;
|
||||
}
|
||||
|
||||
if (file.metadata.isRemote) {
|
||||
if (file.isRemote) {
|
||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
||||
// URLを更新する
|
||||
if (file.metadata.url !== image.url) {
|
||||
file = await DriveFile.findOneAndUpdate({ _id: file._id }, {
|
||||
$set: {
|
||||
'metadata.url': image.url,
|
||||
'metadata.uri': image.url
|
||||
}
|
||||
}, {
|
||||
returnNewDocument: true
|
||||
if (file.url !== image.url) {
|
||||
await DriveFiles.update({ id: file.id }, {
|
||||
url: image.url,
|
||||
uri: image.url
|
||||
});
|
||||
|
||||
file = DriveFiles.findOne(file.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +62,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
|
|||
* Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
|
||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
export async function resolveImage(actor: IRemoteUser, value: any): Promise<IDriveFile> {
|
||||
export async function resolveImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
|
||||
// TODO
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as promiseLimit from 'promise-limit';
|
||||
|
||||
import config from '../../../config';
|
||||
import Resolver from '../resolver';
|
||||
import Note, { INote } from '../../../models/note';
|
||||
import post from '../../../services/note/create';
|
||||
import { INote as INoteActivityStreamsObject, IObject } from '../type';
|
||||
import { resolvePerson, updatePerson } from './person';
|
||||
import { resolveImage } from './image';
|
||||
import { IRemoteUser, IUser } from '../../../models/user';
|
||||
import { IRemoteUser, User } from '../../../models/entities/user';
|
||||
import { fromHtml } from '../../../mfm/fromHtml';
|
||||
import Emoji, { IEmoji } from '../../../models/emoji';
|
||||
import { ITag, extractHashtags } from './tag';
|
||||
import { toUnicode } from 'punycode';
|
||||
import { unique, concat, difference } from '../../../prelude/array';
|
||||
import { extractPollFromQuestion } from './question';
|
||||
import vote from '../../../services/note/polls/vote';
|
||||
import { apLogger } from '../logger';
|
||||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
||||
import Instance from '../../../models/instance';
|
||||
import { extractDbHost } from '../../../misc/convert-host';
|
||||
import { Notes, Emojis, Polls } from '../../../models';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { IObject, INote } from '../type';
|
||||
import { Emoji } from '../../../models/entities/emoji';
|
||||
import { genId } from '../../../misc/gen-id';
|
||||
import fetchMeta from '../../../misc/fetch-meta';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
|
@ -29,17 +30,17 @@ const logger = apLogger;
|
|||
*
|
||||
* Misskeyに対象のNoteが登録されていればそれを返します。
|
||||
*/
|
||||
export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<INote> {
|
||||
export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note> {
|
||||
const uri = typeof value == 'string' ? value : value.id;
|
||||
|
||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||
if (uri.startsWith(config.url + '/')) {
|
||||
const id = new mongo.ObjectID(uri.split('/').pop());
|
||||
return await Note.findOne({ _id: id });
|
||||
const id = uri.split('/').pop();
|
||||
return await Notes.findOne(id);
|
||||
}
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await Note.findOne({ uri });
|
||||
const exist = await Notes.findOne({ uri });
|
||||
|
||||
if (exist) {
|
||||
return exist;
|
||||
|
@ -52,7 +53,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P
|
|||
/**
|
||||
* Noteを作成します。
|
||||
*/
|
||||
export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<INote> {
|
||||
export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<Note> {
|
||||
if (resolver == null) resolver = new Resolver();
|
||||
|
||||
const object: any = await resolver.resolve(value);
|
||||
|
@ -68,7 +69,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
return null;
|
||||
}
|
||||
|
||||
const note: INoteActivityStreamsObject = object;
|
||||
const note: INote = object;
|
||||
|
||||
logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
||||
|
||||
|
@ -87,7 +88,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
note.cc = note.cc == null ? [] : typeof note.cc == 'string' ? [note.cc] : note.cc;
|
||||
|
||||
let visibility = 'public';
|
||||
let visibleUsers: IUser[] = [];
|
||||
let visibleUsers: User[] = [];
|
||||
if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
||||
if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
||||
visibility = 'home';
|
||||
|
@ -113,12 +114,12 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
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(() => resolveImage(actor, x)) as Promise<IDriveFile>)))
|
||||
? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise<DriveFile>)))
|
||||
.filter(image => image != null)
|
||||
: [];
|
||||
|
||||
// リプライ
|
||||
const reply: INote = note.inReplyTo
|
||||
const reply: Note = note.inReplyTo
|
||||
? await resolveNote(note.inReplyTo, resolver).catch(e => {
|
||||
// 4xxの場合はリプライしてないことにする
|
||||
if (e.statusCode >= 400 && e.statusCode < 500) {
|
||||
|
@ -131,7 +132,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
: null;
|
||||
|
||||
// 引用
|
||||
let quote: INote;
|
||||
let quote: Note;
|
||||
|
||||
if (note._misskey_quote && typeof note._misskey_quote == 'string') {
|
||||
quote = await resolveNote(note._misskey_quote).catch(e => {
|
||||
|
@ -151,22 +152,23 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
const text = note._misskey_content || fromHtml(note.content);
|
||||
|
||||
// vote
|
||||
if (reply && reply.poll) {
|
||||
if (reply && reply.hasPoll) {
|
||||
const poll = await Polls.findOne({ noteId: reply.id });
|
||||
const tryCreateVote = async (name: string, index: number): Promise<null> => {
|
||||
if (reply.poll.expiresAt && Date.now() > new Date(reply.poll.expiresAt).getTime()) {
|
||||
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
|
||||
logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
||||
} else if (index >= 0) {
|
||||
logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
||||
await vote(actor, reply, index);
|
||||
|
||||
// リモートフォロワーにUpdate配信
|
||||
deliverQuestionUpdate(reply._id);
|
||||
deliverQuestionUpdate(reply.id);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (note.name) {
|
||||
return await tryCreateVote(note.name, reply.poll.choices.findIndex(x => x.text === note.name));
|
||||
return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name));
|
||||
}
|
||||
|
||||
// 後方互換性のため
|
||||
|
@ -181,7 +183,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
|
||||
const emojis = await extractEmojis(note.tag, actor.host).catch(e => {
|
||||
logger.info(`extractEmojis: ${e}`);
|
||||
return [] as IEmoji[];
|
||||
return [] as Emoji[];
|
||||
});
|
||||
|
||||
const apEmojis = emojis.map(emoji => emoji.name);
|
||||
|
@ -222,13 +224,13 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
* Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ
|
||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<INote> {
|
||||
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note> {
|
||||
const uri = typeof value == 'string' ? value : value.id;
|
||||
|
||||
// ブロックしてたら中断
|
||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||
const instance = await Instance.findOne({ host: extractDbHost(uri) });
|
||||
if (instance && instance.isBlocked) throw { statusCode: 451 };
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 };
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await fetchNote(uri);
|
||||
|
@ -255,7 +257,7 @@ export async function extractEmojis(tags: ITag[], host_: string) {
|
|||
eomjiTags.map(async tag => {
|
||||
const name = tag.name.replace(/^:/, '').replace(/:$/, '');
|
||||
|
||||
const exists = await Emoji.findOne({
|
||||
const exists = await Emojis.findOne({
|
||||
host,
|
||||
name
|
||||
});
|
||||
|
@ -263,31 +265,37 @@ export async function extractEmojis(tags: ITag[], host_: string) {
|
|||
if (exists) {
|
||||
if ((tag.updated != null && exists.updatedAt == null)
|
||||
|| (tag.id != null && exists.uri == null)
|
||||
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)) {
|
||||
return await Emoji.findOneAndUpdate({
|
||||
host,
|
||||
name,
|
||||
}, {
|
||||
$set: {
|
||||
uri: tag.id,
|
||||
url: tag.icon.url,
|
||||
updatedAt: new Date(tag.updated),
|
||||
}
|
||||
});
|
||||
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|
||||
) {
|
||||
await Emojis.update({
|
||||
host,
|
||||
name,
|
||||
}, {
|
||||
uri: tag.id,
|
||||
url: tag.icon.url,
|
||||
updatedAt: new Date(tag.updated),
|
||||
});
|
||||
|
||||
return await Emojis.findOne({
|
||||
host,
|
||||
name
|
||||
});
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
logger.info(`register emoji host=${host}, name=${name}`);
|
||||
|
||||
return await Emoji.insert({
|
||||
return await Emojis.save({
|
||||
id: genId(),
|
||||
host,
|
||||
name,
|
||||
uri: tag.id,
|
||||
url: tag.icon.url,
|
||||
updatedAt: tag.updated ? new Date(tag.updated) : undefined,
|
||||
aliases: []
|
||||
});
|
||||
} as Emoji);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -298,7 +306,7 @@ async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: strin
|
|||
|
||||
const limit = promiseLimit(2);
|
||||
const users = await Promise.all(
|
||||
uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise<IUser>)
|
||||
uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise<User>)
|
||||
);
|
||||
|
||||
return users.filter(x => x != null);
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as promiseLimit from 'promise-limit';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
import config from '../../../config';
|
||||
import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user';
|
||||
import Resolver from '../resolver';
|
||||
import { resolveImage } from './image';
|
||||
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
|
||||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import Meta from '../../../models/meta';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { fromHtml } from '../../../mfm/fromHtml';
|
||||
import usersChart from '../../../services/chart/users';
|
||||
import instanceChart from '../../../services/chart/instance';
|
||||
import { URL } from 'url';
|
||||
import { resolveNote, extractEmojis } from './note';
|
||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||
import Instance from '../../../models/instance';
|
||||
import getDriveFileUrl from '../../../misc/get-drive-file-url';
|
||||
import { IEmoji } from '../../../models/emoji';
|
||||
import { ITag, extractHashtags } from './tag';
|
||||
import Following from '../../../models/following';
|
||||
import { IIdentifier } from './identifier';
|
||||
import { apLogger } from '../logger';
|
||||
import { INote } from '../../../models/note';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { updateHashtag } from '../../../services/update-hashtag';
|
||||
import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserServiceLinkings, UserPublickeys } from '../../../models';
|
||||
import { User, IRemoteUser } from '../../../models/entities/user';
|
||||
import { Emoji } from '../../../models/entities/emoji';
|
||||
import { UserNotePining } from '../../../models/entities/user-note-pinings';
|
||||
import { genId } from '../../../misc/gen-id';
|
||||
import { UserServiceLinking } from '../../../models/entities/user-service-linking';
|
||||
import { instanceChart, usersChart } from '../../../services/chart';
|
||||
import { UserPublickey } from '../../../models/entities/user-publickey';
|
||||
import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
|
||||
const logger = apLogger;
|
||||
|
||||
/**
|
||||
|
@ -50,11 +50,11 @@ function validatePerson(x: any, uri: string) {
|
|||
return new Error('invalid person: inbox is not a string');
|
||||
}
|
||||
|
||||
if (!validateUsername(x.preferredUsername, true)) {
|
||||
if (!Users.validateUsername(x.preferredUsername, true)) {
|
||||
return new Error('invalid person: invalid username');
|
||||
}
|
||||
|
||||
if (!isValidName(x.name == '' ? null : x.name)) {
|
||||
if (!Users.isValidName(x.name == '' ? null : x.name)) {
|
||||
return new Error('invalid person: invalid name');
|
||||
}
|
||||
|
||||
|
@ -84,17 +84,17 @@ function validatePerson(x: any, uri: string) {
|
|||
*
|
||||
* Misskeyに対象のPersonが登録されていればそれを返します。
|
||||
*/
|
||||
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<IUser> {
|
||||
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User> {
|
||||
if (typeof uri !== 'string') throw 'uri is not string';
|
||||
|
||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||
if (uri.startsWith(config.url + '/')) {
|
||||
const id = new mongo.ObjectID(uri.split('/').pop());
|
||||
return await User.findOne({ _id: id });
|
||||
const id = uri.split('/').pop();
|
||||
return await Users.findOne(id);
|
||||
}
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await User.findOne({ uri });
|
||||
const exist = await Users.findOne({ uri });
|
||||
|
||||
if (exist) {
|
||||
return exist;
|
||||
|
@ -107,7 +107,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<IUs
|
|||
/**
|
||||
* Personを作成します。
|
||||
*/
|
||||
export async function createPerson(uri: string, resolver?: Resolver): Promise<IUser> {
|
||||
export async function createPerson(uri: string, resolver?: Resolver): Promise<User> {
|
||||
if (typeof uri !== 'string') throw 'uri is not string';
|
||||
|
||||
if (resolver == null) resolver = new Resolver();
|
||||
|
@ -124,21 +124,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||
|
||||
logger.info(`Creating the Person: ${person.id}`);
|
||||
|
||||
const [followersCount = 0, followingCount = 0, notesCount = 0] = await Promise.all([
|
||||
resolver.resolve(person.followers).then(
|
||||
resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
|
||||
() => undefined
|
||||
),
|
||||
resolver.resolve(person.following).then(
|
||||
resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
|
||||
() => undefined
|
||||
),
|
||||
resolver.resolve(person.outbox).then(
|
||||
resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
|
||||
() => undefined
|
||||
)
|
||||
]);
|
||||
|
||||
const host = toUnicode(new URL(object.id).hostname.toLowerCase());
|
||||
|
||||
const { fields, services } = analyzeAttachments(person.attachment);
|
||||
|
@ -150,24 +135,18 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||
// Create user
|
||||
let user: IRemoteUser;
|
||||
try {
|
||||
user = await User.insert({
|
||||
user = await Users.save({
|
||||
id: genId(),
|
||||
avatarId: null,
|
||||
bannerId: null,
|
||||
createdAt: Date.parse(person.published) || null,
|
||||
createdAt: Date.parse(person.published) || new Date(),
|
||||
lastFetchedAt: new Date(),
|
||||
description: fromHtml(person.summary),
|
||||
followersCount,
|
||||
followingCount,
|
||||
notesCount,
|
||||
name: person.name,
|
||||
isLocked: person.manuallyApprovesFollowers,
|
||||
username: person.preferredUsername,
|
||||
usernameLower: person.preferredUsername.toLowerCase(),
|
||||
host,
|
||||
publicKey: {
|
||||
id: person.publicKey.id,
|
||||
publicKeyPem: person.publicKey.publicKeyPem
|
||||
},
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||
featured: person.featured,
|
||||
|
@ -179,10 +158,22 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||
tags,
|
||||
isBot,
|
||||
isCat: (person as any).isCat === true
|
||||
}) as IRemoteUser;
|
||||
} as Partial<User>) as IRemoteUser;
|
||||
|
||||
await UserPublickeys.save({
|
||||
id: genId(),
|
||||
userId: user.id,
|
||||
keyId: person.publicKey.id,
|
||||
keyPem: person.publicKey.publicKeyPem
|
||||
} as UserPublickey);
|
||||
|
||||
await UserServiceLinkings.save({
|
||||
id: genId(),
|
||||
userId: user.id,
|
||||
} as UserServiceLinking);
|
||||
} catch (e) {
|
||||
// duplicate key error
|
||||
if (e.code === 11000) {
|
||||
if (isDuplicateKeyValueError(e)) {
|
||||
throw new Error('already registered');
|
||||
}
|
||||
|
||||
|
@ -190,33 +181,25 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||
throw e;
|
||||
}
|
||||
|
||||
await UserServiceLinkings.save({
|
||||
id: genId(),
|
||||
userId: user.id
|
||||
} as UserServiceLinking);
|
||||
|
||||
// Register host
|
||||
registerOrFetchInstanceDoc(host).then(i => {
|
||||
Instance.update({ _id: i._id }, {
|
||||
$inc: {
|
||||
usersCount: 1
|
||||
}
|
||||
});
|
||||
|
||||
Instances.increment({ id: i.id }, 'usersCount', 1);
|
||||
instanceChart.newUser(i.host);
|
||||
});
|
||||
|
||||
//#region Increment users count
|
||||
Meta.update({}, {
|
||||
$inc: {
|
||||
'stats.usersCount': 1
|
||||
}
|
||||
}, { upsert: true });
|
||||
|
||||
usersChart.update(user, true);
|
||||
//#endregion
|
||||
|
||||
// ハッシュタグ更新
|
||||
for (const tag of tags) updateHashtag(user, tag, true, true);
|
||||
for (const tag of (user.tags || []).filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false);
|
||||
|
||||
//#region アイコンとヘッダー画像をフェッチ
|
||||
const [avatar, banner] = (await Promise.all<IDriveFile>([
|
||||
const [avatar, banner] = (await Promise.all<DriveFile>([
|
||||
person.icon,
|
||||
person.image
|
||||
].map(img =>
|
||||
|
@ -225,22 +208,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||
: resolveImage(user, img).catch(() => null)
|
||||
)));
|
||||
|
||||
const avatarId = avatar ? avatar._id : null;
|
||||
const bannerId = banner ? banner._id : null;
|
||||
const avatarUrl = getDriveFileUrl(avatar, true);
|
||||
const bannerUrl = getDriveFileUrl(banner, false);
|
||||
const avatarColor = avatar && avatar.metadata.properties.avgColor ? avatar.metadata.properties.avgColor : null;
|
||||
const bannerColor = banner && avatar.metadata.properties.avgColor ? banner.metadata.properties.avgColor : null;
|
||||
const avatarId = avatar ? avatar.id : null;
|
||||
const bannerId = banner ? banner.id : null;
|
||||
const avatarUrl = DriveFiles.getPublicUrl(avatar);
|
||||
const bannerUrl = DriveFiles.getPublicUrl(banner);
|
||||
const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null;
|
||||
const bannerColor = banner && avatar.properties.avgColor ? banner.properties.avgColor : null;
|
||||
|
||||
await User.update({ _id: user._id }, {
|
||||
$set: {
|
||||
avatarId,
|
||||
bannerId,
|
||||
avatarUrl,
|
||||
bannerUrl,
|
||||
avatarColor,
|
||||
bannerColor
|
||||
}
|
||||
await Users.update(user.id, {
|
||||
avatarId,
|
||||
bannerId,
|
||||
avatarUrl,
|
||||
bannerUrl,
|
||||
avatarColor,
|
||||
bannerColor
|
||||
});
|
||||
|
||||
user.avatarId = avatarId;
|
||||
|
@ -254,19 +235,17 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||
//#region カスタム絵文字取得
|
||||
const emojis = await extractEmojis(person.tag, host).catch(e => {
|
||||
logger.info(`extractEmojis: ${e}`);
|
||||
return [] as IEmoji[];
|
||||
return [] as Emoji[];
|
||||
});
|
||||
|
||||
const emojiNames = emojis.map(emoji => emoji.name);
|
||||
|
||||
await User.update({ _id: user._id }, {
|
||||
$set: {
|
||||
emojis: emojiNames
|
||||
}
|
||||
await Users.update(user.id, {
|
||||
emojis: emojiNames
|
||||
});
|
||||
//#endregion
|
||||
|
||||
await updateFeatured(user._id).catch(err => logger.error(err));
|
||||
await updateFeatured(user.id).catch(err => logger.error(err));
|
||||
|
||||
return user;
|
||||
}
|
||||
|
@ -287,7 +266,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
}
|
||||
|
||||
//#region このサーバーに既に登録されているか
|
||||
const exist = await User.findOne({ uri }) as IRemoteUser;
|
||||
const exist = await Users.findOne({ uri }) as IRemoteUser;
|
||||
|
||||
if (exist == null) {
|
||||
return;
|
||||
|
@ -295,10 +274,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
//#endregion
|
||||
|
||||
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
|
||||
await User.update({ _id: exist._id }, {
|
||||
$set: {
|
||||
lastFetchedAt: new Date(),
|
||||
},
|
||||
await Users.update(exist.id, {
|
||||
lastFetchedAt: new Date(),
|
||||
});
|
||||
|
||||
if (resolver == null) resolver = new Resolver();
|
||||
|
@ -315,23 +292,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
|
||||
logger.info(`Updating the Person: ${person.id}`);
|
||||
|
||||
const [followersCount = 0, followingCount = 0, notesCount = 0] = await Promise.all([
|
||||
resolver.resolve(person.followers).then(
|
||||
resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
|
||||
() => undefined
|
||||
),
|
||||
resolver.resolve(person.following).then(
|
||||
resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
|
||||
() => undefined
|
||||
),
|
||||
resolver.resolve(person.outbox).then(
|
||||
resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined,
|
||||
() => undefined
|
||||
)
|
||||
]);
|
||||
|
||||
// アイコンとヘッダー画像をフェッチ
|
||||
const [avatar, banner] = (await Promise.all<IDriveFile>([
|
||||
const [avatar, banner] = (await Promise.all<DriveFile>([
|
||||
person.icon,
|
||||
person.image
|
||||
].map(img =>
|
||||
|
@ -343,7 +305,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
// カスタム絵文字取得
|
||||
const emojis = await extractEmojis(person.tag, exist.host).catch(e => {
|
||||
logger.info(`extractEmojis: ${e}`);
|
||||
return [] as IEmoji[];
|
||||
return [] as Emoji[];
|
||||
});
|
||||
|
||||
const emojiNames = emojis.map(emoji => emoji.name);
|
||||
|
@ -359,40 +321,45 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
featured: person.featured,
|
||||
emojis: emojiNames,
|
||||
description: fromHtml(person.summary),
|
||||
followersCount,
|
||||
followingCount,
|
||||
notesCount,
|
||||
name: person.name,
|
||||
url: person.url,
|
||||
endpoints: person.endpoints,
|
||||
fields,
|
||||
...services,
|
||||
tags,
|
||||
isBot: object.type == 'Service',
|
||||
isCat: (person as any).isCat === true,
|
||||
isLocked: person.manuallyApprovesFollowers,
|
||||
createdAt: Date.parse(person.published) || null,
|
||||
publicKey: {
|
||||
id: person.publicKey.id,
|
||||
publicKeyPem: person.publicKey.publicKeyPem
|
||||
},
|
||||
} as any;
|
||||
createdAt: new Date(Date.parse(person.published)) || null,
|
||||
} as Partial<User>;
|
||||
|
||||
if (avatar) {
|
||||
updates.avatarId = avatar._id;
|
||||
updates.avatarUrl = getDriveFileUrl(avatar, true);
|
||||
updates.avatarColor = avatar.metadata.properties.avgColor ? avatar.metadata.properties.avgColor : null;
|
||||
updates.avatarId = avatar.id;
|
||||
updates.avatarUrl = DriveFiles.getPublicUrl(avatar);
|
||||
updates.avatarColor = avatar.properties.avgColor ? avatar.properties.avgColor : null;
|
||||
}
|
||||
|
||||
if (banner) {
|
||||
updates.bannerId = banner._id;
|
||||
updates.bannerUrl = getDriveFileUrl(banner, true);
|
||||
updates.bannerColor = banner.metadata.properties.avgColor ? banner.metadata.properties.avgColor : null;
|
||||
updates.bannerId = banner.id;
|
||||
updates.bannerUrl = DriveFiles.getPublicUrl(banner);
|
||||
updates.bannerColor = banner.properties.avgColor ? banner.properties.avgColor : null;
|
||||
}
|
||||
|
||||
// Update user
|
||||
await User.update({ _id: exist._id }, {
|
||||
$set: updates
|
||||
await Users.update(exist.id, updates);
|
||||
|
||||
await UserPublickeys.update({ userId: exist.id }, {
|
||||
keyId: person.publicKey.id,
|
||||
keyPem: person.publicKey.publicKeyPem
|
||||
});
|
||||
|
||||
await UserServiceLinkings.update({ userId: exist.id }, {
|
||||
twitterUserId: services.twitter.userId,
|
||||
twitterScreenName: services.twitter.screenName,
|
||||
githubId: services.github.id,
|
||||
githubLogin: services.github.login,
|
||||
discordId: services.discord.id,
|
||||
discordUsername: services.discord.username,
|
||||
discordDiscriminator: services.discord.discriminator,
|
||||
});
|
||||
|
||||
// ハッシュタグ更新
|
||||
|
@ -400,17 +367,13 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
for (const tag of (exist.tags || []).filter(x => !tags.includes(x))) updateHashtag(exist, tag, true, false);
|
||||
|
||||
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
|
||||
await Following.update({
|
||||
followerId: exist._id
|
||||
await Followings.update({
|
||||
followerId: exist.id
|
||||
}, {
|
||||
$set: {
|
||||
'_follower.sharedInbox': person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined)
|
||||
}
|
||||
}, {
|
||||
multi: true
|
||||
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined)
|
||||
});
|
||||
|
||||
await updateFeatured(exist._id).catch(err => logger.error(err));
|
||||
await updateFeatured(exist.id).catch(err => logger.error(err));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -419,7 +382,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<IUser> {
|
||||
export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<User> {
|
||||
if (typeof uri !== 'string') throw 'uri is not string';
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
|
@ -492,9 +455,9 @@ export function analyzeAttachments(attachments: ITag[]) {
|
|||
return { fields, services };
|
||||
}
|
||||
|
||||
export async function updateFeatured(userId: mongo.ObjectID) {
|
||||
const user = await User.findOne({ _id: userId });
|
||||
if (!isRemoteUser(user)) return;
|
||||
export async function updateFeatured(userId: User['id']) {
|
||||
const user = await Users.findOne(userId);
|
||||
if (!Users.isRemoteUser(user)) return;
|
||||
if (!user.featured) return;
|
||||
|
||||
logger.info(`Updating the featured: ${user.uri}`);
|
||||
|
@ -515,11 +478,14 @@ export async function updateFeatured(userId: mongo.ObjectID) {
|
|||
const featuredNotes = await Promise.all(items
|
||||
.filter(item => item.type === 'Note')
|
||||
.slice(0, 5)
|
||||
.map(item => limit(() => resolveNote(item, resolver)) as Promise<INote>));
|
||||
.map(item => limit(() => resolveNote(item, resolver)) as Promise<Note>));
|
||||
|
||||
await User.update({ _id: user._id }, {
|
||||
$set: {
|
||||
pinnedNoteIds: featuredNotes.filter(note => note != null).map(note => note._id)
|
||||
}
|
||||
});
|
||||
for (const note of featuredNotes.filter(note => note != null)) {
|
||||
UserNotePinings.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
noteId: note.id
|
||||
} as UserNotePining);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import config from '../../../config';
|
||||
import Note, { IChoice, IPoll } from '../../../models/note';
|
||||
import Resolver from '../resolver';
|
||||
import { IQuestion } from '../type';
|
||||
import { apLogger } from '../logger';
|
||||
import { Notes, Polls } from '../../../models';
|
||||
import { IPoll } from '../../../models/entities/poll';
|
||||
|
||||
export async function extractPollFromQuestion(source: string | IQuestion): Promise<IPoll> {
|
||||
const question = typeof source === 'string' ? await new Resolver().resolve(source) as IQuestion : source;
|
||||
|
@ -14,14 +15,14 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi
|
|||
}
|
||||
|
||||
const choices = question[multiple ? 'anyOf' : 'oneOf']
|
||||
.map((x, i) => ({
|
||||
id: i,
|
||||
text: x.name,
|
||||
votes: x.replies && x.replies.totalItems || x._misskey_votes || 0,
|
||||
} as IChoice));
|
||||
.map((x, i) => x.name);
|
||||
|
||||
const votes = question[multiple ? 'anyOf' : 'oneOf']
|
||||
.map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0);
|
||||
|
||||
return {
|
||||
choices,
|
||||
votes,
|
||||
multiple,
|
||||
expiresAt
|
||||
};
|
||||
|
@ -39,9 +40,11 @@ export async function updateQuestion(value: any) {
|
|||
if (uri.startsWith(config.url + '/')) throw 'uri points local';
|
||||
|
||||
//#region このサーバーに既に登録されているか
|
||||
const note = await Note.findOne({ uri });
|
||||
|
||||
const note = await Notes.findOne({ uri });
|
||||
if (note == null) throw 'Question is not registed';
|
||||
|
||||
const poll = await Polls.findOne({ noteId: note.id });
|
||||
if (poll == null) throw 'Question is not registed';
|
||||
//#endregion
|
||||
|
||||
// resolve new Question object
|
||||
|
@ -52,27 +55,25 @@ export async function updateQuestion(value: any) {
|
|||
if (question.type !== 'Question') throw 'object is not a Question';
|
||||
|
||||
const apChoices = question.oneOf || question.anyOf;
|
||||
const dbChoices = note.poll.choices;
|
||||
|
||||
let changed = false;
|
||||
|
||||
for (const db of dbChoices) {
|
||||
const oldCount = db.votes;
|
||||
const newCount = apChoices.filter(ap => ap.name === db.text)[0].replies.totalItems;
|
||||
for (const choice of poll.choices) {
|
||||
const oldCount = poll.votes[poll.choices.indexOf(choice)];
|
||||
const newCount = apChoices.filter(ap => ap.name === choice)[0].replies.totalItems;
|
||||
|
||||
if (oldCount != newCount) {
|
||||
changed = true;
|
||||
db.votes = newCount;
|
||||
poll.votes[poll.choices.indexOf(choice)] = newCount;
|
||||
}
|
||||
}
|
||||
|
||||
await Note.update({
|
||||
_id: note._id
|
||||
}, {
|
||||
$set: {
|
||||
'poll.choices': dbChoices,
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
await Notes.update(note.id, {
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
await Polls.update(poll.id, {
|
||||
votes: poll.votes
|
||||
});
|
||||
|
||||
return changed;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Object } from './type';
|
||||
import { IRemoteUser } from '../../models/user';
|
||||
import { IRemoteUser } from '../../models/entities/user';
|
||||
import kernel from './kernel';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
|
||||
export default (object: any, user: ILocalUser) => ({
|
||||
type: 'Accept',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
|
||||
export default (user: ILocalUser, target: any, object: any) => ({
|
||||
type: 'Add',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
target,
|
||||
object
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import config from '../../../config';
|
||||
import { INote } from '../../../models/note';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
|
||||
export default (object: any, note: INote) => {
|
||||
export default (object: any, note: Note) => {
|
||||
const attributedTo = `${config.url}/users/${note.userId}`;
|
||||
|
||||
let to: string[] = [];
|
||||
|
@ -18,7 +18,7 @@ export default (object: any, note: INote) => {
|
|||
}
|
||||
|
||||
return {
|
||||
id: `${config.url}/notes/${note._id}/activity`,
|
||||
id: `${config.url}/notes/${note.id}/activity`,
|
||||
actor: `${config.url}/users/${note.userId}`,
|
||||
type: 'Announce',
|
||||
published: note.createdAt.toISOString(),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser, IRemoteUser } from '../../../models/user';
|
||||
import { ILocalUser, IRemoteUser } from '../../../models/entities/user';
|
||||
|
||||
export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({
|
||||
type: 'Block',
|
||||
actor: `${config.url}/users/${blocker._id}`,
|
||||
actor: `${config.url}/users/${blocker.id}`,
|
||||
object: blockee.uri
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import config from '../../../config';
|
||||
import { INote } from '../../../models/note';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
|
||||
export default (object: any, note: INote) => {
|
||||
export default (object: any, note: Note) => {
|
||||
const activity = {
|
||||
id: `${config.url}/notes/${note._id}/activity`,
|
||||
id: `${config.url}/notes/${note.id}/activity`,
|
||||
actor: `${config.url}/users/${note.userId}`,
|
||||
type: 'Create',
|
||||
published: note.createdAt.toISOString(),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
|
||||
export default (object: any, user: ILocalUser) => ({
|
||||
type: 'Delete',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import getDriveFileUrl from '../../../misc/get-drive-file-url';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { DriveFiles } from '../../../models';
|
||||
|
||||
export default (file: IDriveFile) => ({
|
||||
export default (file: DriveFile) => ({
|
||||
type: 'Document',
|
||||
mediaType: file.contentType,
|
||||
url: getDriveFileUrl(file)
|
||||
mediaType: file.type,
|
||||
url: DriveFiles.getPublicUrl(file)
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IEmoji } from '../../../models/emoji';
|
||||
import config from '../../../config';
|
||||
import { Emoji } from '../../../models/entities/emoji';
|
||||
|
||||
export default (emoji: IEmoji) => ({
|
||||
export default (emoji: Emoji) => ({
|
||||
id: `${config.url}/emojis/${emoji.name}`,
|
||||
type: 'Emoji',
|
||||
name: `:${emoji.name}:`,
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
import config from '../../../config';
|
||||
import * as mongo from 'mongodb';
|
||||
import User, { isLocalUser } from '../../../models/user';
|
||||
import { Users } from '../../../models';
|
||||
import { User } from '../../../models/entities/user';
|
||||
|
||||
/**
|
||||
* Convert (local|remote)(Follower|Followee)ID to URL
|
||||
* @param id Follower|Followee ID
|
||||
*/
|
||||
export default async function renderFollowUser(id: mongo.ObjectID): Promise<any> {
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: id
|
||||
});
|
||||
|
||||
return isLocalUser(user) ? `${config.url}/users/${user._id}` : user.uri;
|
||||
export default async function renderFollowUser(id: User['id']): Promise<any> {
|
||||
const user = await Users.findOne(id);
|
||||
return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import config from '../../../config';
|
||||
import { IUser, isLocalUser } from '../../../models/user';
|
||||
import { User } from '../../../models/entities/user';
|
||||
import { Users } from '../../../models';
|
||||
|
||||
export default (follower: IUser, followee: IUser, requestId?: string) => {
|
||||
export default (follower: User, followee: User, requestId?: string) => {
|
||||
const follow = {
|
||||
type: 'Follow',
|
||||
actor: isLocalUser(follower) ? `${config.url}/users/${follower._id}` : follower.uri,
|
||||
object: isLocalUser(followee) ? `${config.url}/users/${followee._id}` : followee.uri
|
||||
actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri,
|
||||
object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri
|
||||
} as any;
|
||||
|
||||
if (requestId) follow.id = requestId;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import getDriveFileUrl from '../../../misc/get-drive-file-url';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { DriveFiles } from '../../../models';
|
||||
|
||||
export default (file: IDriveFile) => ({
|
||||
export default (file: DriveFile) => ({
|
||||
type: 'Image',
|
||||
url: getDriveFileUrl(file),
|
||||
sensitive: file.metadata.isSensitive
|
||||
url: DriveFiles.getPublicUrl(file),
|
||||
sensitive: file.isSensitive
|
||||
});
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { createPublicKey } from 'crypto';
|
||||
import config from '../../../config';
|
||||
import { extractPublic } from '../../../crypto_key';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
import { UserKeypair } from '../../../models/entities/user-keypair';
|
||||
|
||||
export default (user: ILocalUser) => ({
|
||||
id: `${config.url}/users/${user._id}/publickey`,
|
||||
export default (user: ILocalUser, key: UserKeypair) => ({
|
||||
id: `${config.url}/users/${user.id}/publickey`,
|
||||
type: 'Key',
|
||||
owner: `${config.url}/users/${user._id}`,
|
||||
publicKeyPem: extractPublic(user.keypair)
|
||||
owner: `${config.url}/users/${user.id}`,
|
||||
publicKeyPem: createPublicKey(key.keyPem)
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { INote } from '../../../models/note';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
|
||||
export default (user: ILocalUser, note: INote, reaction: string) => ({
|
||||
export default (user: ILocalUser, note: Note, reaction: string) => ({
|
||||
type: 'Like',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
object: note.uri ? note.uri : `${config.url}/notes/${note._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object: note.uri ? note.uri : `${config.url}/notes/${note.id}`,
|
||||
_misskey_reaction: reaction
|
||||
});
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { IUser, isRemoteUser } from '../../../models/user';
|
||||
import config from '../../../config';
|
||||
import { User, ILocalUser } from '../../../models/entities/user';
|
||||
import { Users } from '../../../models';
|
||||
|
||||
export default (mention: IUser) => ({
|
||||
export default (mention: User) => ({
|
||||
type: 'Mention',
|
||||
href: isRemoteUser(mention) ? mention.uri : `${config.url}/@${mention.username}`,
|
||||
name: isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${mention.username}`,
|
||||
href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/@${(mention as ILocalUser).username}`,
|
||||
name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
|
||||
});
|
||||
|
|
|
@ -3,29 +3,27 @@ import renderHashtag from './hashtag';
|
|||
import renderMention from './mention';
|
||||
import renderEmoji from './emoji';
|
||||
import config from '../../../config';
|
||||
import DriveFile, { IDriveFile } from '../../../models/drive-file';
|
||||
import Note, { INote } from '../../../models/note';
|
||||
import User from '../../../models/user';
|
||||
import toHtml from '../misc/get-note-html';
|
||||
import Emoji, { IEmoji } from '../../../models/emoji';
|
||||
import { Note, IMentionedRemoteUsers } from '../../../models/entities/note';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { DriveFiles, Notes, Users, Emojis, Polls } from '../../../models';
|
||||
import { In } from 'typeorm';
|
||||
import { Emoji } from '../../../models/entities/emoji';
|
||||
import { Poll } from '../../../models/entities/poll';
|
||||
|
||||
export default async function renderNote(note: INote, dive = true): Promise<any> {
|
||||
const promisedFiles: Promise<IDriveFile[]> = note.fileIds
|
||||
? DriveFile.find({ _id: { $in: note.fileIds } })
|
||||
export default async function renderNote(note: Note, dive = true): Promise<any> {
|
||||
const promisedFiles: Promise<DriveFile[]> = note.fileIds.length > 1
|
||||
? DriveFiles.find({ id: In(note.fileIds) })
|
||||
: Promise.resolve([]);
|
||||
|
||||
let inReplyTo;
|
||||
let inReplyToNote: INote;
|
||||
let inReplyToNote: Note;
|
||||
|
||||
if (note.replyId) {
|
||||
inReplyToNote = await Note.findOne({
|
||||
_id: note.replyId,
|
||||
});
|
||||
inReplyToNote = await Notes.findOne(note.replyId);
|
||||
|
||||
if (inReplyToNote !== null) {
|
||||
const inReplyToUser = await User.findOne({
|
||||
_id: inReplyToNote.userId,
|
||||
});
|
||||
const inReplyToUser = await Users.findOne(inReplyToNote.userId);
|
||||
|
||||
if (inReplyToUser !== null) {
|
||||
if (inReplyToNote.uri) {
|
||||
|
@ -34,7 +32,7 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||
if (dive) {
|
||||
inReplyTo = await renderNote(inReplyToNote, false);
|
||||
} else {
|
||||
inReplyTo = `${config.url}/notes/${inReplyToNote._id}`;
|
||||
inReplyTo = `${config.url}/notes/${inReplyToNote.id}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,24 +44,20 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||
let quote;
|
||||
|
||||
if (note.renoteId) {
|
||||
const renote = await Note.findOne({
|
||||
_id: note.renoteId,
|
||||
});
|
||||
const renote = await Notes.findOne(note.renoteId);
|
||||
|
||||
if (renote) {
|
||||
quote = renote.uri ? renote.uri : `${config.url}/notes/${renote._id}`;
|
||||
quote = renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: note.userId
|
||||
const user = await Users.findOne({
|
||||
id: note.userId
|
||||
});
|
||||
|
||||
const attributedTo = `${config.url}/users/${user._id}`;
|
||||
const attributedTo = `${config.url}/users/${user.id}`;
|
||||
|
||||
const mentions = note.mentionedRemoteUsers && note.mentionedRemoteUsers.length > 0
|
||||
? note.mentionedRemoteUsers.map(x => x.uri)
|
||||
: [];
|
||||
const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
|
||||
|
||||
let to: string[] = [];
|
||||
let cc: string[] = [];
|
||||
|
@ -81,10 +75,8 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||
to = mentions;
|
||||
}
|
||||
|
||||
const mentionedUsers = note.mentions ? await User.find({
|
||||
_id: {
|
||||
$in: note.mentions
|
||||
}
|
||||
const mentionedUsers = note.mentions.length > 0 ? await Users.find({
|
||||
id: In(note.mentions)
|
||||
}) : [];
|
||||
|
||||
const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag));
|
||||
|
@ -93,23 +85,28 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||
const files = await promisedFiles;
|
||||
|
||||
let text = note.text;
|
||||
let poll: Poll;
|
||||
|
||||
if (note.hasPoll) {
|
||||
poll = await Polls.findOne({ noteId: note.id });
|
||||
}
|
||||
|
||||
let question: string;
|
||||
if (note.poll != null) {
|
||||
if (poll) {
|
||||
if (text == null) text = '';
|
||||
const url = `${config.url}/notes/${note._id}`;
|
||||
const url = `${config.url}/notes/${note.id}`;
|
||||
// TODO: i18n
|
||||
text += `\n[リモートで結果を表示](${url})`;
|
||||
|
||||
question = `${config.url}/questions/${note._id}`;
|
||||
question = `${config.url}/questions/${note.id}`;
|
||||
}
|
||||
|
||||
let apText = text;
|
||||
if (apText == null) apText = '';
|
||||
|
||||
// Provides choices as text for AP
|
||||
if (note.poll != null) {
|
||||
const cs = note.poll.choices.map(c => `${c.id}: ${c.text}`);
|
||||
if (poll) {
|
||||
const cs = poll.choices.map((c, i) => `${i}: ${c}`);
|
||||
apText += '\n----------------------------------------\n';
|
||||
apText += cs.join('\n');
|
||||
apText += '\n----------------------------------------\n';
|
||||
|
@ -135,31 +132,25 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||
...apemojis,
|
||||
];
|
||||
|
||||
const {
|
||||
choices = [],
|
||||
expiresAt = null,
|
||||
multiple = false
|
||||
} = note.poll || {};
|
||||
|
||||
const asPoll = note.poll ? {
|
||||
const asPoll = poll ? {
|
||||
type: 'Question',
|
||||
content: toHtml(Object.assign({}, note, {
|
||||
text: text
|
||||
})),
|
||||
_misskey_fallback_content: content,
|
||||
[expiresAt && expiresAt < new Date() ? 'closed' : 'endTime']: expiresAt,
|
||||
[multiple ? 'anyOf' : 'oneOf']: choices.map(({ text, votes }) => ({
|
||||
[poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
|
||||
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
|
||||
type: 'Note',
|
||||
name: text,
|
||||
replies: {
|
||||
type: 'Collection',
|
||||
totalItems: votes
|
||||
totalItems: poll.votes[i]
|
||||
}
|
||||
}))
|
||||
} : {};
|
||||
|
||||
return {
|
||||
id: `${config.url}/notes/${note._id}`,
|
||||
id: `${config.url}/notes/${note.id}`,
|
||||
type: 'Note',
|
||||
attributedTo,
|
||||
summary,
|
||||
|
@ -172,17 +163,17 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
|||
cc,
|
||||
inReplyTo,
|
||||
attachment: files.map(renderDocument),
|
||||
sensitive: files.some(file => file.metadata.isSensitive),
|
||||
sensitive: files.some(file => file.isSensitive),
|
||||
tag,
|
||||
...asPoll
|
||||
};
|
||||
}
|
||||
|
||||
export async function getEmojis(names: string[]): Promise<IEmoji[]> {
|
||||
if (names == null || names.length < 1) return [];
|
||||
export async function getEmojis(names: string[]): Promise<Emoji[]> {
|
||||
if (names == null || names.length === 0) return [];
|
||||
|
||||
const emojis = await Promise.all(
|
||||
names.map(name => Emoji.findOne({
|
||||
names.map(name => Emojis.findOne({
|
||||
name,
|
||||
host: null
|
||||
}))
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import renderImage from './image';
|
||||
import renderKey from './key';
|
||||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
import { toHtml } from '../../../mfm/toHtml';
|
||||
import { parse } from '../../../mfm/parse';
|
||||
import DriveFile from '../../../models/drive-file';
|
||||
import { getEmojis } from './note';
|
||||
import renderEmoji from './emoji';
|
||||
import { IIdentifier } from '../models/identifier';
|
||||
import renderHashtag from './hashtag';
|
||||
import { DriveFiles, UserServiceLinkings, UserKeypairs } from '../../../models';
|
||||
|
||||
export default async (user: ILocalUser) => {
|
||||
const id = `${config.url}/users/${user._id}`;
|
||||
export async function renderPerson(user: ILocalUser) {
|
||||
const id = `${config.url}/users/${user.id}`;
|
||||
|
||||
const [avatar, banner] = await Promise.all([
|
||||
DriveFile.findOne({ _id: user.avatarId }),
|
||||
DriveFile.findOne({ _id: user.bannerId })
|
||||
const [avatar, banner, links] = await Promise.all([
|
||||
DriveFiles.findOne(user.avatarId),
|
||||
DriveFiles.findOne(user.bannerId),
|
||||
UserServiceLinkings.findOne({ userId: user.id })
|
||||
]);
|
||||
|
||||
const attachment: {
|
||||
|
@ -26,41 +27,41 @@ export default async (user: ILocalUser) => {
|
|||
identifier?: IIdentifier
|
||||
}[] = [];
|
||||
|
||||
if (user.twitter) {
|
||||
if (links.twitter) {
|
||||
attachment.push({
|
||||
type: 'PropertyValue',
|
||||
name: 'Twitter',
|
||||
value: `<a href="https://twitter.com/intent/user?user_id=${user.twitter.userId}" rel="me nofollow noopener" target="_blank"><span>@${user.twitter.screenName}</span></a>`,
|
||||
value: `<a href="https://twitter.com/intent/user?user_id=${links.twitterUserId}" rel="me nofollow noopener" target="_blank"><span>@${links.twitterScreenName}</span></a>`,
|
||||
identifier: {
|
||||
type: 'PropertyValue',
|
||||
name: 'misskey:authentication:twitter',
|
||||
value: `${user.twitter.userId}@${user.twitter.screenName}`
|
||||
value: `${links.twitterUserId}@${links.twitterScreenName}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (user.github) {
|
||||
if (links.github) {
|
||||
attachment.push({
|
||||
type: 'PropertyValue',
|
||||
name: 'GitHub',
|
||||
value: `<a href="https://github.com/${user.github.login}" rel="me nofollow noopener" target="_blank"><span>@${user.github.login}</span></a>`,
|
||||
value: `<a href="https://github.com/${links.githubLogin}" rel="me nofollow noopener" target="_blank"><span>@${links.githubLogin}</span></a>`,
|
||||
identifier: {
|
||||
type: 'PropertyValue',
|
||||
name: 'misskey:authentication:github',
|
||||
value: `${user.github.id}@${user.github.login}`
|
||||
value: `${links.githubId}@${links.githubLogin}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (user.discord) {
|
||||
if (links.discord) {
|
||||
attachment.push({
|
||||
type: 'PropertyValue',
|
||||
name: 'Discord',
|
||||
value: `<a href="https://discordapp.com/users/${user.discord.id}" rel="me nofollow noopener" target="_blank"><span>${user.discord.username}#${user.discord.discriminator}</span></a>`,
|
||||
value: `<a href="https://discordapp.com/users/${links.discordId}" rel="me nofollow noopener" target="_blank"><span>${links.discordUsername}#${links.discordDiscriminator}</span></a>`,
|
||||
identifier: {
|
||||
type: 'PropertyValue',
|
||||
name: 'misskey:authentication:discord',
|
||||
value: `${user.discord.id}@${user.discord.username}#${user.discord.discriminator}`
|
||||
value: `${links.discordId}@${links.discordUsername}#${links.discordDiscriminator}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -75,6 +76,10 @@ export default async (user: ILocalUser) => {
|
|||
...hashtagTags,
|
||||
];
|
||||
|
||||
const keypair = await UserKeypairs.findOne({
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
return {
|
||||
type: user.isBot ? 'Service' : 'Person',
|
||||
id,
|
||||
|
@ -93,8 +98,8 @@ export default async (user: ILocalUser) => {
|
|||
image: user.bannerId && renderImage(banner),
|
||||
tag,
|
||||
manuallyApprovesFollowers: user.isLocked,
|
||||
publicKey: renderKey(user),
|
||||
publicKey: renderKey(user, keypair),
|
||||
isCat: user.isCat,
|
||||
attachment: attachment.length ? attachment : undefined
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { INote } from '../../../models/note';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { Poll } from '../../../models/entities/poll';
|
||||
|
||||
export default async function renderQuestion(user: ILocalUser, note: INote) {
|
||||
export default async function renderQuestion(user: ILocalUser, note: Note, poll: Poll) {
|
||||
const question = {
|
||||
type: 'Question',
|
||||
id: `${config.url}/questions/${note._id}`,
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
id: `${config.url}/questions/${note.id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
content: note.text || '',
|
||||
[note.poll.multiple ? 'anyOf' : 'oneOf']: note.poll.choices.map(c => ({
|
||||
name: c.text,
|
||||
_misskey_votes: c.votes,
|
||||
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
|
||||
name: text,
|
||||
_misskey_votes: poll.votes[i],
|
||||
replies: {
|
||||
type: 'Collection',
|
||||
totalItems: c.votes
|
||||
totalItems: poll.votes[i]
|
||||
}
|
||||
}))
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
|
||||
export default (object: any, user: ILocalUser) => ({
|
||||
type: 'Reject',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
|
||||
export default (user: ILocalUser, target: any, object: any) => ({
|
||||
type: 'Remove',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
target,
|
||||
object
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser, IUser } from '../../../models/user';
|
||||
import { ILocalUser, User } from '../../../models/entities/user';
|
||||
|
||||
export default (object: any, user: ILocalUser | IUser) => ({
|
||||
export default (object: any, user: ILocalUser | User) => ({
|
||||
type: 'Undo',
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
|
||||
export default (object: any, user: ILocalUser) => {
|
||||
const activity = {
|
||||
id: `${config.url}/users/${user._id}#updates/${new Date().getTime()}`,
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
id: `${config.url}/users/${user.id}#updates/${new Date().getTime()}`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
type: 'Update',
|
||||
to: [ 'https://www.w3.org/ns/activitystreams#Public' ],
|
||||
object
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import config from '../../../config';
|
||||
import { INote } from '../../../models/note';
|
||||
import { IRemoteUser, ILocalUser } from '../../../models/user';
|
||||
import { IPollVote } from '../../../models/poll-vote';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { IRemoteUser, ILocalUser } from '../../../models/entities/user';
|
||||
import { PollVote } from '../../../models/entities/poll-vote';
|
||||
import { Poll } from '../../../models/entities/poll';
|
||||
|
||||
export default async function renderVote(user: ILocalUser, vote: IPollVote, pollNote: INote, pollOwner: IRemoteUser): Promise<any> {
|
||||
export default async function renderVote(user: ILocalUser, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser): Promise<any> {
|
||||
return {
|
||||
id: `${config.url}/users/${user._id}#votes/${vote._id}/activity`,
|
||||
actor: `${config.url}/users/${user._id}`,
|
||||
id: `${config.url}/users/${user.id}#votes/${vote.id}/activity`,
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
type: 'Create',
|
||||
to: [pollOwner.uri],
|
||||
published: new Date().toISOString(),
|
||||
object: {
|
||||
id: `${config.url}/users/${user._id}#votes/${vote._id}`,
|
||||
id: `${config.url}/users/${user.id}#votes/${vote.id}`,
|
||||
type: 'Note',
|
||||
attributedTo: `${config.url}/users/${user._id}`,
|
||||
attributedTo: `${config.url}/users/${user.id}`,
|
||||
to: [pollOwner.uri],
|
||||
inReplyTo: pollNote.uri,
|
||||
name: pollNote.poll.choices.find(x => x.id === vote.choice).text
|
||||
inReplyTo: note.uri,
|
||||
name: poll.choices[vote.choice]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@ import * as promiseAny from 'promise-any';
|
|||
import { toUnicode } from 'punycode';
|
||||
|
||||
import config from '../../config';
|
||||
import { ILocalUser } from '../../models/user';
|
||||
import { ILocalUser } from '../../models/entities/user';
|
||||
import { publishApLogStream } from '../../services/stream';
|
||||
import { apLogger } from './logger';
|
||||
import Instance from '../../models/instance';
|
||||
import { UserKeypairs } from '../../models';
|
||||
import fetchMeta from '../../misc/fetch-meta';
|
||||
|
||||
export const logger = apLogger.createSubLogger('deliver');
|
||||
|
||||
|
@ -23,8 +24,8 @@ export default async (user: ILocalUser, url: string, object: any) => {
|
|||
|
||||
// ブロックしてたら中断
|
||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||
const instance = await Instance.findOne({ host: toUnicode(host) });
|
||||
if (instance && instance.isBlocked) return;
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(toUnicode(host))) return;
|
||||
|
||||
const data = JSON.stringify(object);
|
||||
|
||||
|
@ -35,6 +36,10 @@ export default async (user: ILocalUser, url: string, object: any) => {
|
|||
const addr = await resolveAddr(hostname);
|
||||
if (!addr) return;
|
||||
|
||||
const keypair = await UserKeypairs.findOne({
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
const _ = new Promise((resolve, reject) => {
|
||||
const req = request({
|
||||
protocol,
|
||||
|
@ -62,8 +67,8 @@ export default async (user: ILocalUser, url: string, object: any) => {
|
|||
|
||||
sign(req, {
|
||||
authorizationHeaderName: 'Signature',
|
||||
key: user.keypair,
|
||||
keyId: `${config.url}/users/${user._id}/publickey`,
|
||||
key: keypair.keyPem,
|
||||
keyId: `${config.url}/users/${user.id}/publickey`,
|
||||
headers: ['date', 'host', 'digest']
|
||||
});
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ export default class Resolver {
|
|||
json: true
|
||||
});
|
||||
|
||||
if (object === null || (
|
||||
if (object == null || (
|
||||
Array.isArray(object['@context']) ?
|
||||
!object['@context'].includes('https://www.w3.org/ns/activitystreams') :
|
||||
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import { toUnicode, toASCII } from 'punycode';
|
||||
import User, { IUser, IRemoteUser } from '../models/user';
|
||||
import webFinger from './webfinger';
|
||||
import config from '../config';
|
||||
import { createPerson, updatePerson } from './activitypub/models/person';
|
||||
import { URL } from 'url';
|
||||
import { remoteLogger } from './logger';
|
||||
import chalk from 'chalk';
|
||||
import { User, IRemoteUser } from '../models/entities/user';
|
||||
import { Users } from '../models';
|
||||
|
||||
const logger = remoteLogger.createSubLogger('resolve-user');
|
||||
|
||||
export default async (username: string, _host: string, option?: any, resync?: boolean): Promise<IUser> => {
|
||||
export default async (username: string, _host: string, option?: any, resync = false): Promise<User> => {
|
||||
const usernameLower = username.toLowerCase();
|
||||
|
||||
if (_host == null) {
|
||||
logger.info(`return local user: ${usernameLower}`);
|
||||
return await User.findOne({ usernameLower, host: null });
|
||||
return await Users.findOne({ usernameLower, host: null });
|
||||
}
|
||||
|
||||
const configHostAscii = toASCII(config.host).toLowerCase();
|
||||
|
@ -25,14 +26,14 @@ export default async (username: string, _host: string, option?: any, resync?: bo
|
|||
|
||||
if (configHost == host) {
|
||||
logger.info(`return local user: ${usernameLower}`);
|
||||
return await User.findOne({ usernameLower, host: null });
|
||||
return await Users.findOne({ usernameLower, host: null });
|
||||
}
|
||||
|
||||
const user = await User.findOne({ usernameLower, host }, option);
|
||||
const user = await Users.findOne({ usernameLower, host }, option);
|
||||
|
||||
const acctLower = `${usernameLower}@${hostAscii}`;
|
||||
|
||||
if (user === null) {
|
||||
if (user == null) {
|
||||
const self = await resolveSelf(acctLower);
|
||||
|
||||
logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
|
||||
|
@ -54,13 +55,11 @@ export default async (username: string, _host: string, option?: any, resync?: bo
|
|||
throw new Error(`Invalied uri`);
|
||||
}
|
||||
|
||||
await User.update({
|
||||
await Users.update({
|
||||
usernameLower,
|
||||
host: host
|
||||
}, {
|
||||
$set: {
|
||||
uri: self.href
|
||||
}
|
||||
uri: self.href
|
||||
});
|
||||
} else {
|
||||
logger.info(`uri is fine: ${acctLower}`);
|
||||
|
@ -69,7 +68,7 @@ export default async (username: string, _host: string, option?: any, resync?: bo
|
|||
await updatePerson(self.href);
|
||||
|
||||
logger.info(`return resynced remote user: ${acctLower}`);
|
||||
return await User.findOne({ uri: self.href });
|
||||
return await Users.findOne({ uri: self.href });
|
||||
}
|
||||
|
||||
logger.info(`return existing remote user: ${acctLower}`);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue