summaryrefslogtreecommitdiff
path: root/src/server/api/endpoints
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/api/endpoints')
-rw-r--r--src/server/api/endpoints/admin/drive/files.ts81
-rw-r--r--src/server/api/endpoints/admin/reset-password.ts57
-rw-r--r--src/server/api/endpoints/admin/show-user.ts40
-rw-r--r--src/server/api/endpoints/admin/update-meta.ts167
-rw-r--r--src/server/api/endpoints/aggregation/hashtags.ts9
-rw-r--r--src/server/api/endpoints/ap/show.ts13
-rw-r--r--src/server/api/endpoints/charts/drive.ts2
-rw-r--r--src/server/api/endpoints/charts/federation.ts2
-rw-r--r--src/server/api/endpoints/charts/hashtag.ts2
-rw-r--r--src/server/api/endpoints/charts/network.ts2
-rw-r--r--src/server/api/endpoints/charts/notes.ts2
-rw-r--r--src/server/api/endpoints/charts/user/drive.ts2
-rw-r--r--src/server/api/endpoints/charts/user/following.ts2
-rw-r--r--src/server/api/endpoints/charts/user/notes.ts2
-rw-r--r--src/server/api/endpoints/charts/user/reactions.ts2
-rw-r--r--src/server/api/endpoints/charts/users.ts2
-rw-r--r--src/server/api/endpoints/drive/files.ts2
-rw-r--r--src/server/api/endpoints/drive/files/check_existence.ts2
-rw-r--r--src/server/api/endpoints/drive/files/create.ts2
-rw-r--r--src/server/api/endpoints/drive/files/delete.ts7
-rw-r--r--src/server/api/endpoints/drive/files/find.ts2
-rw-r--r--src/server/api/endpoints/drive/files/show.ts3
-rw-r--r--src/server/api/endpoints/drive/files/update.ts13
-rw-r--r--src/server/api/endpoints/drive/files/upload_from_url.ts4
-rw-r--r--src/server/api/endpoints/drive/stream.ts2
-rw-r--r--src/server/api/endpoints/games/reversi/games/show.ts3
-rw-r--r--src/server/api/endpoints/games/reversi/match.ts2
-rw-r--r--src/server/api/endpoints/hashtags/trend.ts7
-rw-r--r--src/server/api/endpoints/i.ts9
-rw-r--r--src/server/api/endpoints/i/update.ts29
-rw-r--r--src/server/api/endpoints/i/update_email.ts100
-rw-r--r--src/server/api/endpoints/i/update_widget.ts10
-rw-r--r--src/server/api/endpoints/meta.ts24
-rw-r--r--src/server/api/endpoints/notes.ts5
-rw-r--r--src/server/api/endpoints/notes/create.ts31
-rw-r--r--src/server/api/endpoints/notes/delete.ts7
-rw-r--r--src/server/api/endpoints/notes/mentions.ts4
-rw-r--r--src/server/api/endpoints/notes/polls/recommendation.ts1
-rw-r--r--src/server/api/endpoints/notes/polls/vote.ts4
-rw-r--r--src/server/api/endpoints/notes/replies.ts17
-rw-r--r--src/server/api/endpoints/sw/register.ts8
-rw-r--r--src/server/api/endpoints/users.ts45
-rw-r--r--src/server/api/endpoints/users/get_frequently_replied_users.ts11
-rw-r--r--src/server/api/endpoints/users/lists/pull.ts64
-rw-r--r--src/server/api/endpoints/users/recommendation.ts54
-rw-r--r--src/server/api/endpoints/users/search.ts91
-rw-r--r--src/server/api/endpoints/users/show.ts2
47 files changed, 770 insertions, 182 deletions
diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts
new file mode 100644
index 0000000000..dfd8eee91e
--- /dev/null
+++ b/src/server/api/endpoints/admin/drive/files.ts
@@ -0,0 +1,81 @@
+import $ from 'cafy';
+import File, { packMany } from '../../../../../models/drive-file';
+import define from '../../../define';
+
+export const meta = {
+ requireCredential: false,
+ requireModerator: true,
+
+ params: {
+ limit: {
+ validator: $.num.optional.range(1, 100),
+ default: 10
+ },
+
+ offset: {
+ validator: $.num.optional.min(0),
+ default: 0
+ },
+
+ sort: {
+ validator: $.str.optional.or([
+ '+createdAt',
+ '-createdAt',
+ '+size',
+ '-size',
+ ]),
+ },
+
+ origin: {
+ validator: $.str.optional.or([
+ 'combined',
+ 'local',
+ 'remote',
+ ]),
+ default: 'local'
+ }
+ }
+};
+
+export default define(meta, (ps, me) => new Promise(async (res, rej) => {
+ let _sort;
+ if (ps.sort) {
+ if (ps.sort == '+createdAt') {
+ _sort = {
+ uploadDate: -1
+ };
+ } else if (ps.sort == '-createdAt') {
+ _sort = {
+ uploadDate: 1
+ };
+ } else if (ps.sort == '+size') {
+ _sort = {
+ length: -1
+ };
+ } else if (ps.sort == '-size') {
+ _sort = {
+ length: 1
+ };
+ }
+ } else {
+ _sort = {
+ _id: -1
+ };
+ }
+
+ const q = {
+ 'metadata.deletedAt': { $exists: false },
+ } as any;
+
+ if (ps.origin == 'local') q['metadata._user.host'] = null;
+ if (ps.origin == 'remote') q['metadata._user.host'] = { $ne: null };
+
+ const files = await File
+ .find(q, {
+ limit: ps.limit,
+ sort: _sort,
+ skip: ps.offset
+ });
+
+ res(await packMany(files, { detail: true, withUser: true }));
+}));
diff --git a/src/server/api/endpoints/admin/reset-password.ts b/src/server/api/endpoints/admin/reset-password.ts
new file mode 100644
index 0000000000..c072c12e0d
--- /dev/null
+++ b/src/server/api/endpoints/admin/reset-password.ts
@@ -0,0 +1,57 @@
+import $ from 'cafy';
+import ID, { transform } from '../../../../misc/cafy-id';
+import define from '../../define';
+import User from '../../../../models/user';
+import * as bcrypt from 'bcryptjs';
+import rndstr from 'rndstr';
+
+export const meta = {
+ desc: {
+ 'ja-JP': '指定したユーザーのパスワードをリセットします。',
+ },
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ userId: {
+ validator: $.type(ID),
+ transform: transform,
+ desc: {
+ 'ja-JP': '対象のユーザーID',
+ 'en-US': 'The user ID which you want to suspend'
+ }
+ },
+ }
+};
+
+export default define(meta, (ps) => new Promise(async (res, rej) => {
+ const user = await User.findOne({
+ _id: ps.userId
+ });
+
+ if (user == null) {
+ return rej('user not found');
+ }
+
+ if (user.isAdmin) {
+ return rej('cannot reset password of admin');
+ }
+
+ const passwd = rndstr('a-zA-Z0-9', 8);
+
+ // Generate hash of password
+ const hash = bcrypt.hashSync(passwd);
+
+ await User.findOneAndUpdate({
+ _id: user._id
+ }, {
+ $set: {
+ password: hash
+ }
+ });
+
+ res({
+ password: passwd
+ });
+}));
diff --git a/src/server/api/endpoints/admin/show-user.ts b/src/server/api/endpoints/admin/show-user.ts
new file mode 100644
index 0000000000..490b685352
--- /dev/null
+++ b/src/server/api/endpoints/admin/show-user.ts
@@ -0,0 +1,40 @@
+import $ from 'cafy';
+import ID, { transform } from '../../../../misc/cafy-id';
+import define from '../../define';
+import User from '../../../../models/user';
+
+export const meta = {
+ desc: {
+ 'ja-JP': '指定したユーザーの情報を取得します。',
+ },
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ userId: {
+ validator: $.type(ID),
+ transform: transform,
+ desc: {
+ 'ja-JP': '対象のユーザーID',
+ 'en-US': 'The user ID which you want to suspend'
+ }
+ },
+ }
+};
+
+export default define(meta, (ps, me) => new Promise(async (res, rej) => {
+ const user = await User.findOne({
+ _id: ps.userId
+ });
+
+ if (user == null) {
+ return rej('user not found');
+ }
+
+ if (me.isModerator && user.isAdmin) {
+ return rej('cannot show info of admin');
+ }
+
+ res(user);
+}));
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index bbae212bd7..6ceb2a98dc 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -46,6 +46,13 @@ export const meta = {
}
},
+ errorImageUrl: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'インスタンスのエラー画像URL'
+ }
+ },
+
name: {
validator: $.str.optional.nullable,
desc: {
@@ -139,6 +146,13 @@ export const meta = {
}
},
+ summalyProxy: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'summalyプロキシURL'
+ }
+ },
+
enableTwitterIntegration: {
validator: $.bool.optional,
desc: {
@@ -200,7 +214,98 @@ export const meta = {
desc: {
'ja-JP': 'DiscordアプリのClient Secret'
}
- }
+ },
+
+ enableExternalUserRecommendation: {
+ validator: $.bool.optional,
+ desc: {
+ 'ja-JP': '外部ユーザーレコメンデーションを有効にする'
+ }
+ },
+
+ externalUserRecommendationEngine: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': '外部ユーザーレコメンデーションのサードパーティエンジン'
+ }
+ },
+
+ externalUserRecommendationTimeout: {
+ validator: $.num.optional.nullable.min(0),
+ desc: {
+ 'ja-JP': '外部ユーザーレコメンデーションのタイムアウト (ミリ秒)'
+ }
+ },
+
+ enableEmail: {
+ validator: $.bool.optional,
+ desc: {
+ 'ja-JP': 'メール配信を有効にするか否か'
+ }
+ },
+
+ email: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'メール配信する際に利用するメールアドレス'
+ }
+ },
+
+ smtpSecure: {
+ validator: $.bool.optional,
+ desc: {
+ 'ja-JP': 'SMTPサーバがSSLを使用しているか否か'
+ }
+ },
+
+ smtpHost: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'SMTPサーバのホスト'
+ }
+ },
+
+ smtpPort: {
+ validator: $.num.optional.nullable,
+ desc: {
+ 'ja-JP': 'SMTPサーバのポート'
+ }
+ },
+
+ smtpUser: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'SMTPサーバのユーザー名'
+ }
+ },
+
+ smtpPass: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'SMTPサーバのパスワード'
+ }
+ },
+
+ enableServiceWorker: {
+ validator: $.bool.optional,
+ desc: {
+ 'ja-JP': 'ServiceWorkerを有効にするか否か'
+ }
+ },
+
+ swPublicKey: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'ServiceWorkerのVAPIDキーペアの公開鍵'
+ }
+ },
+
+ swPrivateKey: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'ServiceWorkerのVAPIDキーペアの秘密鍵'
+ }
+ },
}
};
@@ -279,6 +384,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
set.langs = ps.langs;
}
+ if (ps.summalyProxy !== undefined) {
+ set.summalyProxy = ps.summalyProxy;
+ }
+
if (ps.enableTwitterIntegration !== undefined) {
set.enableTwitterIntegration = ps.enableTwitterIntegration;
}
@@ -315,6 +424,62 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
set.discordClientSecret = ps.discordClientSecret;
}
+ if (ps.enableExternalUserRecommendation !== undefined) {
+ set.enableExternalUserRecommendation = ps.enableExternalUserRecommendation;
+ }
+
+ if (ps.externalUserRecommendationEngine !== undefined) {
+ set.externalUserRecommendationEngine = ps.externalUserRecommendationEngine;
+ }
+
+ if (ps.externalUserRecommendationTimeout !== undefined) {
+ set.externalUserRecommendationTimeout = ps.externalUserRecommendationTimeout;
+ }
+
+ if (ps.enableEmail !== undefined) {
+ set.enableEmail = ps.enableEmail;
+ }
+
+ if (ps.email !== undefined) {
+ set.email = ps.email;
+ }
+
+ if (ps.smtpSecure !== undefined) {
+ set.smtpSecure = ps.smtpSecure;
+ }
+
+ if (ps.smtpHost !== undefined) {
+ set.smtpHost = ps.smtpHost;
+ }
+
+ if (ps.smtpPort !== undefined) {
+ set.smtpPort = ps.smtpPort;
+ }
+
+ if (ps.smtpUser !== undefined) {
+ set.smtpUser = ps.smtpUser;
+ }
+
+ if (ps.smtpPass !== undefined) {
+ set.smtpPass = ps.smtpPass;
+ }
+
+ if (ps.errorImageUrl !== undefined) {
+ set.errorImageUrl = ps.errorImageUrl;
+ }
+
+ if (ps.enableServiceWorker !== undefined) {
+ set.enableServiceWorker = ps.enableServiceWorker;
+ }
+
+ if (ps.swPublicKey !== undefined) {
+ set.swPublicKey = ps.swPublicKey;
+ }
+
+ if (ps.swPrivateKey !== undefined) {
+ set.swPrivateKey = ps.swPrivateKey;
+ }
+
await Meta.update({}, {
$set: set
}, { upsert: true });
diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts
index f8fc7162f5..7577711b5f 100644
--- a/src/server/api/endpoints/aggregation/hashtags.ts
+++ b/src/server/api/endpoints/aggregation/hashtags.ts
@@ -47,10 +47,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}> = [];
// カウント
- data.map(x => x._id).forEach(x => {
- // ブラックリストに登録されているタグなら弾く
- if (hidedTags.includes(x.tag)) return;
-
+ for (const x of data.map(x => x._id).filter(x => !hidedTags.includes(x.tag))) {
const i = tags.findIndex(tag => tag.name == x.tag);
if (i != -1) {
tags[i].count++;
@@ -60,10 +57,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
count: 1
});
}
- });
+ }
// タグを人気順に並べ替え
- tags = tags.sort((a, b) => b.count - a.count);
+ tags.sort((a, b) => b.count - a.count);
tags = tags.slice(0, 30);
diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts
index 778d1e5099..3b4021e0a7 100644
--- a/src/server/api/endpoints/ap/show.ts
+++ b/src/server/api/endpoints/ap/show.ts
@@ -25,11 +25,10 @@ export const meta = {
},
};
-export default define(meta, (ps) => new Promise(async (res, rej) => {
- const object = await fetchAny(ps.uri);
- if (object == null) return rej('object not found');
-
- res(object);
+export default define(meta, (ps) => new Promise((res, rej) => {
+ fetchAny(ps.uri)
+ .then(object => object != null ? res(object) : rej('object not found'))
+ .catch(e => rej(e));
}));
/***
@@ -80,7 +79,7 @@ async function fetchAny(uri: string) {
const user = await createPerson(object.id);
return {
type: 'User',
- object: user
+ object: await packUser(user, null, { detail: true })
};
}
@@ -88,7 +87,7 @@ async function fetchAny(uri: string) {
const note = await createNote(object.id);
return {
type: 'Note',
- object: note
+ object: await packNote(note, null, { detail: true })
};
}
diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts
index 25ede16102..2fd0139e8e 100644
--- a/src/server/api/endpoints/charts/drive.ts
+++ b/src/server/api/endpoints/charts/drive.ts
@@ -3,6 +3,8 @@ import define from '../../define';
import driveChart from '../../../../chart/drive';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ドライブのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts
index fb5fa4426e..f7d12e5c33 100644
--- a/src/server/api/endpoints/charts/federation.ts
+++ b/src/server/api/endpoints/charts/federation.ts
@@ -3,6 +3,8 @@ import define from '../../define';
import federationChart from '../../../../chart/federation';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'フェデレーションのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts
index b2140068a5..3df2dd8500 100644
--- a/src/server/api/endpoints/charts/hashtag.ts
+++ b/src/server/api/endpoints/charts/hashtag.ts
@@ -3,6 +3,8 @@ import define from '../../define';
import hashtagChart from '../../../../chart/hashtag';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ハッシュタグごとのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts
index 6d7468f950..af5b799a80 100644
--- a/src/server/api/endpoints/charts/network.ts
+++ b/src/server/api/endpoints/charts/network.ts
@@ -3,6 +3,8 @@ import define from '../../define';
import networkChart from '../../../../chart/network';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ネットワークのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts
index c9889d1840..906cef1fa9 100644
--- a/src/server/api/endpoints/charts/notes.ts
+++ b/src/server/api/endpoints/charts/notes.ts
@@ -3,6 +3,8 @@ import define from '../../define';
import notesChart from '../../../../chart/notes';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': '投稿のチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts
index 7e806c151b..1ef133c6bf 100644
--- a/src/server/api/endpoints/charts/user/drive.ts
+++ b/src/server/api/endpoints/charts/user/drive.ts
@@ -4,6 +4,8 @@ import perUserDriveChart from '../../../../../chart/per-user-drive';
import ID, { transform } from '../../../../../misc/cafy-id';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ユーザーごとのドライブのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts
index ee20b3c39c..c8a64455e5 100644
--- a/src/server/api/endpoints/charts/user/following.ts
+++ b/src/server/api/endpoints/charts/user/following.ts
@@ -4,6 +4,8 @@ import perUserFollowingChart from '../../../../../chart/per-user-following';
import ID, { transform } from '../../../../../misc/cafy-id';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ユーザーごとのフォロー/フォロワーのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts
index 1b6a312a01..f8a3c726ef 100644
--- a/src/server/api/endpoints/charts/user/notes.ts
+++ b/src/server/api/endpoints/charts/user/notes.ts
@@ -4,6 +4,8 @@ import perUserNotesChart from '../../../../../chart/per-user-notes';
import ID, { transform } from '../../../../../misc/cafy-id';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ユーザーごとの投稿のチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts
index 78bd22cc2f..f24c84593c 100644
--- a/src/server/api/endpoints/charts/user/reactions.ts
+++ b/src/server/api/endpoints/charts/user/reactions.ts
@@ -4,6 +4,8 @@ import perUserReactionsChart from '../../../../../chart/per-user-reactions';
import ID, { transform } from '../../../../../misc/cafy-id';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ユーザーごとの被リアクション数のチャートを取得します。'
},
diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts
index 733b7b8178..00c2871148 100644
--- a/src/server/api/endpoints/charts/users.ts
+++ b/src/server/api/endpoints/charts/users.ts
@@ -3,6 +3,8 @@ import define from '../../define';
import usersChart from '../../../../chart/users';
export const meta = {
+ stability: 'stable',
+
desc: {
'ja-JP': 'ユーザーのチャートを取得します。'
},
diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts
index 27f101562d..20955e0e4e 100644
--- a/src/server/api/endpoints/drive/files.ts
+++ b/src/server/api/endpoints/drive/files.ts
@@ -77,5 +77,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
sort: sort
});
- res(await packMany(files));
+ res(await packMany(files, { detail: false, self: true }));
}));
diff --git a/src/server/api/endpoints/drive/files/check_existence.ts b/src/server/api/endpoints/drive/files/check_existence.ts
index d3ba4b386d..6e986d4170 100644
--- a/src/server/api/endpoints/drive/files/check_existence.ts
+++ b/src/server/api/endpoints/drive/files/check_existence.ts
@@ -32,6 +32,6 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
if (file === null) {
res({ file: null });
} else {
- res({ file: await pack(file) });
+ res({ file: await pack(file, { self: true }) });
}
}));
diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts
index 53c62dd868..0660627f08 100644
--- a/src/server/api/endpoints/drive/files/create.ts
+++ b/src/server/api/endpoints/drive/files/create.ts
@@ -74,7 +74,7 @@ export default define(meta, (ps, user, app, file, cleanup) => new Promise(async
cleanup();
- res(pack(driveFile));
+ res(pack(driveFile, { self: true }));
} catch (e) {
console.error(e);
diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts
index 7367c8fbb6..0c2799c708 100644
--- a/src/server/api/endpoints/drive/files/delete.ts
+++ b/src/server/api/endpoints/drive/files/delete.ts
@@ -32,14 +32,17 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Fetch file
const file = await DriveFile
.findOne({
- _id: ps.fileId,
- 'metadata.userId': user._id
+ _id: ps.fileId
});
if (file === null) {
return rej('file-not-found');
}
+ if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) {
+ return rej('access denied');
+ }
+
// Delete
await del(file);
diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts
index 8bc392fefe..25135e83a2 100644
--- a/src/server/api/endpoints/drive/files/find.ts
+++ b/src/server/api/endpoints/drive/files/find.ts
@@ -31,5 +31,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
'metadata.folderId': ps.folderId
});
- res(await Promise.all(files.map(file => pack(file))));
+ res(await Promise.all(files.map(file => pack(file, { self: true }))));
}));
diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts
index 450a97065b..95c3323fbb 100644
--- a/src/server/api/endpoints/drive/files/show.ts
+++ b/src/server/api/endpoints/drive/files/show.ts
@@ -41,7 +41,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Serialize
const _file = await pack(file, {
- detail: true
+ detail: true,
+ self: true
});
res(_file);
diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts
index 4efec3dc2a..a17ff2bf34 100644
--- a/src/server/api/endpoints/drive/files/update.ts
+++ b/src/server/api/endpoints/drive/files/update.ts
@@ -57,14 +57,17 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Fetch file
const file = await DriveFile
.findOne({
- _id: ps.fileId,
- 'metadata.userId': user._id
+ _id: ps.fileId
});
if (file === null) {
return rej('file-not-found');
}
+ if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) {
+ return rej('access denied');
+ }
+
if (ps.name) file.filename = ps.name;
if (ps.isSensitive !== undefined) file.metadata.isSensitive = ps.isSensitive;
@@ -100,18 +103,18 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
Note.find({
'_files._id': file._id
}).then(notes => {
- notes.forEach(note => {
+ for (const note of notes) {
note._files[note._files.findIndex(f => f._id.equals(file._id))] = file;
Note.update({ _id: note._id }, {
$set: {
_files: note._files
}
});
- });
+ }
});
// Serialize
- const fileObj = await pack(file);
+ const fileObj = await pack(file, { self: true });
// Response
res(fileObj);
diff --git a/src/server/api/endpoints/drive/files/upload_from_url.ts b/src/server/api/endpoints/drive/files/upload_from_url.ts
index a8faab1d73..fc386e1638 100644
--- a/src/server/api/endpoints/drive/files/upload_from_url.ts
+++ b/src/server/api/endpoints/drive/files/upload_from_url.ts
@@ -26,7 +26,7 @@ export const meta = {
folderId: {
validator: $.type(ID).optional.nullable,
- default: null as any as any,
+ default: null as any,
transform: transform
},
@@ -50,5 +50,5 @@ export const meta = {
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
- res(pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force)));
+ res(pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }));
}));
diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts
index 804ecf50d9..c8342c66b5 100644
--- a/src/server/api/endpoints/drive/stream.ts
+++ b/src/server/api/endpoints/drive/stream.ts
@@ -65,5 +65,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
sort: sort
});
- res(await packMany(files));
+ res(await packMany(files, { self: true }));
}));
diff --git a/src/server/api/endpoints/games/reversi/games/show.ts b/src/server/api/endpoints/games/reversi/games/show.ts
index c747202354..0882456102 100644
--- a/src/server/api/endpoints/games/reversi/games/show.ts
+++ b/src/server/api/endpoints/games/reversi/games/show.ts
@@ -25,9 +25,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
loopedBoard: game.settings.loopedBoard
});
- game.logs.forEach(log => {
+ for (const log of game.logs)
o.put(log.color, log.pos);
- });
const packed = await pack(game, user);
diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts
index 43b6fc8eed..a3cc523a8c 100644
--- a/src/server/api/endpoints/games/reversi/match.ts
+++ b/src/server/api/endpoints/games/reversi/match.ts
@@ -66,7 +66,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
if (other == 0) {
- publishMainStream(user._id, 'reversi_no_invites');
+ publishMainStream(user._id, 'reversiNoInvites');
}
} else {
// Fetch child
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index ed4c8e337f..a26dbf0941 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -58,10 +58,7 @@ export default define(meta, () => new Promise(async (res, rej) => {
}> = [];
// カウント
- data.map(x => x._id).forEach(x => {
- // ブラックリストに登録されているタグなら弾く
- if (hidedTags.includes(x.tag)) return;
-
+ for (const x of data.map(x => x._id).filter(x => !hidedTags.includes(x.tag))) {
const i = tags.findIndex(tag => tag.name == x.tag);
if (i != -1) {
tags[i].count++;
@@ -71,7 +68,7 @@ export default define(meta, () => new Promise(async (res, rej) => {
count: 1
});
}
- });
+ }
// 最低要求投稿者数を下回るならカットする
const limitedTags = tags.filter(tag => tag.count >= requiredUsers);
diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts
index aea47ad795..ab85b17d09 100644
--- a/src/server/api/endpoints/i.ts
+++ b/src/server/api/endpoints/i.ts
@@ -1,4 +1,4 @@
-import User, { pack } from '../../../models/user';
+import { pack } from '../../../models/user';
import define from '../define';
export const meta = {
@@ -27,11 +27,4 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
includeHasUnreadNotes: true,
includeSecrets: isSecure
}));
-
- // Update lastUsedAt
- User.update({ _id: user._id }, {
- $set: {
- lastUsedAt: new Date()
- }
- });
}));
diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts
index 4952b2f010..7bdd52883c 100644
--- a/src/server/api/endpoints/i/update.ts
+++ b/src/server/api/endpoints/i/update.ts
@@ -6,6 +6,9 @@ import acceptAllFollowRequests from '../../../../services/following/requests/acc
import { publishToFollowers } from '../../../../services/i/update';
import define from '../../define';
import getDriveFileUrl from '../../../../misc/get-drive-file-url';
+import parse from '../../../../mfm/parse';
+import extractEmojis from '../../../../misc/extract-emojis';
+const langmap = require('langmap');
export const meta = {
desc: {
@@ -32,6 +35,13 @@ export const meta = {
}
},
+ lang: {
+ validator: $.str.optional.nullable.or(Object.keys(langmap)),
+ desc: {
+ 'ja-JP': '言語'
+ }
+ },
+
location: {
validator: $.str.optional.nullable.pipe(isValidLocation),
desc: {
@@ -121,6 +131,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
if (ps.name !== undefined) updates.name = ps.name;
if (ps.description !== undefined) updates.description = ps.description;
+ if (ps.lang !== undefined) updates.lang = ps.lang;
if (ps.location !== undefined) updates['profile.location'] = ps.location;
if (ps.birthday !== undefined) updates['profile.birthday'] = ps.birthday;
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
@@ -182,6 +193,24 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
}
}
+ //#region emojis
+ if (updates.name != null || updates.description != null) {
+ let emojis = [] as string[];
+
+ if (updates.name != null) {
+ const tokens = parse(updates.name, true);
+ emojis = emojis.concat(extractEmojis(tokens));
+ }
+
+ if (updates.description != null) {
+ const tokens = parse(updates.description);
+ emojis = emojis.concat(extractEmojis(tokens));
+ }
+
+ updates.emojis = emojis;
+ }
+ //#endregion
+
await User.update(user._id, {
$set: updates
});
diff --git a/src/server/api/endpoints/i/update_email.ts b/src/server/api/endpoints/i/update_email.ts
new file mode 100644
index 0000000000..e08d1fba05
--- /dev/null
+++ b/src/server/api/endpoints/i/update_email.ts
@@ -0,0 +1,100 @@
+import $ from 'cafy';
+import User, { pack } from '../../../../models/user';
+import { publishMainStream } from '../../../../stream';
+import define from '../../define';
+import * as nodemailer from 'nodemailer';
+import fetchMeta from '../../../../misc/fetch-meta';
+import rndstr from 'rndstr';
+import config from '../../../../config';
+const ms = require('ms');
+import * as bcrypt from 'bcryptjs';
+
+export const meta = {
+ requireCredential: true,
+
+ secure: true,
+
+ limit: {
+ duration: ms('1hour'),
+ max: 3
+ },
+
+ params: {
+ password: {
+ validator: $.str
+ },
+
+ email: {
+ validator: $.str.optional.nullable
+ },
+ }
+};
+
+export default define(meta, (ps, user) => new Promise(async (res, rej) => {
+ // Compare password
+ const same = await bcrypt.compare(ps.password, user.password);
+
+ if (!same) {
+ return rej('incorrect password');
+ }
+
+ await User.update(user._id, {
+ $set: {
+ email: ps.email,
+ emailVerified: false,
+ emailVerifyCode: null
+ }
+ });
+
+ // Serialize
+ const iObj = await pack(user._id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+
+ // Send response
+ res(iObj);
+
+ // Publish meUpdated event
+ publishMainStream(user._id, 'meUpdated', iObj);
+
+ if (ps.email != null) {
+ const code = rndstr('a-z0-9', 16);
+
+ await User.update(user._id, {
+ $set: {
+ emailVerifyCode: code
+ }
+ });
+
+ const meta = await fetchMeta();
+
+ const enableAuth = meta.smtpUser != null && meta.smtpUser !== '';
+
+ const transporter = nodemailer.createTransport({
+ host: meta.smtpHost,
+ port: meta.smtpPort,
+ secure: meta.smtpSecure,
+ ignoreTLS: !enableAuth,
+ auth: enableAuth ? {
+ user: meta.smtpUser,
+ pass: meta.smtpPass
+ } : undefined
+ });
+
+ const link = `${config.url}/verify-email/${code}`;
+
+ transporter.sendMail({
+ from: meta.email,
+ to: ps.email,
+ subject: meta.name,
+ text: `To verify email, please click this link: ${link}`
+ }, (error, info) => {
+ if (error) {
+ return console.error(error);
+ }
+
+ console.log('Message sent: %s', info.messageId);
+ });
+ }
+}));
diff --git a/src/server/api/endpoints/i/update_widget.ts b/src/server/api/endpoints/i/update_widget.ts
index 90fe8fbe23..da96ec6fc1 100644
--- a/src/server/api/endpoints/i/update_widget.ts
+++ b/src/server/api/endpoints/i/update_widget.ts
@@ -59,11 +59,11 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
//#region Deck
if (widget == null && user.clientSettings.deck && user.clientSettings.deck.columns) {
const deck = user.clientSettings.deck;
- deck.columns.filter((c: any) => c.type == 'widgets').forEach((c: any) => {
- c.widgets.forEach((w: any) => {
- if (w.id == ps.id) widget = w;
- });
- });
+ for (const c of deck.columns.filter((c: any) => c.type == 'widgets')) {
+ for (const w of c.widgets.filter((w: any) => w.id == ps.id)) {
+ widget = w;
+ }
+ }
if (widget) {
widget.data = ps.data;
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index 9846e95959..57311426a8 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -63,11 +63,12 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
cacheRemoteFiles: instance.cacheRemoteFiles,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
- swPublickey: config.sw ? config.sw.public_key : null,
+ swPublickey: instance.swPublicKey,
bannerUrl: instance.bannerUrl,
+ errorImageUrl: instance.errorImageUrl,
maxNoteTextLength: instance.maxNoteTextLength,
-
emojis: emojis,
+ enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
enableGithubIntegration: instance.enableGithubIntegration,
@@ -84,8 +85,12 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
twitter: instance.enableTwitterIntegration,
github: instance.enableGithubIntegration,
discord: instance.enableDiscordIntegration,
- serviceWorker: config.sw ? true : false,
- userRecommendation: config.user_recommendation ? config.user_recommendation : {}
+ serviceWorker: instance.enableServiceWorker,
+ userRecommendation: {
+ external: instance.enableExternalUserRecommendation,
+ engine: instance.externalUserRecommendationEngine,
+ timeout: instance.externalUserRecommendationTimeout
+ }
};
}
@@ -99,6 +104,17 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
response.githubClientSecret = instance.githubClientSecret;
response.discordClientId = instance.discordClientId;
response.discordClientSecret = instance.discordClientSecret;
+ response.enableExternalUserRecommendation = instance.enableExternalUserRecommendation;
+ response.externalUserRecommendationEngine = instance.externalUserRecommendationEngine;
+ response.externalUserRecommendationTimeout = instance.externalUserRecommendationTimeout;
+ response.summalyProxy = instance.summalyProxy;
+ response.email = instance.email;
+ response.smtpSecure = instance.smtpSecure;
+ response.smtpHost = instance.smtpHost;
+ response.smtpPort = instance.smtpPort;
+ response.smtpUser = instance.smtpUser;
+ response.smtpPass = instance.smtpPass;
+ response.swPrivateKey = instance.swPrivateKey;
}
res(response);
diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts
index 5edc56165e..b489197076 100644
--- a/src/server/api/endpoints/notes.ts
+++ b/src/server/api/endpoints/notes.ts
@@ -78,6 +78,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
_id: -1
};
const query = {
+ deletedAt: null,
visibility: 'public'
} as any;
if (ps.sinceId) {
@@ -105,9 +106,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media;
- if (withFiles) {
- query.fileIds = withFiles ? { $exists: true, $ne: null } : [];
- }
+ if (withFiles) query.fileIds = { $exists: true, $ne: null };
if (ps.poll != undefined) {
query.poll = ps.poll ? { $exists: true, $ne: null } : null;
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 4f8d6a4f4f..e8c37664f5 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -42,7 +42,7 @@ export const meta = {
},
visibleUserIds: {
- validator: $.arr($.type(ID)).optional.unique().min(1),
+ validator: $.arr($.type(ID)).optional.unique().min(0),
transform: transformMany,
desc: {
'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー'
@@ -82,6 +82,30 @@ export const meta = {
}
},
+ noExtractMentions: {
+ validator: $.bool.optional,
+ default: false,
+ desc: {
+ 'ja-JP': '本文からメンションを展開しないか否か。'
+ }
+ },
+
+ noExtractHashtags: {
+ validator: $.bool.optional,
+ default: false,
+ desc: {
+ 'ja-JP': '本文からハッシュタグを展開しないか否か。'
+ }
+ },
+
+ noExtractEmojis: {
+ validator: $.bool.optional,
+ default: false,
+ desc: {
+ 'ja-JP': '本文からカスタム絵文字を展開しないか否か。'
+ }
+ },
+
geo: {
validator: $.obj({
coordinates: $.arr().length(2)
@@ -219,7 +243,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
}
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
- if ((ps.text == null) && files === null && renote === null && ps.poll == null) {
+ if (!(ps.text || files.length || renote || ps.poll)) {
return rej('text, fileIds, renoteId or poll is required');
}
@@ -237,6 +261,9 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
localOnly: ps.localOnly,
visibility: ps.visibility,
visibleUsers,
+ apMentions: ps.noExtractMentions ? [] : undefined,
+ apHashtags: ps.noExtractHashtags ? [] : undefined,
+ apEmojis: ps.noExtractEmojis ? [] : undefined,
geo: ps.geo
})
.then(note => pack(note, user))
diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts
index aa11f7bf19..1923aed9ba 100644
--- a/src/server/api/endpoints/notes/delete.ts
+++ b/src/server/api/endpoints/notes/delete.ts
@@ -3,6 +3,7 @@ import Note from '../../../../models/note';
import deleteNote from '../../../../services/note/delete';
import User from '../../../../models/user';
import define from '../../define';
+const ms = require('ms');
export const meta = {
stability: 'stable',
@@ -16,6 +17,12 @@ export const meta = {
kind: 'note-write',
+ limit: {
+ duration: ms('1hour'),
+ max: 300,
+ minInterval: ms('1sec')
+ },
+
params: {
noteId: {
validator: $.type(ID),
diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts
index 718f5e4403..a353165cd0 100644
--- a/src/server/api/endpoints/notes/mentions.ts
+++ b/src/server/api/endpoints/notes/mentions.ts
@@ -91,5 +91,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
res(await packMany(mentions, user));
- mentions.forEach(note => read(user._id, note._id));
+ for (const note of mentions) {
+ read(user._id, note._id);
+ }
}));
diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts
index cfcba788ec..2bc1a4f913 100644
--- a/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -39,6 +39,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const notes = await Note
.find({
+ '_user.host': null,
_id: {
$nin: nin
},
diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts
index dc012152c6..8de0eb420e 100644
--- a/src/server/api/endpoints/notes/polls/vote.ts
+++ b/src/server/api/endpoints/notes/polls/vote.ts
@@ -102,12 +102,12 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
})
.then(watchers => {
- watchers.forEach(watcher => {
+ for (const watcher of watchers) {
notify(watcher.userId, user._id, 'poll_vote', {
noteId: note._id,
choice: ps.choice
});
- });
+ }
});
// この投稿をWatchする
diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts
index 6046b9b310..6c2b690ab2 100644
--- a/src/server/api/endpoints/notes/replies.ts
+++ b/src/server/api/endpoints/notes/replies.ts
@@ -33,16 +33,13 @@ export const meta = {
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
- // Lookup note
- const note = await Note.findOne({
- _id: ps.noteId
- });
- if (note === null) {
- return rej('note not found');
- }
-
- const ids = (note._replyIds || []).slice(ps.offset, ps.offset + ps.limit);
+ const notes = await Note.find({
+ replyId: ps.noteId
+ }, {
+ limit: ps.limit,
+ skip: ps.offset
+ });
- res(await packMany(ids, user));
+ res(await packMany(notes, user));
}));
diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts
index 26427452fd..095c1b765d 100644
--- a/src/server/api/endpoints/sw/register.ts
+++ b/src/server/api/endpoints/sw/register.ts
@@ -1,7 +1,7 @@
import $ from 'cafy';
import Subscription from '../../../../models/sw-subscription';
-import config from '../../../../config';
import define from '../../define';
+import fetchMeta from '../../../../misc/fetch-meta';
export const meta = {
requireCredential: true,
@@ -31,10 +31,12 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
deletedAt: { $exists: false }
});
+ const instance = await fetchMeta();
+
if (exist != null) {
return res({
state: 'already-subscribed',
- key: config.sw.public_key
+ key: instance.swPublicKey
});
}
@@ -47,6 +49,6 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
res({
state: 'subscribed',
- key: config.sw.public_key
+ key: instance.swPublicKey
});
}));
diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts
index 203b4a53c8..aef5bd8507 100644
--- a/src/server/api/endpoints/users.ts
+++ b/src/server/api/endpoints/users.ts
@@ -17,7 +17,23 @@ export const meta = {
},
sort: {
- validator: $.str.optional.or('+follower|-follower'),
+ validator: $.str.optional.or([
+ '+follower',
+ '-follower',
+ '+createdAt',
+ '-createdAt',
+ '+updatedAt',
+ '-updatedAt',
+ ]),
+ },
+
+ origin: {
+ validator: $.str.optional.or([
+ 'combined',
+ 'local',
+ 'remote',
+ ]),
+ default: 'local'
}
}
};
@@ -33,6 +49,22 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
_sort = {
followersCount: 1
};
+ } else if (ps.sort == '+createdAt') {
+ _sort = {
+ createdAt: -1
+ };
+ } else if (ps.sort == '+updatedAt') {
+ _sort = {
+ updatedAt: -1
+ };
+ } else if (ps.sort == '-createdAt') {
+ _sort = {
+ createdAt: 1
+ };
+ } else if (ps.sort == '-updatedAt') {
+ _sort = {
+ updatedAt: 1
+ };
}
} else {
_sort = {
@@ -40,14 +72,17 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
};
}
+ const q =
+ ps.origin == 'local' ? { host: null } :
+ ps.origin == 'remote' ? { host: { $ne: null } } :
+ {};
+
const users = await User
- .find({
- host: null
- }, {
+ .find(q, {
limit: ps.limit,
sort: _sort,
skip: ps.offset
});
- res(await Promise.all(users.map(user => pack(user, me))));
+ res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
}));
diff --git a/src/server/api/endpoints/users/get_frequently_replied_users.ts b/src/server/api/endpoints/users/get_frequently_replied_users.ts
index cf123b82ba..353ccef1e9 100644
--- a/src/server/api/endpoints/users/get_frequently_replied_users.ts
+++ b/src/server/api/endpoints/users/get_frequently_replied_users.ts
@@ -2,6 +2,7 @@ import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import User, { pack } from '../../../../models/user';
import define from '../../define';
+import { maximum } from '../../../../prelude/array';
export const meta = {
requireCredential: false,
@@ -77,20 +78,16 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
const repliedUsers: any = {};
// Extract replies from recent notes
- replyTargetNotes.forEach(note => {
- const userId = note.userId.toString();
+ for (const userId of replyTargetNotes.map(x => x.userId.toString())) {
if (repliedUsers[userId]) {
repliedUsers[userId]++;
} else {
repliedUsers[userId] = 1;
}
- });
+ }
// Calc peak
- let peak = 0;
- Object.keys(repliedUsers).forEach(user => {
- if (repliedUsers[user] > peak) peak = repliedUsers[user];
- });
+ const peak = maximum(Object.values(repliedUsers));
// Sort replies by frequency
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
diff --git a/src/server/api/endpoints/users/lists/pull.ts b/src/server/api/endpoints/users/lists/pull.ts
new file mode 100644
index 0000000000..f1b25127b3
--- /dev/null
+++ b/src/server/api/endpoints/users/lists/pull.ts
@@ -0,0 +1,64 @@
+import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
+import UserList from '../../../../../models/user-list';
+import User, { pack as packUser } from '../../../../../models/user';
+import { publishUserListStream } from '../../../../../stream';
+import define from '../../../define';
+
+export const meta = {
+ desc: {
+ 'ja-JP': '指定したユーザーリストから指定したユーザーを削除します。',
+ 'en-US': 'Remove a user to a user list.'
+ },
+
+ requireCredential: true,
+
+ kind: 'account-write',
+
+ params: {
+ listId: {
+ validator: $.type(ID),
+ transform: transform,
+ },
+
+ userId: {
+ validator: $.type(ID),
+ transform: transform,
+ desc: {
+ 'ja-JP': '対象のユーザーのID',
+ 'en-US': 'Target user ID'
+ }
+ },
+ }
+};
+
+export default define(meta, (ps, me) => new Promise(async (res, rej) => {
+ // Fetch the list
+ const userList = await UserList.findOne({
+ _id: ps.listId,
+ userId: me._id,
+ });
+
+ if (userList == null) {
+ return rej('list not found');
+ }
+
+ // Fetch the user
+ const user = await User.findOne({
+ _id: ps.userId
+ });
+
+ if (user == null) {
+ return rej('user not found');
+ }
+
+ // Pull the user
+ await UserList.update({ _id: userList._id }, {
+ $pull: {
+ userIds: user._id
+ }
+ });
+
+ res();
+
+ publishUserListStream(userList._id, 'userRemoved', await packUser(user));
+}));
diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts
index 127029f83c..bacace6a6a 100644
--- a/src/server/api/endpoints/users/recommendation.ts
+++ b/src/server/api/endpoints/users/recommendation.ts
@@ -1,11 +1,13 @@
const ms = require('ms');
import $ from 'cafy';
-import User, { pack } from '../../../../models/user';
+import User, { pack, ILocalUser } from '../../../../models/user';
import { getFriendIds } from '../../common/get-friends';
import Mute from '../../../../models/mute';
-import * as request from 'request';
+import * as request from 'request-promise-native';
import config from '../../../../config';
import define from '../../define';
+import fetchMeta from '../../../../misc/fetch-meta';
+import resolveUser from '../../../../remote/resolve-user';
export const meta = {
desc: {
@@ -30,13 +32,15 @@ export const meta = {
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
- if (config.user_recommendation && config.user_recommendation.external) {
+ const instance = await fetchMeta();
+
+ if (instance.enableExternalUserRecommendation) {
const userName = me.username;
const hostName = config.hostname;
const limit = ps.limit;
const offset = ps.offset;
- const timeout = config.user_recommendation.timeout;
- const engine = config.user_recommendation.engine;
+ const timeout = instance.externalUserRecommendationTimeout;
+ const engine = instance.externalUserRecommendationEngine;
const url = engine
.replace('{{host}}', hostName)
.replace('{{user}}', userName)
@@ -50,13 +54,10 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
json: true,
followRedirect: true,
followAllRedirects: true
- }, (error: any, response: any, body: any) => {
- if (!error && response.statusCode == 200) {
- res(body);
- } else {
- res([]);
- }
- });
+ })
+ .then(body => convertUsers(body, me))
+ .then(packed => res(packed))
+ .catch(e => rej(e));
} else {
// ID list of the user itself and other users who the user follows
const followingIds = await getFriendIds(me._id);
@@ -72,7 +73,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
$nin: followingIds.concat(mutedUserIds)
},
isLocked: { $ne: true },
- lastUsedAt: {
+ updatedAt: {
$gte: new Date(Date.now() - ms('7days'))
},
host: null
@@ -87,3 +88,30 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
}
}));
+
+type IRecommendUser = {
+ name: string;
+ username: string;
+ host: string;
+ description: string;
+ avatarUrl: string;
+};
+
+/**
+ * Resolve/Pack dummy users
+ */
+async function convertUsers(src: IRecommendUser[], me: ILocalUser) {
+ const packed = await Promise.all(src.map(async x => {
+ const user = await resolveUser(x.username, x.host)
+ .catch(() => {
+ console.warn(`Can't resolve ${x.username}@${x.host}`);
+ return null;
+ });
+
+ if (user == null) return x;
+
+ return await pack(user, me, { detail: true });
+ }));
+
+ return packed;
+}
diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts
index edc4d603ca..86b16dcbb1 100644
--- a/src/server/api/endpoints/users/search.ts
+++ b/src/server/api/endpoints/users/search.ts
@@ -41,11 +41,19 @@ export const meta = {
'ja-JP': 'ローカルユーザーのみ検索対象にするか否か'
}
},
+
+ detail: {
+ validator: $.bool.optional,
+ default: true,
+ desc: {
+ 'ja-JP': '詳細なユーザー情報を含めるか否か'
+ }
+ },
},
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
- const isUsername = validateUsername(ps.query.replace('@', ''));
+ const isUsername = validateUsername(ps.query.replace('@', ''), !ps.localOnly);
let users: IUser[] = [];
@@ -70,86 +78,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
users = users.concat(otherUsers);
}
-
- if (users.length < ps.limit) {
- const otherUsers = await User
- .find({
- _id: { $nin: users.map(u => u._id) },
- host: null,
- usernameLower: new RegExp(escapeRegexp(ps.query.replace('@', '').toLowerCase()))
- }, {
- limit: ps.limit - users.length
- });
-
- users = users.concat(otherUsers);
- }
-
- if (users.length < ps.limit && !ps.localOnly) {
- const otherUsers = await User
- .find({
- _id: { $nin: users.map(u => u._id) },
- host: { $ne: null },
- usernameLower: new RegExp(escapeRegexp(ps.query.replace('@', '').toLowerCase()))
- }, {
- limit: ps.limit - users.length
- });
-
- users = users.concat(otherUsers);
- }
- }
-
- if (users.length < ps.limit) {
- const otherUsers = await User
- .find({
- _id: { $nin: users.map(u => u._id) },
- host: null,
- name: new RegExp('^' + escapeRegexp(ps.query.toLowerCase()))
- }, {
- limit: ps.limit - users.length
- });
-
- users = users.concat(otherUsers);
- }
-
- if (users.length < ps.limit && !ps.localOnly) {
- const otherUsers = await User
- .find({
- _id: { $nin: users.map(u => u._id) },
- host: { $ne: null },
- name: new RegExp('^' + escapeRegexp(ps.query.toLowerCase()))
- }, {
- limit: ps.limit - users.length
- });
-
- users = users.concat(otherUsers);
- }
-
- if (users.length < ps.limit) {
- const otherUsers = await User
- .find({
- _id: { $nin: users.map(u => u._id) },
- host: null,
- name: new RegExp(escapeRegexp(ps.query.toLowerCase()))
- }, {
- limit: ps.limit - users.length
- });
-
- users = users.concat(otherUsers);
- }
-
- if (users.length < ps.limit && !ps.localOnly) {
- const otherUsers = await User
- .find({
- _id: { $nin: users.map(u => u._id) },
- host: { $ne: null },
- name: new RegExp(escapeRegexp(ps.query.toLowerCase()))
- }, {
- limit: ps.limit - users.length
- });
-
- users = users.concat(otherUsers);
}
- // Serialize
- res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
+ res(await Promise.all(users.map(user => pack(user, me, { detail: ps.detail }))));
}));
diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts
index 6e4cf514de..fd26554709 100644
--- a/src/server/api/endpoints/users/show.ts
+++ b/src/server/api/endpoints/users/show.ts
@@ -80,7 +80,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
}));
if (isRemoteUser(user)) {
- if (user.updatedAt == null || Date.now() - user.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
+ if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
resolveRemoteUser(ps.username, ps.host, { }, true);
}
}