egirlskey/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts

122 lines
3.5 KiB
TypeScript
Raw Normal View History

2022-09-17 18:27:08 +00:00
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
2022-09-17 18:27:08 +00:00
import unzipper from 'unzipper';
import { DI } from '@/di-symbols.js';
2022-09-20 20:33:11 +00:00
import type { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
2022-09-17 18:27:08 +00:00
import type Logger from '@/logger.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { createTempDir } from '@/misc/create-temp.js';
import { DriveService } from '@/core/DriveService.js';
import { DownloadService } from '@/core/DownloadService.js';
import { bindThis } from '@/decorators.js';
2022-09-17 18:27:08 +00:00
import { QueueLoggerService } from '../QueueLoggerService.js';
import type Bull from 'bull';
import type { DbUserImportJobData } from '../types.js';
// TODO: 名前衝突時の動作を選べるようにする
@Injectable()
export class ImportCustomEmojisProcessorService {
2022-09-18 18:11:50 +00:00
private logger: Logger;
2022-09-17 18:27:08 +00:00
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.db)
private db: DataSource,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private customEmojiService: CustomEmojiService,
private driveService: DriveService,
private downloadService: DownloadService,
private queueLoggerService: QueueLoggerService,
) {
2022-09-18 18:11:50 +00:00
this.logger = this.queueLoggerService.logger.createSubLogger('import-custom-emojis');
2022-09-17 18:27:08 +00:00
}
@bindThis
2022-09-17 18:27:08 +00:00
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
2022-09-18 18:11:50 +00:00
this.logger.info('Importing custom emojis ...');
2022-09-17 18:27:08 +00:00
const file = await this.driveFilesRepository.findOneBy({
id: job.data.fileId,
});
if (file == null) {
done();
return;
}
const [path, cleanup] = await createTempDir();
2022-09-18 18:11:50 +00:00
this.logger.info(`Temp dir is ${path}`);
2022-09-17 18:27:08 +00:00
const destPath = path + '/emojis.zip';
try {
fs.writeFileSync(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') {
2022-09-18 18:11:50 +00:00
this.logger.error(e);
2022-09-17 18:27:08 +00:00
}
throw e;
}
const outputPath = path + '/emojis';
const unzipStream = fs.createReadStream(destPath);
const extractor = unzipper.Extract({ path: outputPath });
extractor.on('close', async () => {
const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8');
const meta = JSON.parse(metaRaw);
for (const record of meta.emojis) {
if (!record.downloaded) continue;
if (!/^[a-zA-Z0-9_]+?([a-zA-Z0-9\.]+)?$/.test(record.fileName)) {
this.logger.error(`invalid filename: ${record.fileName}`);
continue;
}
2022-09-17 18:27:08 +00:00
const emojiInfo = record.emoji;
if (!/^[a-zA-Z0-9_]+$/.test(emojiInfo.name)) {
this.logger.error(`invalid emojiname: ${emojiInfo.name}`);
continue;
}
2022-09-17 18:27:08 +00:00
const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({
name: emojiInfo.name,
});
const driveFile = await this.driveService.addFile({
user: null,
path: emojiPath,
name: record.fileName,
force: true,
});
await this.customEmojiService.add({
name: emojiInfo.name,
category: emojiInfo.category,
host: null,
aliases: emojiInfo.aliases,
driveFile,
license: emojiInfo.license,
2022-09-17 18:27:08 +00:00
});
}
cleanup();
2022-09-18 18:11:50 +00:00
this.logger.succ('Imported');
2022-09-17 18:27:08 +00:00
done();
});
unzipStream.pipe(extractor);
2022-09-18 18:11:50 +00:00
this.logger.succ(`Unzipping to ${outputPath}`);
2022-09-17 18:27:08 +00:00
}
}