"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.videoImportsRouter = void 0;
const tslib_1 = require("tslib");
const express_1 = (0, tslib_1.__importDefault)(require("express"));
const fs_extra_1 = require("fs-extra");
const magnet_uri_1 = require("magnet-uri");
const parse_torrent_1 = (0, tslib_1.__importDefault)(require("parse-torrent"));
const path_1 = require("path");
const video_captions_1 = require("../../../helpers/custom-validators/video-captions");
const videos_1 = require("../../../helpers/custom-validators/videos");
const dns_1 = require("../../../helpers/dns");
const hooks_1 = require("../../../lib/plugins/hooks");
const server_config_manager_1 = require("../../../lib/server-config-manager");
const video_1 = require("../../../lib/video");
const audit_logger_1 = require("../../../helpers/audit-logger");
const captions_utils_1 = require("../../../helpers/captions-utils");
const misc_1 = require("../../../helpers/custom-validators/misc");
const express_utils_1 = require("../../../helpers/express-utils");
const logger_1 = require("../../../helpers/logger");
const utils_1 = require("../../../helpers/utils");
const youtube_dl_1 = require("../../../helpers/youtube-dl");
const config_1 = require("../../../initializers/config");
const constants_1 = require("../../../initializers/constants");
const database_1 = require("../../../initializers/database");
const url_1 = require("../../../lib/activitypub/url");
const job_queue_1 = require("../../../lib/job-queue/job-queue");
const thumbnail_1 = require("../../../lib/thumbnail");
const video_blacklist_1 = require("../../../lib/video-blacklist");
const middlewares_1 = require("../../../middlewares");
const video_2 = require("../../../models/video/video");
const video_caption_1 = require("../../../models/video/video-caption");
const video_import_1 = require("../../../models/video/video-import");
const auditLogger = (0, audit_logger_1.auditLoggerFactory)('video-imports');
const videoImportsRouter = express_1.default.Router();
exports.videoImportsRouter = videoImportsRouter;
const reqVideoFileImport = (0, express_utils_1.createReqFiles)(['thumbnailfile', 'previewfile', 'torrentfile'], Object.assign({}, constants_1.MIMETYPES.TORRENT.MIMETYPE_EXT, constants_1.MIMETYPES.IMAGE.MIMETYPE_EXT), {
    thumbnailfile: config_1.CONFIG.STORAGE.TMP_DIR,
    previewfile: config_1.CONFIG.STORAGE.TMP_DIR,
    torrentfile: config_1.CONFIG.STORAGE.TMP_DIR
});
videoImportsRouter.post('/imports', middlewares_1.authenticate, reqVideoFileImport, (0, middlewares_1.asyncMiddleware)(middlewares_1.videoImportAddValidator), (0, middlewares_1.asyncRetryTransactionMiddleware)(addVideoImport));
videoImportsRouter.post('/imports/:id/cancel', middlewares_1.authenticate, (0, middlewares_1.asyncMiddleware)(middlewares_1.videoImportCancelValidator), (0, middlewares_1.asyncRetryTransactionMiddleware)(cancelVideoImport));
videoImportsRouter.delete('/imports/:id', middlewares_1.authenticate, (0, middlewares_1.asyncMiddleware)(middlewares_1.videoImportDeleteValidator), (0, middlewares_1.asyncRetryTransactionMiddleware)(deleteVideoImport));
function deleteVideoImport(req, res) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const videoImport = res.locals.videoImport;
        yield videoImport.destroy();
        return res.sendStatus(204);
    });
}
function cancelVideoImport(req, res) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const videoImport = res.locals.videoImport;
        videoImport.state = 5;
        yield videoImport.save();
        return res.sendStatus(204);
    });
}
function addVideoImport(req, res) {
    var _a, _b;
    if (req.body.targetUrl)
        return addYoutubeDLImport(req, res);
    const file = (_b = (_a = req.files) === null || _a === void 0 ? void 0 : _a['torrentfile']) === null || _b === void 0 ? void 0 : _b[0];
    if (req.body.magnetUri || file)
        return addTorrentImport(req, res, file);
}
function addTorrentImport(req, res, torrentfile) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const body = req.body;
        const user = res.locals.oauth.token.User;
        let videoName;
        let torrentName;
        let magnetUri;
        if (torrentfile) {
            const result = yield processTorrentOrAbortRequest(req, res, torrentfile);
            if (!result)
                return;
            videoName = result.name;
            torrentName = result.torrentName;
        }
        else {
            const result = processMagnetURI(body);
            magnetUri = result.magnetUri;
            videoName = result.name;
        }
        const video = yield buildVideo(res.locals.videoChannel.id, body, { name: videoName });
        const thumbnailModel = yield processThumbnail(req, video);
        const previewModel = yield processPreview(req, video);
        const videoImport = yield insertIntoDB({
            video,
            thumbnailModel,
            previewModel,
            videoChannel: res.locals.videoChannel,
            tags: body.tags || undefined,
            user,
            videoImportAttributes: {
                magnetUri,
                torrentName,
                state: 1,
                userId: user.id
            }
        });
        const payload = {
            type: torrentfile
                ? 'torrent-file'
                : 'magnet-uri',
            videoImportId: videoImport.id,
            magnetUri
        };
        yield job_queue_1.JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload });
        auditLogger.create((0, audit_logger_1.getAuditIdFromRes)(res), new audit_logger_1.VideoImportAuditView(videoImport.toFormattedJSON()));
        return res.json(videoImport.toFormattedJSON()).end();
    });
}
function addYoutubeDLImport(req, res) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const body = req.body;
        const targetUrl = body.targetUrl;
        const user = res.locals.oauth.token.User;
        const youtubeDL = new youtube_dl_1.YoutubeDLWrapper(targetUrl, server_config_manager_1.ServerConfigManager.Instance.getEnabledResolutions('vod'));
        let youtubeDLInfo;
        try {
            youtubeDLInfo = yield youtubeDL.getInfoForDownload();
        }
        catch (err) {
            logger_1.logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err });
            return res.fail({
                message: 'Cannot fetch remote information of this URL.',
                data: {
                    targetUrl
                }
            });
        }
        if (!(yield hasUnicastURLsOnly(youtubeDLInfo))) {
            return res.fail({
                status: 403,
                message: 'Cannot use non unicast IP as targetUrl.'
            });
        }
        const video = yield buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo);
        let thumbnailModel = yield processThumbnail(req, video);
        if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) {
            try {
                thumbnailModel = yield processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video);
            }
            catch (err) {
                logger_1.logger.warn('Cannot process thumbnail %s from youtubedl.', youtubeDLInfo.thumbnailUrl, { err });
            }
        }
        let previewModel = yield processPreview(req, video);
        if (!previewModel && youtubeDLInfo.thumbnailUrl) {
            try {
                previewModel = yield processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video);
            }
            catch (err) {
                logger_1.logger.warn('Cannot process preview %s from youtubedl.', youtubeDLInfo.thumbnailUrl, { err });
            }
        }
        const videoImport = yield insertIntoDB({
            video,
            thumbnailModel,
            previewModel,
            videoChannel: res.locals.videoChannel,
            tags: body.tags || youtubeDLInfo.tags,
            user,
            videoImportAttributes: {
                targetUrl,
                state: 1,
                userId: user.id
            }
        });
        yield processYoutubeSubtitles(youtubeDL, targetUrl, video.id);
        let fileExt = `.${youtubeDLInfo.ext}`;
        if (!(0, videos_1.isVideoFileExtnameValid)(fileExt))
            fileExt = '.mp4';
        const payload = {
            type: 'youtube-dl',
            videoImportId: videoImport.id,
            fileExt
        };
        yield job_queue_1.JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload });
        auditLogger.create((0, audit_logger_1.getAuditIdFromRes)(res), new audit_logger_1.VideoImportAuditView(videoImport.toFormattedJSON()));
        return res.json(videoImport.toFormattedJSON()).end();
    });
}
function buildVideo(channelId, body, importData) {
    var _a, _b, _c, _d;
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        let videoData = {
            name: body.name || importData.name || 'Unknown name',
            remote: false,
            category: body.category || importData.category,
            licence: (_b = (_a = body.licence) !== null && _a !== void 0 ? _a : importData.licence) !== null && _b !== void 0 ? _b : config_1.CONFIG.DEFAULTS.PUBLISH.LICENCE,
            language: body.language || importData.language,
            commentsEnabled: (_c = body.commentsEnabled) !== null && _c !== void 0 ? _c : config_1.CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
            downloadEnabled: (_d = body.downloadEnabled) !== null && _d !== void 0 ? _d : config_1.CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED,
            waitTranscoding: body.waitTranscoding || false,
            state: 3,
            nsfw: body.nsfw || importData.nsfw || false,
            description: body.description || importData.description,
            support: body.support || null,
            privacy: body.privacy || 3,
            duration: 0,
            channelId: channelId,
            originallyPublishedAt: body.originallyPublishedAt
                ? new Date(body.originallyPublishedAt)
                : importData.originallyPublishedAt
        };
        videoData = yield hooks_1.Hooks.wrapObject(videoData, body.targetUrl
            ? 'filter:api.video.import-url.video-attribute.result'
            : 'filter:api.video.import-torrent.video-attribute.result');
        const video = new video_2.VideoModel(videoData);
        video.url = (0, url_1.getLocalVideoActivityPubUrl)(video);
        return video;
    });
}
function processThumbnail(req, video) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined;
        if (thumbnailField) {
            const thumbnailPhysicalFile = thumbnailField[0];
            return (0, thumbnail_1.updateVideoMiniatureFromExisting)({
                inputPath: thumbnailPhysicalFile.path,
                video,
                type: 1,
                automaticallyGenerated: false
            });
        }
        return undefined;
    });
}
function processPreview(req, video) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const previewField = req.files ? req.files['previewfile'] : undefined;
        if (previewField) {
            const previewPhysicalFile = previewField[0];
            return (0, thumbnail_1.updateVideoMiniatureFromExisting)({
                inputPath: previewPhysicalFile.path,
                video,
                type: 2,
                automaticallyGenerated: false
            });
        }
        return undefined;
    });
}
function processThumbnailFromUrl(url, video) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        try {
            return (0, thumbnail_1.updateVideoMiniatureFromUrl)({ downloadUrl: url, video, type: 1 });
        }
        catch (err) {
            logger_1.logger.warn('Cannot generate video thumbnail %s for %s.', url, video.url, { err });
            return undefined;
        }
    });
}
function processPreviewFromUrl(url, video) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        try {
            return (0, thumbnail_1.updateVideoMiniatureFromUrl)({ downloadUrl: url, video, type: 2 });
        }
        catch (err) {
            logger_1.logger.warn('Cannot generate video preview %s for %s.', url, video.url, { err });
            return undefined;
        }
    });
}
function insertIntoDB(parameters) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters;
        const videoImport = yield database_1.sequelizeTypescript.transaction((t) => (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
            const sequelizeOptions = { transaction: t };
            const videoCreated = yield video.save(sequelizeOptions);
            videoCreated.VideoChannel = videoChannel;
            if (thumbnailModel)
                yield videoCreated.addAndSaveThumbnail(thumbnailModel, t);
            if (previewModel)
                yield videoCreated.addAndSaveThumbnail(previewModel, t);
            yield (0, video_blacklist_1.autoBlacklistVideoIfNeeded)({
                video: videoCreated,
                user,
                notify: false,
                isRemote: false,
                isNew: true,
                transaction: t
            });
            yield (0, video_1.setVideoTags)({ video: videoCreated, tags, transaction: t });
            const videoImport = yield video_import_1.VideoImportModel.create(Object.assign({ videoId: videoCreated.id }, videoImportAttributes), sequelizeOptions);
            videoImport.Video = videoCreated;
            return videoImport;
        }));
        return videoImport;
    });
}
function processTorrentOrAbortRequest(req, res, torrentfile) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const torrentName = torrentfile.originalname;
        const newTorrentPath = (0, path_1.join)(config_1.CONFIG.STORAGE.TORRENTS_DIR, (0, utils_1.getSecureTorrentName)(torrentName));
        yield (0, fs_extra_1.move)(torrentfile.path, newTorrentPath, { overwrite: true });
        torrentfile.path = newTorrentPath;
        const buf = yield (0, fs_extra_1.readFile)(torrentfile.path);
        const parsedTorrent = (0, parse_torrent_1.default)(buf);
        if (parsedTorrent.files.length !== 1) {
            (0, express_utils_1.cleanUpReqFiles)(req);
            res.fail({
                type: "incorrect_files_in_torrent",
                message: 'Torrents with only 1 file are supported.'
            });
            return undefined;
        }
        return {
            name: extractNameFromArray(parsedTorrent.name),
            torrentName
        };
    });
}
function processMagnetURI(body) {
    const magnetUri = body.magnetUri;
    const parsed = (0, magnet_uri_1.decode)(magnetUri);
    return {
        name: extractNameFromArray(parsed.name),
        magnetUri
    };
}
function extractNameFromArray(name) {
    return (0, misc_1.isArray)(name) ? name[0] : name;
}
function processYoutubeSubtitles(youtubeDL, targetUrl, videoId) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        try {
            const subtitles = yield youtubeDL.getSubtitles();
            logger_1.logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl);
            for (const subtitle of subtitles) {
                if (!(yield (0, video_captions_1.isVTTFileValid)(subtitle.path))) {
                    yield (0, fs_extra_1.remove)(subtitle.path);
                    continue;
                }
                const videoCaption = new video_caption_1.VideoCaptionModel({
                    videoId,
                    language: subtitle.language,
                    filename: video_caption_1.VideoCaptionModel.generateCaptionName(subtitle.language)
                });
                yield (0, captions_utils_1.moveAndProcessCaptionFile)(subtitle, videoCaption);
                yield database_1.sequelizeTypescript.transaction((t) => (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
                    yield video_caption_1.VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t);
                }));
            }
        }
        catch (err) {
            logger_1.logger.warn('Cannot get video subtitles.', { err });
        }
    });
}
function hasUnicastURLsOnly(youtubeDLInfo) {
    return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
        const hosts = youtubeDLInfo.urls.map(u => new URL(u).hostname);
        const uniqHosts = new Set(hosts);
        for (const h of uniqHosts) {
            if ((yield (0, dns_1.isResolvingToUnicastOnly)(h)) !== true) {
                return false;
            }
        }
        return true;
    });
}
//# sourceMappingURL=import.js.map