From 3791fee78decb8f926e806eb80f01811fb261297 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Mon, 11 Mar 2019 09:59:07 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE=E3=82=A8?=
 =?UTF-8?q?=E3=82=AF=E3=82=B9=E3=83=9D=E3=83=BC=E3=83=88=E3=82=92=E3=81=A7?=
 =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

#4259
---
 locales/ja-JP.yml                             |  1 +
 .../views/components/settings/profile.vue     |  2 +
 src/queue/index.ts                            |  9 +++
 src/queue/processors/db/export-user-lists.ts  | 73 +++++++++++++++++++
 src/queue/processors/db/index.ts              |  2 +
 .../api/endpoints/i/export-user-lists.ts      | 18 +++++
 6 files changed, 105 insertions(+)
 create mode 100644 src/queue/processors/db/export-user-lists.ts
 create mode 100644 src/server/api/endpoints/i/export-user-lists.ts

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 83441af8f..124e71f6b 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -706,6 +706,7 @@ common/views/components/profile-editor.vue:
     following-list: "フォロー"
     mute-list: "ミュート"
     blocking-list: "ブロック"
+    user-lists: "リスト"
   export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
   enter-password: "パスワードを入力してください"
   danger-zone: "危険な設定"
diff --git a/src/client/app/common/views/components/settings/profile.vue b/src/client/app/common/views/components/settings/profile.vue
index 61d273cfa..16e7a3b25 100644
--- a/src/client/app/common/views/components/settings/profile.vue
+++ b/src/client/app/common/views/components/settings/profile.vue
@@ -97,6 +97,7 @@
 				<option value="following">{{ $t('export-targets.following-list') }}</option>
 				<option value="mute">{{ $t('export-targets.mute-list') }}</option>
 				<option value="blocking">{{ $t('export-targets.blocking-list') }}</option>
+				<option value="user-lists">{{ $t('export-targets.user-lists') }}</option>
 			</ui-select>
 			<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button>
 		</div>
@@ -284,6 +285,7 @@ export default Vue.extend({
 				this.exportTarget == 'following' ? 'i/export-following' :
 				this.exportTarget == 'mute' ? 'i/export-mute' :
 				this.exportTarget == 'blocking' ? 'i/export-blocking' :
+				this.exportTarget == 'user-lists' ? 'i/export-user-lists' :
 				null, {});
 
 			this.$root.dialog({
diff --git a/src/queue/index.ts b/src/queue/index.ts
index 83cebe247..00a4a48f1 100644
--- a/src/queue/index.ts
+++ b/src/queue/index.ts
@@ -136,6 +136,15 @@ export function createExportBlockingJob(user: ILocalUser) {
 	});
 }
 
+export function createExportUserListsJob(user: ILocalUser) {
+	return dbQueue.add('exportUserLists', {
+		user: user
+	}, {
+		removeOnComplete: true,
+		removeOnFail: true
+	});
+}
+
 export default function() {
 	if (!program.onlyServer) {
 		deliverQueue.process(128, processDeliver);
diff --git a/src/queue/processors/db/export-user-lists.ts b/src/queue/processors/db/export-user-lists.ts
new file mode 100644
index 000000000..6b5f72c15
--- /dev/null
+++ b/src/queue/processors/db/export-user-lists.ts
@@ -0,0 +1,73 @@
+import * as Bull from 'bull';
+import * as tmp from 'tmp';
+import * as fs from 'fs';
+import * as mongo from 'mongodb';
+
+import { queueLogger } from '../../logger';
+import addFile from '../../../services/drive/add-file';
+import User from '../../../models/user';
+import dateFormat = require('dateformat');
+import config from '../../../config';
+import UserList from '../../../models/user-list';
+
+const logger = queueLogger.createSubLogger('export-user-lists');
+
+export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
+	logger.info(`Exporting user lists of ${job.data.user._id} ...`);
+
+	const user = await User.findOne({
+		_id: new mongo.ObjectID(job.data.user._id.toString())
+	});
+
+	const lists = await UserList.find({
+		userId: user._id
+	});
+
+	// Create temp file
+	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
+		tmp.file((e, path, fd, cleanup) => {
+			if (e) return rej(e);
+			res([path, cleanup]);
+		});
+	});
+
+	logger.info(`Temp file is ${path}`);
+
+	const stream = fs.createWriteStream(path, { flags: 'a' });
+
+	for (const list of lists) {
+		const users = await User.find({
+			_id: { $in: list.userIds }
+		}, {
+			fields: {
+				username: true,
+				host: true
+			}
+		});
+
+		for (const u of users) {
+			const acct = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`;
+			const content = `${list.title},${acct}`;
+			await new Promise((res, rej) => {
+				stream.write(content + '\n', err => {
+					if (err) {
+						logger.error(err);
+						rej(err);
+					} else {
+						res();
+					}
+				});
+			});
+		}
+	}
+
+	stream.end();
+	logger.succ(`Exported to: ${path}`);
+
+	const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
+	const driveFile = await addFile(user, path, fileName);
+
+	logger.succ(`Exported to: ${driveFile._id}`);
+	cleanup();
+	done();
+}
diff --git a/src/queue/processors/db/index.ts b/src/queue/processors/db/index.ts
index 31a176cda..8ac9c1a3d 100644
--- a/src/queue/processors/db/index.ts
+++ b/src/queue/processors/db/index.ts
@@ -5,6 +5,7 @@ import { exportNotes } from './export-notes';
 import { exportFollowing } from './export-following';
 import { exportMute } from './export-mute';
 import { exportBlocking } from './export-blocking';
+import { exportUserLists } from './export-user-lists';
 
 const jobs = {
 	deleteNotes,
@@ -13,6 +14,7 @@ const jobs = {
 	exportFollowing,
 	exportMute,
 	exportBlocking,
+	exportUserLists
 } as any;
 
 export default function(dbQueue: Bull.Queue) {
diff --git a/src/server/api/endpoints/i/export-user-lists.ts b/src/server/api/endpoints/i/export-user-lists.ts
new file mode 100644
index 000000000..9d7424ad8
--- /dev/null
+++ b/src/server/api/endpoints/i/export-user-lists.ts
@@ -0,0 +1,18 @@
+import define from '../../define';
+import { createExportUserListsJob } from '../../../../queue';
+import ms = require('ms');
+
+export const meta = {
+	secure: true,
+	requireCredential: true,
+	limit: {
+		duration: ms('1min'),
+		max: 1,
+	},
+};
+
+export default define(meta, async (ps, user) => {
+	createExportUserListsJob(user);
+
+	return;
+});