"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isVideoAccepted = exports.videosOverviewValidator = exports.commonVideosFiltersValidator = exports.getCommonVideoEditAttributes = exports.videosRemoveValidator = exports.videosCustomGetValidator = exports.checkVideoFollowConstraints = exports.videosDownloadValidator = exports.videoFileMetadataGetValidator = exports.videosGetValidator = exports.videosUpdateValidator = exports.videosAddResumableInitValidator = exports.videosAddResumableValidator = exports.videosAddLegacyValidator = void 0;
const tslib_1 = require("tslib");
const express_validator_1 = require("express-validator");
const core_utils_1 = require("../../../helpers/core-utils");
const upload_1 = require("../../../helpers/upload");
const redis_1 = require("../../../lib/redis");
const application_1 = require("../../../models/application/application");
const core_utils_2 = require("../../../../shared/core-utils");
const misc_1 = require("../../../helpers/custom-validators/misc");
const search_1 = require("../../../helpers/custom-validators/search");
const videos_1 = require("../../../helpers/custom-validators/videos");
const express_utils_1 = require("../../../helpers/express-utils");
const ffmpeg_1 = require("../../../helpers/ffmpeg");
const logger_1 = require("../../../helpers/logger");
const utils_1 = require("../../../helpers/utils");
const video_1 = require("../../../helpers/video");
const config_1 = require("../../../initializers/config");
const constants_1 = require("../../../initializers/constants");
const moderation_1 = require("../../../lib/moderation");
const hooks_1 = require("../../../lib/plugins/hooks");
const video_2 = require("../../../models/video/video");
const shared_1 = require("../shared");
const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
    (0, express_validator_1.body)('videofile')
        .custom((_, { req }) => (0, misc_1.isFileValid)({ files: req.files, field: 'videofile', mimeTypeRegex: null, maxSize: null }))
        .withMessage('Should have a file'),
    (0, express_validator_1.body)('name')
        .trim()
        .custom(videos_1.isVideoNameValid).withMessage(`Should have a video name between ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`),
    (0, express_validator_1.body)('channelId')
        .customSanitizer(misc_1.toIntOrNull)
        .custom(misc_1.isIdValid),
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        if ((0, shared_1.areValidationErrors)(req, res))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        const videoFile = req.files['videofile'][0];
        const user = res.locals.oauth.token.User;
        if (!(yield commonVideoChecksPass({ req, res, user, videoFileSize: videoFile.size, files: req.files }))) {
            return (0, express_utils_1.cleanUpReqFiles)(req);
        }
        try {
            if (!videoFile.duration)
                yield addDurationToVideo(videoFile);
        }
        catch (err) {
            logger_1.logger.error('Invalid input file in videosAddLegacyValidator.', { err });
            res.fail({
                status: 422,
                message: 'Video file unreadable.'
            });
            return (0, express_utils_1.cleanUpReqFiles)(req);
        }
        if (!(yield isVideoAccepted(req, res, videoFile)))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        return next();
    })
]);
exports.videosAddLegacyValidator = videosAddLegacyValidator;
const videosAddResumableValidator = [
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        const user = res.locals.oauth.token.User;
        const body = req.body;
        const file = Object.assign(Object.assign({}, body), { duration: undefined, path: (0, upload_1.getResumableUploadPath)(body.name), filename: body.metadata.filename });
        const cleanup = () => (0, utils_1.deleteFileAndCatch)(file.path);
        const uploadId = req.query.upload_id;
        const sessionExists = yield redis_1.Redis.Instance.doesUploadSessionExist(uploadId);
        if (sessionExists) {
            const sessionResponse = yield redis_1.Redis.Instance.getUploadSession(uploadId);
            if (!sessionResponse) {
                res.setHeader('Retry-After', 300);
                return res.fail({
                    status: 503,
                    message: 'The upload is already being processed'
                });
            }
            if ((0, core_utils_1.isTestInstance)()) {
                res.setHeader('x-resumable-upload-cached', 'true');
            }
            return res.json(sessionResponse);
        }
        yield redis_1.Redis.Instance.setUploadSession(uploadId);
        if (!(yield (0, shared_1.doesVideoChannelOfAccountExist)(file.metadata.channelId, user, res)))
            return cleanup();
        try {
            if (!file.duration)
                yield addDurationToVideo(file);
        }
        catch (err) {
            logger_1.logger.error('Invalid input file in videosAddResumableValidator.', { err });
            res.fail({
                status: 422,
                message: 'Video file unreadable.'
            });
            return cleanup();
        }
        if (!(yield isVideoAccepted(req, res, file)))
            return cleanup();
        res.locals.videoFileResumable = Object.assign(Object.assign({}, file), { originalname: file.filename });
        return next();
    })
];
exports.videosAddResumableValidator = videosAddResumableValidator;
const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
    (0, express_validator_1.body)('filename')
        .exists(),
    (0, express_validator_1.body)('name')
        .trim()
        .custom(videos_1.isVideoNameValid).withMessage(`Should have a video name between ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`),
    (0, express_validator_1.body)('channelId')
        .customSanitizer(misc_1.toIntOrNull)
        .custom(misc_1.isIdValid),
    (0, express_validator_1.header)('x-upload-content-length')
        .isNumeric()
        .exists()
        .withMessage('Should specify the file length'),
    (0, express_validator_1.header)('x-upload-content-type')
        .isString()
        .exists()
        .withMessage('Should specify the file mimetype'),
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        var _a;
        const videoFileMetadata = {
            mimetype: req.headers['x-upload-content-type'],
            size: +req.headers['x-upload-content-length'],
            originalname: req.body.filename
        };
        const user = res.locals.oauth.token.User;
        const cleanup = () => (0, express_utils_1.cleanUpReqFiles)(req);
        logger_1.logger.debug('Checking videosAddResumableInitValidator parameters and headers', {
            parameters: req.body,
            headers: req.headers,
            files: req.files
        });
        if ((0, shared_1.areValidationErrors)(req, res, { omitLog: true }))
            return cleanup();
        const files = { videofile: [videoFileMetadata] };
        if (!(yield commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })))
            return cleanup();
        req.headers['content-type'] = 'application/json; charset=utf-8';
        if ((_a = req.files) === null || _a === void 0 ? void 0 : _a['previewfile'])
            req.body.previewfile = req.files['previewfile'];
        return next();
    })
]);
exports.videosAddResumableInitValidator = videosAddResumableInitValidator;
const videosUpdateValidator = getCommonVideoEditAttributes().concat([
    (0, shared_1.isValidVideoIdParam)('id'),
    (0, express_validator_1.body)('name')
        .optional()
        .trim()
        .custom(videos_1.isVideoNameValid).withMessage(`Should have a video name between ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`),
    (0, express_validator_1.body)('channelId')
        .optional()
        .customSanitizer(misc_1.toIntOrNull)
        .custom(misc_1.isIdValid),
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        if ((0, shared_1.areValidationErrors)(req, res))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        if (areErrorsInScheduleUpdate(req, res))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        if (!(yield (0, shared_1.doesVideoExist)(req.params.id, res)))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        const video = (0, video_1.getVideoWithAttributes)(res);
        if (req.body.privacy && video.isLive && video.state !== 4) {
            return res.fail({ message: 'Cannot update privacy of a live that has already started' });
        }
        const user = res.locals.oauth.token.User;
        if (!(0, shared_1.checkUserCanManageVideo)(user, res.locals.videoAll, 17, res))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        if (req.body.channelId && !(yield (0, shared_1.doesVideoChannelOfAccountExist)(req.body.channelId, user, res)))
            return (0, express_utils_1.cleanUpReqFiles)(req);
        return next();
    })
]);
exports.videosUpdateValidator = videosUpdateValidator;
function checkVideoFollowConstraints(req, res, next) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const video = (0, video_1.getVideoWithAttributes)(res);
        if (video.isOwned() === true)
            return next();
        if (res.locals.oauth) {
            if (config_1.CONFIG.SEARCH.REMOTE_URI.USERS === true)
                return next();
        }
        if (config_1.CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true)
            return next();
        const serverActor = yield (0, application_1.getServerActor)();
        if ((yield video_2.VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id)) === true)
            return next();
        return res.fail({
            status: 403,
            message: 'Cannot get this video regarding follow constraints',
            type: "does_not_respect_follow_constraints",
            data: {
                originUrl: video.url
            }
        });
    });
}
exports.checkVideoFollowConstraints = checkVideoFollowConstraints;
const videosCustomGetValidator = (fetchType) => {
    return [
        (0, shared_1.isValidVideoIdParam)('id'),
        (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
            if ((0, shared_1.areValidationErrors)(req, res))
                return;
            if (!(yield (0, shared_1.doesVideoExist)(req.params.id, res, fetchType)))
                return;
            if (fetchType === 'only-immutable-attributes')
                return next();
            const video = (0, video_1.getVideoWithAttributes)(res);
            if (!(yield (0, shared_1.checkCanSeeVideo)({ req, res, video, paramId: req.params.id })))
                return;
            return next();
        })
    ];
};
exports.videosCustomGetValidator = videosCustomGetValidator;
const videosGetValidator = videosCustomGetValidator('all');
exports.videosGetValidator = videosGetValidator;
const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
    (0, shared_1.isValidVideoIdParam)('id'),
    (0, express_validator_1.param)('videoFileId')
        .custom(misc_1.isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'),
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        if ((0, shared_1.areValidationErrors)(req, res))
            return;
        if (!(yield (0, shared_1.doesVideoFileOfVideoExist)(+req.params.videoFileId, req.params.id, res)))
            return;
        return next();
    })
]);
exports.videoFileMetadataGetValidator = videoFileMetadataGetValidator;
const videosDownloadValidator = [
    (0, shared_1.isValidVideoIdParam)('id'),
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        if ((0, shared_1.areValidationErrors)(req, res))
            return;
        if (!(yield (0, shared_1.doesVideoExist)(req.params.id, res, 'all')))
            return;
        const video = (0, video_1.getVideoWithAttributes)(res);
        if (!(yield (0, shared_1.checkCanAccessVideoStaticFiles)({ req, res, video, paramId: req.params.id })))
            return;
        return next();
    })
];
exports.videosDownloadValidator = videosDownloadValidator;
const videosRemoveValidator = [
    (0, shared_1.isValidVideoIdParam)('id'),
    (req, res, next) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
        if ((0, shared_1.areValidationErrors)(req, res))
            return;
        if (!(yield (0, shared_1.doesVideoExist)(req.params.id, res)))
            return;
        if (!(0, shared_1.checkUserCanManageVideo)(res.locals.oauth.token.User, res.locals.videoAll, 14, res))
            return;
        return next();
    })
];
exports.videosRemoveValidator = videosRemoveValidator;
const videosOverviewValidator = [
    (0, express_validator_1.query)('page')
        .optional()
        .isInt({ min: 1, max: constants_1.OVERVIEWS.VIDEOS.SAMPLES_COUNT }),
    (req, res, next) => {
        if ((0, shared_1.areValidationErrors)(req, res))
            return;
        return next();
    }
];
exports.videosOverviewValidator = videosOverviewValidator;
function getCommonVideoEditAttributes() {
    return [
        (0, express_validator_1.body)('thumbnailfile')
            .custom((value, { req }) => (0, videos_1.isVideoImageValid)(req.files, 'thumbnailfile')).withMessage('This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
            constants_1.CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')),
        (0, express_validator_1.body)('previewfile')
            .custom((value, { req }) => (0, videos_1.isVideoImageValid)(req.files, 'previewfile')).withMessage('This preview file is not supported or too large. Please, make sure it is of the following type: ' +
            constants_1.CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')),
        (0, express_validator_1.body)('category')
            .optional()
            .customSanitizer(misc_1.toIntOrNull)
            .custom(videos_1.isVideoCategoryValid),
        (0, express_validator_1.body)('licence')
            .optional()
            .customSanitizer(misc_1.toIntOrNull)
            .custom(videos_1.isVideoLicenceValid),
        (0, express_validator_1.body)('language')
            .optional()
            .customSanitizer(misc_1.toValueOrNull)
            .custom(videos_1.isVideoLanguageValid),
        (0, express_validator_1.body)('nsfw')
            .optional()
            .customSanitizer(misc_1.toBooleanOrNull)
            .custom(misc_1.isBooleanValid).withMessage('Should have a valid nsfw boolean'),
        (0, express_validator_1.body)('waitTranscoding')
            .optional()
            .customSanitizer(misc_1.toBooleanOrNull)
            .custom(misc_1.isBooleanValid).withMessage('Should have a valid waitTranscoding boolean'),
        (0, express_validator_1.body)('privacy')
            .optional()
            .customSanitizer(misc_1.toIntOrNull)
            .custom(videos_1.isVideoPrivacyValid),
        (0, express_validator_1.body)('description')
            .optional()
            .customSanitizer(misc_1.toValueOrNull)
            .custom(videos_1.isVideoDescriptionValid),
        (0, express_validator_1.body)('support')
            .optional()
            .customSanitizer(misc_1.toValueOrNull)
            .custom(videos_1.isVideoSupportValid),
        (0, express_validator_1.body)('tags')
            .optional()
            .customSanitizer(misc_1.toValueOrNull)
            .custom(videos_1.areVideoTagsValid)
            .withMessage(`Should have an array of up to ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.TAGS.max} tags between ` +
            `${constants_1.CONSTRAINTS_FIELDS.VIDEOS.TAG.min} and ${constants_1.CONSTRAINTS_FIELDS.VIDEOS.TAG.max} characters each`),
        (0, express_validator_1.body)('commentsEnabled')
            .optional()
            .customSanitizer(misc_1.toBooleanOrNull)
            .custom(misc_1.isBooleanValid).withMessage('Should have commentsEnabled boolean'),
        (0, express_validator_1.body)('downloadEnabled')
            .optional()
            .customSanitizer(misc_1.toBooleanOrNull)
            .custom(misc_1.isBooleanValid).withMessage('Should have downloadEnabled boolean'),
        (0, express_validator_1.body)('originallyPublishedAt')
            .optional()
            .customSanitizer(misc_1.toValueOrNull)
            .custom(videos_1.isVideoOriginallyPublishedAtValid),
        (0, express_validator_1.body)('scheduleUpdate')
            .optional()
            .customSanitizer(misc_1.toValueOrNull),
        (0, express_validator_1.body)('scheduleUpdate.updateAt')
            .optional()
            .custom(misc_1.isDateValid).withMessage('Should have a schedule update date that conforms to ISO 8601'),
        (0, express_validator_1.body)('scheduleUpdate.privacy')
            .optional()
            .customSanitizer(misc_1.toIntOrNull)
            .custom(videos_1.isScheduleVideoUpdatePrivacyValid)
    ];
}
exports.getCommonVideoEditAttributes = getCommonVideoEditAttributes;
const commonVideosFiltersValidator = [
    (0, express_validator_1.query)('categoryOneOf')
        .optional()
        .customSanitizer(core_utils_2.arrayify)
        .custom(search_1.isNumberArray).withMessage('Should have a valid categoryOneOf array'),
    (0, express_validator_1.query)('licenceOneOf')
        .optional()
        .customSanitizer(core_utils_2.arrayify)
        .custom(search_1.isNumberArray).withMessage('Should have a valid licenceOneOf array'),
    (0, express_validator_1.query)('languageOneOf')
        .optional()
        .customSanitizer(core_utils_2.arrayify)
        .custom(search_1.isStringArray).withMessage('Should have a valid languageOneOf array'),
    (0, express_validator_1.query)('privacyOneOf')
        .optional()
        .customSanitizer(core_utils_2.arrayify)
        .custom(search_1.isNumberArray).withMessage('Should have a valid privacyOneOf array'),
    (0, express_validator_1.query)('tagsOneOf')
        .optional()
        .customSanitizer(core_utils_2.arrayify)
        .custom(search_1.isStringArray).withMessage('Should have a valid tagsOneOf array'),
    (0, express_validator_1.query)('tagsAllOf')
        .optional()
        .customSanitizer(core_utils_2.arrayify)
        .custom(search_1.isStringArray).withMessage('Should have a valid tagsAllOf array'),
    (0, express_validator_1.query)('nsfw')
        .optional()
        .custom(search_1.isBooleanBothQueryValid),
    (0, express_validator_1.query)('isLive')
        .optional()
        .customSanitizer(misc_1.toBooleanOrNull)
        .custom(misc_1.isBooleanValid).withMessage('Should have a valid isLive boolean'),
    (0, express_validator_1.query)('filter')
        .optional()
        .custom(videos_1.isVideoFilterValid),
    (0, express_validator_1.query)('include')
        .optional()
        .custom(videos_1.isVideoIncludeValid),
    (0, express_validator_1.query)('isLocal')
        .optional()
        .customSanitizer(misc_1.toBooleanOrNull)
        .custom(misc_1.isBooleanValid).withMessage('Should have a valid isLocal boolean'),
    (0, express_validator_1.query)('hasHLSFiles')
        .optional()
        .customSanitizer(misc_1.toBooleanOrNull)
        .custom(misc_1.isBooleanValid).withMessage('Should have a valid hasHLSFiles boolean'),
    (0, express_validator_1.query)('hasWebtorrentFiles')
        .optional()
        .customSanitizer(misc_1.toBooleanOrNull)
        .custom(misc_1.isBooleanValid).withMessage('Should have a valid hasWebtorrentFiles boolean'),
    (0, express_validator_1.query)('skipCount')
        .optional()
        .customSanitizer(misc_1.toBooleanOrNull)
        .custom(misc_1.isBooleanValid).withMessage('Should have a valid skipCount boolean'),
    (0, express_validator_1.query)('search')
        .optional()
        .custom(misc_1.exists),
    (req, res, next) => {
        var _a;
        if ((0, shared_1.areValidationErrors)(req, res))
            return;
        {
            if (req.query.filter === 'all-local') {
                req.query.include = 1;
                req.query.isLocal = true;
                req.query.privacyOneOf = (0, core_utils_2.getAllPrivacies)();
            }
            else if (req.query.filter === 'all') {
                req.query.include = 1;
                req.query.privacyOneOf = (0, core_utils_2.getAllPrivacies)();
            }
            else if (req.query.filter === 'local') {
                req.query.isLocal = true;
            }
            req.query.filter = undefined;
        }
        const user = (_a = res.locals.oauth) === null || _a === void 0 ? void 0 : _a.token.User;
        if ((!user || user.hasRight(20) !== true)) {
            if (req.query.include || req.query.privacyOneOf) {
                return res.fail({
                    status: 401,
                    message: 'You are not allowed to see all videos.'
                });
            }
        }
        return next();
    }
];
exports.commonVideosFiltersValidator = commonVideosFiltersValidator;
function areErrorsInScheduleUpdate(req, res) {
    if (req.body.scheduleUpdate) {
        if (!req.body.scheduleUpdate.updateAt) {
            logger_1.logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.');
            res.fail({ message: 'Schedule update at is mandatory.' });
            return true;
        }
    }
    return false;
}
function commonVideoChecksPass(parameters) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { req, res, user, videoFileSize, files } = parameters;
        if (areErrorsInScheduleUpdate(req, res))
            return false;
        if (!(yield (0, shared_1.doesVideoChannelOfAccountExist)(req.body.channelId, user, res)))
            return false;
        if (!(0, videos_1.isVideoFileMimeTypeValid)(files)) {
            res.fail({
                status: 415,
                message: 'This file is not supported. Please, make sure it is of the following type: ' +
                    constants_1.CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
            });
            return false;
        }
        if (!(0, videos_1.isVideoFileSizeValid)(videoFileSize.toString())) {
            res.fail({
                status: 413,
                message: 'This file is too large. It exceeds the maximum file size authorized.',
                type: "max_file_size_reached"
            });
            return false;
        }
        if ((yield (0, shared_1.checkUserQuota)(user, videoFileSize, res)) === false)
            return false;
        return true;
    });
}
function isVideoAccepted(req, res, videoFile) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const acceptParameters = {
            videoBody: req.body,
            videoFile,
            user: res.locals.oauth.token.User
        };
        const acceptedResult = yield hooks_1.Hooks.wrapFun(moderation_1.isLocalVideoAccepted, acceptParameters, 'filter:api.video.upload.accept.result');
        if (!acceptedResult || acceptedResult.accepted !== true) {
            logger_1.logger.info('Refused local video.', { acceptedResult, acceptParameters });
            res.fail({
                status: 403,
                message: acceptedResult.errorMessage || 'Refused local video'
            });
            return false;
        }
        return true;
    });
}
exports.isVideoAccepted = isVideoAccepted;
function addDurationToVideo(videoFile) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const duration = yield (0, ffmpeg_1.getVideoStreamDuration)(videoFile.path);
        if (isNaN(duration))
            videoFile.duration = 0;
        else
            videoFile.duration = duration;
    });
}
//# sourceMappingURL=videos.js.map