mixins/uploads.js

"use strict";
/**
 * @module Uploads
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UploadsMixin = void 0;
const helpers_1 = require("../helpers");
const parsers_1 = require("../parsers");
const albums_1 = require("../parsers/albums");
const library_1 = require("../parsers/library");
const uploads_1 = require("../parsers/uploads");
const utils_1 = require("../parsers/utils");
const fs_1 = require("fs");
const path_1 = require("path");
const utf8_1 = __importDefault(require("utf8"));
const axios_1 = __importDefault(require("axios"));
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const UploadsMixin = (Base) => {
    return class UploadsMixin extends Base {
        async getLibraryUploadSongs(options, order) {
            this._checkAuth();
            const { limit = 25, order: _order } = typeof options == 'object' ? options : { limit: options, order: order };
            const endpoint = 'browse';
            const body = {
                browseId: 'FEmusic_library_privately_owned_tracks',
            };
            (0, helpers_1.validateOrderParameters)(_order);
            if (_order) {
                body['params'] = (0, helpers_1.prepareOrderParams)(_order);
            }
            const response = await this._sendRequest(endpoint, body);
            let results = (0, utils_1.findObjectByKey)((0, utils_1.nav)(response, [...parsers_1.SINGLE_COLUMN_TAB, ...parsers_1.SECTION_LIST]), 'itemSectionRenderer');
            results = (0, utils_1.nav)(results, parsers_1.ITEM_SECTION);
            if (!results['musicShelfRenderer']) {
                return [];
            }
            else {
                results = results['musicShelfRenderer'];
            }
            let songs = [];
            songs = [...(0, uploads_1.parseUploadedItems)(results['contents'].slice(1))];
            if ('continuations' in results) {
                const requestFunc = async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams);
                songs = [
                    ...songs,
                    ...(await (0, utils_1.getContinuations)(results, 'musicShelfContinuation', limit - songs.length, requestFunc, uploads_1.parseUploadedItems)),
                ];
            }
            return songs;
        }
        async getLibraryUploadAlbums(options, order) {
            this._checkAuth();
            const { limit = 25, order: _order } = typeof options == 'object' ? options : { limit: options, order: order };
            const body = {
                browseId: 'FEmusic_library_privately_owned_releases',
            };
            (0, helpers_1.validateOrderParameters)(_order);
            if (_order) {
                body['params'] = (0, helpers_1.prepareOrderParams)(_order);
            }
            const endpoint = 'browse';
            const response = await this._sendRequest(endpoint, body);
            return await (0, library_1.parseLibraryAlbums)(response, async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams), limit);
        }
        async getLibraryUploadArtists(options, order) {
            this._checkAuth();
            const { limit = 25, order: _order } = typeof options == 'object' ? options : { limit: options, order: order };
            const body = {
                browseId: 'FEmusic_library_privately_owned_artists',
            };
            (0, helpers_1.validateOrderParameters)(_order);
            if (_order) {
                body['params'] = (0, helpers_1.prepareOrderParams)(_order);
            }
            const endpoint = 'browse';
            const response = await this._sendRequest(endpoint, body);
            return (0, library_1.parseLibraryArtists)(response, async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams), limit);
        }
        /**
         * Returns a list of uploaded tracks for the artist.
         * @param {string} [browseId] Browse id of the upload artist, i.e. from `get_library_upload_songs`.
         * @param {number} [limit=25]  Number of songs to return (increments of 25).
         * @example
         * [
         *   {
         *     "entityId": "t_po_CICr2crg7OWpchDKwoakAQ",
         *     "videoId": "Dtffhy8WJgw",
         *     "title": "Hold Me (Original Mix)",
         *     "artists": [
         *       {
         *         "name": "Jakko",
         *         "id": "FEmusic_library_privately_owned_artist_detaila_po_CICr2crg7OWpchIFamFra28"
         *       }
         *     ],
         *     "album": null,
         *     "likeStatus": "LIKE",
         *     "thumbnails": [...]
         *   }
         * ]
         */
        async getLibraryUploadArtist(browseId, limit = 25) {
            this._checkAuth();
            const body = { browseId: browseId };
            const endpoint = 'browse';
            const response = await this._sendRequest(endpoint, body);
            const results = (0, utils_1.nav)(response, [
                ...parsers_1.SINGLE_COLUMN_TAB,
                ...parsers_1.SECTION_LIST_ITEM,
                ...parsers_1.MUSIC_SHELF,
            ]);
            if (results['contents'].results > 1) {
                results['contents'].pop(0);
            }
            let items = (0, uploads_1.parseUploadedItems)(results['contents']);
            if ('continuations' in results) {
                const requestFunc = async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams);
                const parseFunc = (contents) => (0, uploads_1.parseUploadedItems)(contents);
                items = [
                    ...items,
                    ...(await (0, utils_1.getContinuations)(results, 'musicShelfContinuation', limit, requestFunc, parseFunc)),
                ];
            }
            return items;
        }
        /**
         * Get information and tracks of an album associated with uploaded tracks
         * @param {string} [browseId] Browse id of the upload album, i.e. from `getLibraryUploadSongs`
         * @return Object with title, description, artist, and tracks.
         * @example
         * {
         *   "title": "18 Months",
         *   "type": "Album",
         *   "thumbnails": [...],
         *   "trackCount": 7,
         *   "duration": "24 minutes",
         *   "audioPlaylistId": "MLPRb_po_55chars",
         *   "tracks": [
         *     {
         *       "entityId": "t_po_22chars",
         *       "videoId": "FVo-UZoPygI",
         *       "title": "Feel So Close",
         *       "duration": "4:15",
         *       "duration_seconds": 255,
         *       "artists": None,
         *       "album": {
         *         "name": "18 Months",
         *         "id": "FEmusic_library_privately_owned_release_detailb_po_55chars"
         *       },
         *       "likeStatus": "INDIFFERENT",
         *       "thumbnails": None
         *     }
         *   ]
         * }
         */
        async getLibraryUploadAlbum(browseId) {
            this._checkAuth();
            const body = { browseId: browseId };
            const endpoint = 'browse';
            const response = await this._sendRequest(endpoint, body);
            const album = (0, albums_1.parseAlbumHeader)(response);
            const results = (0, utils_1.nav)(response, [
                ...parsers_1.SINGLE_COLUMN_TAB,
                ...parsers_1.SECTION_LIST_ITEM,
                ...parsers_1.MUSIC_SHELF,
            ]);
            album['tracks'] = (0, uploads_1.parseUploadedItems)(results['contents']);
            album['duration_seconds'] = (0, helpers_1.sumTotalDuration)(album);
            return album;
        }
        /**
         * Uploads a song to YouTube Music.
         * @param filepath Path to the music file (mp3, m4a, wma, flac or ogg).
         * @returns Status String or full response.
         */
        async uploadSong(filepath) {
            this._checkAuth();
            if (!(0, fs_1.existsSync)(filepath)) {
                throw new Error('The provided file does not exist.');
            }
            const supportedFiletypes = ['mp3', 'm4a', 'wma', 'flac', 'ogg'];
            if (supportedFiletypes.includes((0, path_1.extname)(filepath))) {
                throw new Error('The provided file type is not supported by YouTube Music. Supported file types are ' +
                    supportedFiletypes.join(', '));
            }
            const headers = this._headers;
            let uploadUrl = `https://upload.youtube.com/upload/usermusic/http?authuser=${headers['x-goog-authuser']}`;
            const filesize = (0, fs_1.statSync)(filepath).size;
            const body = 'filename=' + utf8_1.default.encode((0, path_1.basename)(filepath));
            delete headers['content-encoding'];
            headers['content-type'] =
                'application/x-www-form-urlencoded;charset=utf-8';
            headers['X-Goog-Upload-Command'] = 'start';
            headers['X-Goog-Upload-Header-Content-Length'] = filesize;
            headers['X-Goog-Upload-Protocol'] = 'resumable';
            const response = await axios_1.default.post(uploadUrl, body, {
                headers: headers,
                proxy: this._proxies,
            });
            headers['X-Goog-Upload-Command'] = 'upload, finalize';
            headers['X-Goog-Upload-Offset'] = '0';
            uploadUrl =
                response.headers['X-Goog-Upload-URL'] ??
                    response.headers['x-goog-upload-url'];
            const data = (0, fs_1.readFileSync)(filepath);
            const response2 = await axios_1.default.post(uploadUrl, data, {
                headers: headers,
                proxy: this._proxies,
            });
            if (response2.status == 200) {
                return 'STATUS_SUCCEEDED';
            }
            else {
                return response2;
            }
        }
        /**
         * Deletes a previously uploaded song or album.
         * @param entityId The entity id of the uploaded song or album,
         * e.g. retrieved from `getLibraryUploadSongs`
         * @return Status String or error.
         */
        async deleteUploadEntity(entityId) {
            this._checkAuth();
            const endpoint = 'music/delete_privately_owned_entity';
            if (entityId.includes('FEmusic_library_privately_owned_release_detail')) {
                entityId = entityId.replace('FEmusic_library_privately_owned_release_detail', '');
            }
            const body = { entityId: entityId };
            const response = await this._sendRequest(endpoint, body);
            if (!response['error']) {
                return 'STATUS_SUCCEEDED';
            }
            else {
                return response['error'];
            }
        }
    };
};
exports.UploadsMixin = UploadsMixin;