Resolve #4151
This commit is contained in:
		| @@ -558,7 +558,11 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "メールアドレスが確認されました" |   email-verified: "メールアドレスが確認されました" | ||||||
|   email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。" |   email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。" | ||||||
|   export: "エクスポート" |   export: "エクスポート" | ||||||
|   export-notes: "すべての投稿のエクスポート" |   export-targets: | ||||||
|  |     all-notes: "すべての投稿データ" | ||||||
|  |     following-list: "フォロー" | ||||||
|  |     mute-list: "ミュート" | ||||||
|  |     blocking-list: "ブロック" | ||||||
|   export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。" |   export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。" | ||||||
|  |  | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   | |||||||
| @@ -92,7 +92,14 @@ | |||||||
| 		<header>{{ $t('export') }}</header> | 		<header>{{ $t('export') }}</header> | ||||||
|  |  | ||||||
| 		<div> | 		<div> | ||||||
| 			<ui-button @click="exportNotes()"><fa :icon="faDownload"/> {{ $t('export-notes') }}</ui-button> | 			<ui-select v-model="exportTarget"> | ||||||
|  | 				<span slot="label">{{ $t('export-target') }}</span> | ||||||
|  | 				<option value="notes">{{ $t('export-targets.all-notes') }}</option> | ||||||
|  | 				<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> | ||||||
|  | 			</ui-select> | ||||||
|  | 			<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button> | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
| </ui-card> | </ui-card> | ||||||
| @@ -133,6 +140,7 @@ export default Vue.extend({ | |||||||
| 			saving: false, | 			saving: false, | ||||||
| 			avatarUploading: false, | 			avatarUploading: false, | ||||||
| 			bannerUploading: false, | 			bannerUploading: false, | ||||||
|  | 			exportTarget: 'notes', | ||||||
| 			faDownload | 			faDownload | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| @@ -264,8 +272,13 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		exportNotes() { | 		doExport() { | ||||||
| 			this.$root.api('i/export-notes', {}); | 			this.$root.api( | ||||||
|  | 				this.exportTarget == 'notes' ? 'i/export-notes' : | ||||||
|  | 				this.exportTarget == 'following' ? 'i/export-following' : | ||||||
|  | 				this.exportTarget == 'mute' ? 'i/export-mute' : | ||||||
|  | 				this.exportTarget == 'blocking' ? 'i/export-blocking' : | ||||||
|  | 				null, {}); | ||||||
|  |  | ||||||
| 			this.$root.dialog({ | 			this.$root.dialog({ | ||||||
| 				type: 'info', | 				type: 'info', | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								src/queue/processors/export-blocking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/queue/processors/export-blocking.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  |  | ||||||
|  | import * as bq from 'bee-queue'; | ||||||
|  | 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 Blocking from '../../models/blocking'; | ||||||
|  | import config from '../../config'; | ||||||
|  |  | ||||||
|  | const logger = queueLogger.createSubLogger('export-blocking'); | ||||||
|  |  | ||||||
|  | export async function exportBlocking(job: bq.Job, done: any): Promise<void> { | ||||||
|  | 	logger.info(`Exporting blocking of ${job.data.user._id} ...`); | ||||||
|  |  | ||||||
|  | 	const user = await User.findOne({ | ||||||
|  | 		_id: new mongo.ObjectID(job.data.user._id.toString()) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// 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' }); | ||||||
|  |  | ||||||
|  | 	let exportedCount = 0; | ||||||
|  | 	let ended = false; | ||||||
|  | 	let cursor: any = null; | ||||||
|  |  | ||||||
|  | 	while (!ended) { | ||||||
|  | 		const blockings = await Blocking.find({ | ||||||
|  | 			blockerId: user._id, | ||||||
|  | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | 		}, { | ||||||
|  | 			limit: 100, | ||||||
|  | 			sort: { | ||||||
|  | 				_id: 1 | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		if (blockings.length === 0) { | ||||||
|  | 			ended = true; | ||||||
|  | 			job.reportProgress(100); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cursor = blockings[blockings.length - 1]._id; | ||||||
|  |  | ||||||
|  | 		for (const block of blockings) { | ||||||
|  | 			const u = await User.findOne({ _id: block.blockeeId }, { fields: { username: true, host: true } }); | ||||||
|  | 			const content = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`; | ||||||
|  | 			await new Promise((res, rej) => { | ||||||
|  | 				stream.write(content + '\n', err => { | ||||||
|  | 					if (err) { | ||||||
|  | 						logger.error(err); | ||||||
|  | 						rej(err); | ||||||
|  | 					} else { | ||||||
|  | 						res(); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 			exportedCount++; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const total = await Blocking.count({ | ||||||
|  | 			blockerId: user._id, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		job.reportProgress(exportedCount / total); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	stream.end(); | ||||||
|  | 	logger.succ(`Exported to: ${path}`); | ||||||
|  |  | ||||||
|  | 	const fileName = dateFormat(new Date(), 'blocking-yyyy-mm-dd-HH-MM-ss') + '.json'; | ||||||
|  | 	const driveFile = await addFile(user, path, fileName); | ||||||
|  |  | ||||||
|  | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
|  | 	cleanup(); | ||||||
|  | 	done(); | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								src/queue/processors/export-following.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/queue/processors/export-following.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  |  | ||||||
|  | import * as bq from 'bee-queue'; | ||||||
|  | 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 Following from '../../models/following'; | ||||||
|  | import config from '../../config'; | ||||||
|  |  | ||||||
|  | const logger = queueLogger.createSubLogger('export-following'); | ||||||
|  |  | ||||||
|  | export async function exportFollowing(job: bq.Job, done: any): Promise<void> { | ||||||
|  | 	logger.info(`Exporting following of ${job.data.user._id} ...`); | ||||||
|  |  | ||||||
|  | 	const user = await User.findOne({ | ||||||
|  | 		_id: new mongo.ObjectID(job.data.user._id.toString()) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// 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' }); | ||||||
|  |  | ||||||
|  | 	let exportedCount = 0; | ||||||
|  | 	let ended = false; | ||||||
|  | 	let cursor: any = null; | ||||||
|  |  | ||||||
|  | 	while (!ended) { | ||||||
|  | 		const followings = await Following.find({ | ||||||
|  | 			followerId: user._id, | ||||||
|  | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | 		}, { | ||||||
|  | 			limit: 100, | ||||||
|  | 			sort: { | ||||||
|  | 				_id: 1 | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		if (followings.length === 0) { | ||||||
|  | 			ended = true; | ||||||
|  | 			job.reportProgress(100); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cursor = followings[followings.length - 1]._id; | ||||||
|  |  | ||||||
|  | 		for (const following of followings) { | ||||||
|  | 			const u = await User.findOne({ _id: following.followeeId }, { fields: { username: true, host: true } }); | ||||||
|  | 			const content = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`; | ||||||
|  | 			await new Promise((res, rej) => { | ||||||
|  | 				stream.write(content + '\n', err => { | ||||||
|  | 					if (err) { | ||||||
|  | 						logger.error(err); | ||||||
|  | 						rej(err); | ||||||
|  | 					} else { | ||||||
|  | 						res(); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 			exportedCount++; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const total = await Following.count({ | ||||||
|  | 			followerId: user._id, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		job.reportProgress(exportedCount / total); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	stream.end(); | ||||||
|  | 	logger.succ(`Exported to: ${path}`); | ||||||
|  |  | ||||||
|  | 	const fileName = dateFormat(new Date(), 'following-yyyy-mm-dd-HH-MM-ss') + '.json'; | ||||||
|  | 	const driveFile = await addFile(user, path, fileName); | ||||||
|  |  | ||||||
|  | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
|  | 	cleanup(); | ||||||
|  | 	done(); | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								src/queue/processors/export-mute.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/queue/processors/export-mute.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  |  | ||||||
|  | import * as bq from 'bee-queue'; | ||||||
|  | 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 Mute from '../../models/mute'; | ||||||
|  | import config from '../../config'; | ||||||
|  |  | ||||||
|  | const logger = queueLogger.createSubLogger('export-mute'); | ||||||
|  |  | ||||||
|  | export async function exportMute(job: bq.Job, done: any): Promise<void> { | ||||||
|  | 	logger.info(`Exporting mute of ${job.data.user._id} ...`); | ||||||
|  |  | ||||||
|  | 	const user = await User.findOne({ | ||||||
|  | 		_id: new mongo.ObjectID(job.data.user._id.toString()) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// 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' }); | ||||||
|  |  | ||||||
|  | 	let exportedCount = 0; | ||||||
|  | 	let ended = false; | ||||||
|  | 	let cursor: any = null; | ||||||
|  |  | ||||||
|  | 	while (!ended) { | ||||||
|  | 		const mutes = await Mute.find({ | ||||||
|  | 			muterId: user._id, | ||||||
|  | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | 		}, { | ||||||
|  | 			limit: 100, | ||||||
|  | 			sort: { | ||||||
|  | 				_id: 1 | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		if (mutes.length === 0) { | ||||||
|  | 			ended = true; | ||||||
|  | 			job.reportProgress(100); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cursor = mutes[mutes.length - 1]._id; | ||||||
|  |  | ||||||
|  | 		for (const mute of mutes) { | ||||||
|  | 			const u = await User.findOne({ _id: mute.muteeId }, { fields: { username: true, host: true } }); | ||||||
|  | 			const content = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`; | ||||||
|  | 			await new Promise((res, rej) => { | ||||||
|  | 				stream.write(content + '\n', err => { | ||||||
|  | 					if (err) { | ||||||
|  | 						logger.error(err); | ||||||
|  | 						rej(err); | ||||||
|  | 					} else { | ||||||
|  | 						res(); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 			exportedCount++; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const total = await Mute.count({ | ||||||
|  | 			muterId: user._id, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		job.reportProgress(exportedCount / total); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	stream.end(); | ||||||
|  | 	logger.succ(`Exported to: ${path}`); | ||||||
|  |  | ||||||
|  | 	const fileName = dateFormat(new Date(), 'mute-yyyy-mm-dd-HH-MM-ss') + '.json'; | ||||||
|  | 	const driveFile = await addFile(user, path, fileName); | ||||||
|  |  | ||||||
|  | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
|  | 	cleanup(); | ||||||
|  | 	done(); | ||||||
|  | } | ||||||
| @@ -1,12 +1,18 @@ | |||||||
| import deliver from './http/deliver'; | import deliver from './http/deliver'; | ||||||
| import processInbox from './http/process-inbox'; | import processInbox from './http/process-inbox'; | ||||||
| import { exportNotes } from './export-notes'; | import { exportNotes } from './export-notes'; | ||||||
|  | import { exportFollowing } from './export-following'; | ||||||
|  | import { exportMute } from './export-mute'; | ||||||
|  | import { exportBlocking } from './export-blocking'; | ||||||
| import { queueLogger } from '../logger'; | import { queueLogger } from '../logger'; | ||||||
|  |  | ||||||
| const handlers: any = { | const handlers: any = { | ||||||
| 	deliver, | 	deliver, | ||||||
| 	processInbox, | 	processInbox, | ||||||
| 	exportNotes, | 	exportNotes, | ||||||
|  | 	exportFollowing, | ||||||
|  | 	exportMute, | ||||||
|  | 	exportBlocking, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default (job: any, done: any) => { | export default (job: any, done: any) => { | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/server/api/endpoints/i/export-blocking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/server/api/endpoints/i/export-blocking.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import define from '../../define'; | ||||||
|  | import { createExportBlockingJob } from '../../../../queue'; | ||||||
|  | import ms = require('ms'); | ||||||
|  |  | ||||||
|  | export const meta = { | ||||||
|  | 	secure: true, | ||||||
|  | 	requireCredential: true, | ||||||
|  | 	limit: { | ||||||
|  | 		duration: ms('1hour'), | ||||||
|  | 		max: 1, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default define(meta, (ps, user) => new Promise(async (res, rej) => { | ||||||
|  | 	createExportBlockingJob(user); | ||||||
|  |  | ||||||
|  | 	res(); | ||||||
|  | })); | ||||||
							
								
								
									
										18
									
								
								src/server/api/endpoints/i/export-following.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/server/api/endpoints/i/export-following.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import define from '../../define'; | ||||||
|  | import { createExportFollowingJob } from '../../../../queue'; | ||||||
|  | import ms = require('ms'); | ||||||
|  |  | ||||||
|  | export const meta = { | ||||||
|  | 	secure: true, | ||||||
|  | 	requireCredential: true, | ||||||
|  | 	limit: { | ||||||
|  | 		duration: ms('1hour'), | ||||||
|  | 		max: 1, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default define(meta, (ps, user) => new Promise(async (res, rej) => { | ||||||
|  | 	createExportFollowingJob(user); | ||||||
|  |  | ||||||
|  | 	res(); | ||||||
|  | })); | ||||||
							
								
								
									
										18
									
								
								src/server/api/endpoints/i/export-mute.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/server/api/endpoints/i/export-mute.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import define from '../../define'; | ||||||
|  | import { createExportMuteJob } from '../../../../queue'; | ||||||
|  | import ms = require('ms'); | ||||||
|  |  | ||||||
|  | export const meta = { | ||||||
|  | 	secure: true, | ||||||
|  | 	requireCredential: true, | ||||||
|  | 	limit: { | ||||||
|  | 		duration: ms('1hour'), | ||||||
|  | 		max: 1, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default define(meta, (ps, user) => new Promise(async (res, rej) => { | ||||||
|  | 	createExportMuteJob(user); | ||||||
|  |  | ||||||
|  | 	res(); | ||||||
|  | })); | ||||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo