From bce0115285dce6caa3f75378e3e9e7b33980e8cb Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Mon, 21 Sep 2020 17:11:16 -0700 Subject: [PATCH 001/209] Generate types from OpenAPI --- .gitignore | 3 +- Public API v1.yaml | 250 ++++++++++++++++----------------- package-lock.json | 97 +++++++++++++ package.json | 5 +- src/app/main/main.component.ts | 2 +- src/app/posts.services.ts | 53 +++---- 6 files changed, 257 insertions(+), 153 deletions(-) diff --git a/.gitignore b/.gitignore index 9d2ac87..32c9fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ backend/subscriptions/playlists/* backend/subscriptions/archives/* backend/*.exe src/assets/default.json +src/api-types backend/appdata/db.json backend/appdata/archives/archive_audio.txt backend/appdata/archives/archive_video.txt @@ -65,4 +66,4 @@ backend/appdata/logs/error.log backend/appdata/users.json backend/users/* backend/appdata/cookies.txt -backend/public \ No newline at end of file +backend/public diff --git a/Public API v1.yaml b/Public API v1.yaml index e82328a..15013e0 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -21,14 +21,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body' + $ref: '#/components/schemas/Mp3DownloadRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200' + $ref: '#/components/schemas/Mp3DownloadResponse' + '500': + description: Server download error security: - Auth query parameter: [] /api/tomp4: @@ -46,14 +48,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_1' + $ref: '#/components/schemas/Mp4DownloadRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_1' + $ref: '#/components/schemas/Mp4DownloadResponse' + '500': + description: Server download error security: - Auth query parameter: [] /api/getMp3s: @@ -69,8 +73,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_2' - requestBody: {} + $ref: '#/components/schemas/GetMp3sResponse' security: - Auth query parameter: [] /api/getMp4s: @@ -86,7 +89,23 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_3' + $ref: '#/components/schemas/GetMp4sResponse' + security: + - Auth query parameter: [] + /api/getAllFiles: + post: + tags: + - files + summary: Get all files + description: Gets all files and playlists stored in the db + operationId: get-getAllFiles + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetAllFilesResponse' security: - Auth query parameter: [] /api/getFile: @@ -100,14 +119,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_2' + $ref: '#/components/schemas/GetFileRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_4' + $ref: '#/components/schemas/GetFileResponse' security: - Auth query parameter: [] /api/enableSharing: @@ -122,7 +141,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_3' + $ref: '#/components/schemas/SharingToggle' responses: '200': description: OK @@ -144,7 +163,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_4' + $ref: '#/components/schemas/SharingToggle' responses: '200': description: OK @@ -718,7 +737,7 @@ paths: type: object properties: user: - $ref: '#/components/schemas/user' + $ref: '#/components/schemas/User' token: type: string permissions: @@ -761,7 +780,7 @@ paths: type: object properties: user: - $ref: '#/components/schemas/user' + $ref: '#/components/schemas/User' description: Use this endpoint to register a user. It will only work if registration is enabled. requestBody: content: @@ -958,7 +977,7 @@ paths: users: type: array items: - $ref: '#/components/schemas/user' + $ref: '#/components/schemas/User' description: 'Gets all users, returns a list of the user objects including their user permissions, videos, playlists, subscriptions, etc.' security: - Auth query parameter: [] @@ -966,7 +985,12 @@ paths: - multi-user mode components: schemas: - body: + FileType: + type: string + enum: + - audio + - video + BaseDownloadRequest: required: - url type: object @@ -975,64 +999,72 @@ components: type: string customQualityConfiguration: type: string + description: Video format code. Overrides other quality options. example: '251' - maxBitrate: - type: string - example: '160' customArgs: type: string + description: Custom command-line arguments for youtubedl. Overrides all other options, except url. customOutput: type: string + description: Custom output filename template. youtubeUsername: type: string + description: Login with this account ID youtubePassword: type: string - inline_response_200: + description: Account password + ui_uid: + type: string + nullable: true + Mp3DownloadRequest: + allOf: + - $ref: '#/components/schemas/BaseDownloadRequest' + - type: object + properties: + maxBitrate: + type: string + description: Specify ffmpeg/avconv audio quality + example: '160' + Mp4DownloadRequest: + allOf: + - $ref: '#/components/schemas/BaseDownloadRequest' + - type: object + properties: + selectedHeight: + type: string + description: Height of the video, if known + example: '1080' + BaseDownloadResponse: required: - - audiopathEncoded - uid type: object properties: uid: type: string file_names: - type: string - audiopathEncoded: - type: string - body_1: - required: - - url - type: object - properties: - url: - type: string - customQualityConfiguration: - type: string - example: 242+251 - selectedHeight: - type: string - example: '1080' - customArgs: - type: string - customOutput: - type: string - youtubeUsername: - type: string - youtubePassword: - type: string - inline_response_200_1: - required: - - uid - - videopathEncoded - type: object - properties: - uid: - type: string - file_names: - type: string - videopathEncoded: - type: string - inline_response_200_2: + nullable: true + type: array + items: + type: string + Mp3DownloadResponse: + allOf: + - $ref: '#/components/schemas/BaseDownloadResponse' + - type: object + required: + - audiopathEncoded + properties: + audiopathEncoded: + type: string + Mp4DownloadResponse: + allOf: + - $ref: '#/components/schemas/BaseDownloadResponse' + - type: object + required: + - videopathEncoded + properties: + videopathEncoded: + type: string + GetMp3sResponse: required: - mp3s - playlists @@ -1041,48 +1073,66 @@ components: mp3s: type: array items: - $ref: '#/components/schemas/inline_response_200_2_mp3s' + $ref: '#/components/schemas/DatabaseFile' playlists: type: array description: All audio playlists items: - $ref: '#/components/schemas/inline_response_200_2_playlists' - inline_response_200_3: + $ref: '#/components/schemas/Playlist' + GetMp4sResponse: required: - mp4s + - playlists type: object properties: mp4s: type: array items: - $ref: '#/components/schemas/inline_response_200_3_mp4s' + $ref: '#/components/schemas/DatabaseFile' playlists: type: array description: All video playlists items: - type: object - body_2: + $ref: '#/components/schemas/Playlist' + GetAllFilesResponse: + required: + - files + - playlists + type: object + properties: + files: + type: array + items: + $ref: '#/components/schemas/DatabaseFile' + playlists: + type: array + description: All video playlists + items: + $ref: '#/components/schemas/Playlist' + GetFileRequest: required: - uid type: object properties: uid: type: string + description: Video UID type: + $ref: '#/components/schemas/FileType' + uuid: type: string - inline_response_200_4: + description: User UID + GetFileResponse: required: - - file - success type: object properties: success: - type: string + type: boolean file: - $ref: '#/components/schemas/inline_response_200_2_mp3s' - body_3: + $ref: '#/components/schemas/DatabaseFile' + SharingToggle: required: - - is_playlist - type - uid type: object @@ -1090,7 +1140,7 @@ components: uid: type: string type: - type: string + $ref: '#/components/schemas/FileType' is_playlist: type: boolean inline_response_200_5: @@ -1100,19 +1150,6 @@ components: properties: success: type: boolean - body_4: - required: - - type - - uid - type: object - properties: - type: - type: string - uid: - type: string - description: uid is either the video uid or the playlist ID - is_playlist: - type: boolean body_5: required: - url @@ -1303,7 +1340,7 @@ components: type: object properties: playlist: - $ref: '#/components/schemas/inline_response_200_2_playlists' + $ref: '#/components/schemas/Playlist' type: type: string success: @@ -1457,7 +1494,7 @@ components: properties: new_config_file: type: object - inline_response_200_2_mp3s: + DatabaseFile: required: - duration - id @@ -1497,7 +1534,7 @@ components: type: string sharingEnabled: type: boolean - inline_response_200_2_playlists: + Playlist: required: - fileNames - id @@ -1515,45 +1552,6 @@ components: type: string thumbnailURL: type: string - inline_response_200_3_mp4s: - required: - - duration - - id - - isAudio - - path - - size - - thumbnailURL - - title - - uid - - upload_date - - uploader - - url - type: object - properties: - id: - type: string - title: - type: string - thumbnailURL: - type: string - isAudio: - type: boolean - duration: - type: number - url: - type: string - uploader: - type: string - size: - type: number - path: - type: string - upload_date: - type: string - uid: - type: string - sharingEnabled: - type: boolean inline_response_200_6_new_sub: required: - id @@ -1633,7 +1631,7 @@ components: type: string type: type: string - user: + User: title: user type: object properties: diff --git a/package-lock.json b/package-lock.json index 08cd667..2cfdacc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9618,6 +9618,76 @@ "is-wsl": "^2.1.1" } }, + "openapi-typescript-codegen": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.4.11.tgz", + "integrity": "sha512-KWhJE4xlFXDd7sNyEiJeUx7+H1hqQ+WETzwrcXCE+CNR+pmfKkOH87fjPRUPu1TY3hSgDgZ6ex7F3lWLbirvMQ==", + "dev": true, + "requires": { + "camelcase": "6.0.0", + "commander": "6.1.0", + "handlebars": "4.7.6", + "js-yaml": "3.14.0", + "mkdirp": "1.0.4", + "path": "0.12.7", + "rimraf": "3.0.2" + }, + "dependencies": { + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "commander": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", + "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==", + "dev": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -10048,6 +10118,33 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "dev": true, + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + } + } + }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", diff --git a/package.json b/package.json index 14aa13e..0e1ff92 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", - "electron": "ng build --base-href ./ && electron ." + "electron": "ng build --base-href ./ && electron .", + "generate": "openapi --input ./\"Public API v1.yaml\" --output ./src/api-types --exportCore false --exportServices false --exportModels true", + "prepare": "npm run generate" }, "engines": { "node": "12.3.1", @@ -66,6 +68,7 @@ "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", + "openapi-typescript-codegen": "^0.4.11", "protractor": "~5.1.2", "ts-node": "~3.0.4", "tslint": "~5.3.2" diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index b444e11..3625449 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -63,7 +63,7 @@ export class MainComponent implements OnInit { youtubeUsername = null; youtubePassword = null; urlError = false; - path = ''; + path: string | string[] = ''; url = ''; exists = ''; percentDownloaded: number; diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 416fd2d..2153f73 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -8,9 +8,9 @@ import { THEMES_CONFIG } from '../themes'; import { Router, CanActivate } from '@angular/router'; import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; -import { v4 as uuid } from 'uuid'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; +import type { FileType, GetAllFilesResponse, GetFileRequest, GetFileResponse, GetMp3sResponse, GetMp4sResponse, Mp3DownloadRequest, Mp3DownloadResponse, Mp4DownloadRequest, Mp4DownloadResponse } from 'api-types'; @Injectable() export class PostsService implements CanActivate { @@ -25,7 +25,9 @@ export class PostsService implements CanActivate { // auth auth_token = '4241b401-7236-493e-92b5-b72696b9d853'; session_id = null; - httpOptions = null; + httpOptions: { + params: HttpParams + }; http_params: string = null; unauthorized = false; @@ -155,27 +157,29 @@ export class PostsService implements CanActivate { } // tslint:disable-next-line: max-line-length - makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) { - return this.http.post(this.path + 'tomp3', {url: url, - maxBitrate: selectedQuality, - customQualityConfiguration: customQualityConfiguration, - customArgs: customArgs, - customOutput: customOutput, - youtubeUsername: youtubeUsername, - youtubePassword: youtubePassword, - ui_uid: ui_uid}, this.httpOptions); + makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid: string = null) { + const body: Mp3DownloadRequest = {url: url, + maxBitrate: selectedQuality, + customQualityConfiguration: customQualityConfiguration, + customArgs: customArgs, + customOutput: customOutput, + youtubeUsername: youtubeUsername, + youtubePassword: youtubePassword, + ui_uid: ui_uid} + return this.http.post(this.path + 'tomp3', body, this.httpOptions); } // tslint:disable-next-line: max-line-length makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) { - return this.http.post(this.path + 'tomp4', {url: url, - selectedHeight: selectedQuality, - customQualityConfiguration: customQualityConfiguration, - customArgs: customArgs, - customOutput: customOutput, - youtubeUsername: youtubeUsername, - youtubePassword: youtubePassword, - ui_uid: ui_uid}, this.httpOptions); + const body: Mp4DownloadRequest = {url: url, + selectedHeight: selectedQuality, + customQualityConfiguration: customQualityConfiguration, + customArgs: customArgs, + customOutput: customOutput, + youtubeUsername: youtubeUsername, + youtubePassword: youtubePassword, + ui_uid: ui_uid} + return this.http.post(this.path + 'tomp4', body, this.httpOptions); } killAllDownloads() { @@ -207,19 +211,20 @@ export class PostsService implements CanActivate { } getMp3s() { - return this.http.get(this.path + 'getMp3s', this.httpOptions); + return this.http.get(this.path + 'getMp3s', this.httpOptions); } getMp4s() { - return this.http.get(this.path + 'getMp4s', this.httpOptions); + return this.http.get(this.path + 'getMp4s', this.httpOptions); } - getFile(uid, type, uuid = null) { - return this.http.post(this.path + 'getFile', {uid: uid, type: type, uuid: uuid}, this.httpOptions); + getFile(uid: string, type: FileType, uuid: string = null) { + const body: GetFileRequest = {uid: uid, type: type, uuid: uuid}; + return this.http.post(this.path + 'getFile', body, this.httpOptions); } getAllFiles() { - return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions); + return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions); } downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null, From b667e1dc7970bf57ca17095b6e3be77d0e3063ec Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Thu, 24 Sep 2020 14:16:13 -0700 Subject: [PATCH 002/209] Add additional named types --- Public API v1.yaml | 261 +++++++++++++++++++++----------------- src/app/posts.services.ts | 17 ++- 2 files changed, 156 insertions(+), 122 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 15013e0..2289c3b 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -395,7 +395,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_14' + $ref: '#/components/schemas/DeleteMp3Mp4Request' responses: '200': description: OK @@ -436,7 +436,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_16' + $ref: '#/components/schemas/DeleteFileRequest' responses: '200': description: OK @@ -581,7 +581,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_21' + $ref: '#/components/schemas/DeleteMp3Mp4Request' responses: '200': description: OK @@ -637,33 +637,7 @@ paths: content: application/json: schema: - type: object - properties: - downloads: - type: object - properties: - uid: - type: string - downloading: - type: boolean - complete: - type: boolean - url: - type: string - type: - type: string - percent_complete: - type: string - is_playlist: - type: boolean - timestamp_start: - type: number - timestamp_end: - type: number - fileNames: - type: array - items: - type: string + $ref: '#/components/schemas/GetAllDownloadsResponse' operationId: get-api-downloads description: Retrieves all downloads recorded by the server and their status. security: @@ -678,46 +652,12 @@ paths: content: application/json: schema: - type: object - properties: - download: - type: object - properties: - uid: - type: string - downloading: - type: boolean - complete: - type: boolean - url: - type: string - type: - type: string - percent_complete: - type: string - is_playlist: - type: boolean - timestamp_start: - type: number - timestamp_end: - type: number - fileNames: - type: array - items: - type: string + $ref: '#/components/schemas/GetDownloadResponse' requestBody: content: application/json: schema: - type: object - properties: - session_id: - type: string - download_id: - type: string - required: - - session_id - - download_id + $ref: '#/components/schemas/GetDownloadRequest' description: '' description: "Gets a single download using its download_id and session_id. session_id is the device fingerprint. If none was provided at the time of download, then set session_id is 'undeclared'." security: @@ -809,6 +749,10 @@ paths: responses: '200': description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessObject' requestBody: content: application/json: @@ -816,8 +760,12 @@ paths: type: object properties: change_object: + required: + - uid type: object properties: + uid: + type: string name: type: string role: @@ -839,10 +787,7 @@ paths: content: application/json: schema: - type: object - properties: - success: - type: boolean + $ref: '#/components/schemas/SuccessObject' description: Deletes a user by its uid. requestBody: content: @@ -879,14 +824,14 @@ paths: permissions: type: array items: - type: string + $ref: '#/components/schemas/UserPermission' user: type: object properties: permissions: type: array items: - type: string + $ref: '#/components/schemas/UserPermission' description: Gets the available roles and their permissions security: - Auth query parameter: [] @@ -902,10 +847,7 @@ paths: content: application/json: schema: - type: object - properties: - success: - type: boolean + $ref: '#/components/schemas/SuccessObject' description: "Changes the permissions for a user. Available values for each permission are: `default`, `yes`, and `no`. `default` will use the user's role's default permission." security: - Auth query parameter: [] @@ -913,18 +855,7 @@ paths: content: application/json: schema: - type: object - properties: - user_uid: - type: string - permission: - type: string - new_value: - type: string - required: - - user_uid - - permission - - new_value + $ref: '#/components/schemas/ChangeUserPermissionsRequest' tags: - multi-user mode /api/changeRolePermissions: @@ -937,26 +868,12 @@ paths: content: application/json: schema: - type: object - properties: - success: - type: boolean + $ref: '#/components/schemas/SuccessObject' requestBody: content: application/json: schema: - type: object - properties: - role: - type: string - permission: - type: string - new_value: - type: string - required: - - role - - permission - - new_value + $ref: '#/components/schemas/ChangeRolePermissionsRequest' description: 'Changes the permissions for a role. Available values for each permission are: `yes`, and `no`.' security: - Auth query parameter: [] @@ -985,6 +902,13 @@ paths: - multi-user mode components: schemas: + SuccessObject: + required: + - success + type: object + properties: + success: + type: boolean FileType: type: string enum: @@ -1064,6 +988,33 @@ components: properties: videopathEncoded: type: string + GetDownloadRequest: + type: object + properties: + session_id: + type: string + download_id: + type: string + required: + - session_id + - download_id + GetDownloadResponse: + type: object + properties: + download: + $ref: '#/components/schemas/Download' + nullable: true + GetAllDownloadsResponse: + type: object + properties: + downloads: + type: object + description: Map of Session ID to inner map + additionalProperties: + type: object + description: Map of Download UID to downoad + additionalProperties: + $ref: '#/components/schemas/Download' GetMp3sResponse: required: - mp3s @@ -1370,13 +1321,6 @@ components: type: string type: type: string - body_14: - required: - - uid - type: object - properties: - uid: - type: string body_15: required: - fileNames @@ -1402,7 +1346,7 @@ components: subscriptionPlaylist: type: boolean description: Only used for subscriptions - body_16: + DeleteFileRequest: required: - fileName - type @@ -1411,7 +1355,7 @@ components: fileName: type: string type: - type: string + $ref: '#/components/schemas/FileType' apidownloadArchive_sub: required: - archive_dir @@ -1470,13 +1414,15 @@ components: new_api_key: type: string example: 4241b401-7236-493e-92b5-b72696b9d853 - body_21: + DeleteMp3Mp4Request: required: - uid type: object properties: uid: type: string + blacklistMode: + type: boolean inline_response_200_17: required: - config_file @@ -1552,6 +1498,49 @@ components: type: string thumbnailURL: type: string + Download: + required: + - uid + - ui_uid + - downloading + - complete + - url + - type + - percent_complete + - is_playlist + - timestamp_start + type: object + properties: + uid: + type: string + ui_uid: + type: string + downloading: + type: boolean + complete: + type: boolean + url: + type: string + type: + type: string + percent_complete: + type: number + is_playlist: + type: boolean + timestamp_start: + type: number + timestamp_end: + type: number + filesize: + type: number + nullable: true + error: + type: string + description: Error text, set if download fails. + fileNames: + type: array + items: + type: string inline_response_200_6_new_sub: required: - id @@ -1674,11 +1663,51 @@ components: permissions: type: array items: - type: string + $ref: '#/components/schemas/UserPermission' permission_overrides: type: array items: - type: string + $ref: '#/components/schemas/UserPermission' + UserPermission: + type: string + enum: + - filemanager + - settings + - subscriptions + - sharing + - advanced_download + - downloads_manager + BaseChangePermissionsRequest: + required: + - permission + - new_value + type: object + properties: + permission: + $ref: '#/components/schemas/UserPermission' + new_value: + type: string + enum: + - 'yes' + - 'no' + ChangeUserPermissionsRequest: + allOf: + - $ref: '#/components/schemas/BaseChangePermissionsRequest' + - type: object + properties: + user_uid: + type: string + required: + - user_uid + ChangeRolePermissionsRequest: + allOf: + - $ref: '#/components/schemas/BaseChangePermissionsRequest' + - type: object + properties: + role: + type: string + required: + - role file: title: file type: object diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 2153f73..c6784f5 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -10,7 +10,10 @@ import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; -import type { FileType, GetAllFilesResponse, GetFileRequest, GetFileResponse, GetMp3sResponse, GetMp4sResponse, Mp3DownloadRequest, Mp3DownloadResponse, Mp4DownloadRequest, Mp4DownloadResponse } from 'api-types'; +import type { DeleteMp3Mp4Request, FileType, GetAllFilesResponse, GetFileRequest, GetFileResponse, GetMp3sResponse, GetMp4sResponse, Mp3DownloadRequest, Mp3DownloadResponse, Mp4DownloadRequest, Mp4DownloadResponse } from 'api-types'; +import { GetAllDownloadsResponse } from 'api-types/models/GetAllDownloadsResponse'; +import { GetDownloadResponse } from 'api-types/models/GetDownloadResponse'; +import { GetDownloadRequest } from 'api-types/models/GetDownloadRequest'; @Injectable() export class PostsService implements CanActivate { @@ -203,10 +206,11 @@ export class PostsService implements CanActivate { } deleteFile(uid: string, isAudio: boolean, blacklistMode = false) { + const body: DeleteMp3Mp4Request = {uid: uid, blacklistMode: blacklistMode} if (isAudio) { - return this.http.post(this.path + 'deleteMp3', {uid: uid, blacklistMode: blacklistMode}, this.httpOptions); + return this.http.post(this.path + 'deleteMp3', body, this.httpOptions); } else { - return this.http.post(this.path + 'deleteMp4', {uid: uid, blacklistMode: blacklistMode}, this.httpOptions); + return this.http.post(this.path + 'deleteMp4', body, this.httpOptions); } } @@ -330,12 +334,13 @@ export class PostsService implements CanActivate { // current downloads getCurrentDownloads() { - return this.http.get(this.path + 'downloads', this.httpOptions); + return this.http.get(this.path + 'downloads', this.httpOptions); } // current download - getCurrentDownload(session_id, download_id) { - return this.http.post(this.path + 'download', {download_id: download_id, session_id: session_id}, this.httpOptions); + getCurrentDownload(session_id: string, download_id: string) { + const body: GetDownloadRequest = {download_id: download_id, session_id: session_id}; + return this.http.post(this.path + 'download', body, this.httpOptions); } // clear downloads. download_id is optional, if it exists only 1 download will be cleared From 62c79c267e805ea16682be0aace33c640b12629d Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Thu, 24 Sep 2020 15:15:23 -0700 Subject: [PATCH 003/209] Add additional types, mainly for subscriptions --- Public API v1.yaml | 226 ++++++++++++++------------------------ src/app/posts.services.ts | 56 +++++++--- 2 files changed, 126 insertions(+), 156 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 2289c3b..589e91f 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -184,14 +184,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_5' + $ref: '#/components/schemas/SubscribeRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_6' + $ref: '#/components/schemas/SubscribeResponse' security: - Auth query parameter: [] /api/unsubscribe: @@ -205,14 +205,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_6' + $ref: '#/components/schemas/UnsubscribeRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_7' + $ref: '#/components/schemas/UnsubscribeResponse' security: - Auth query parameter: [] /api/deleteSubscriptionFile: @@ -226,14 +226,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_7' + $ref: '#/components/schemas/DeleteSubscriptionFileRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_8' + $ref: '#/components/schemas/SuccessObject' '500': description: Internal Server Error security: @@ -249,14 +249,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_8' + $ref: '#/components/schemas/GetSubscriptionRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_9' + $ref: '#/components/schemas/GetSubscriptionResponse' security: - Auth query parameter: [] /api/downloadVideosForSubscription: @@ -270,14 +270,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_9' + $ref: '#/components/schemas/DownloadVideosForSubscriptionRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_10' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/getAllSubscriptions: @@ -297,7 +297,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_11' + $ref: '#/components/schemas/GetAllSubscriptionsResponse' security: - Auth query parameter: [] /api/createPlaylist: @@ -360,7 +360,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/deletePlaylist: @@ -457,6 +457,8 @@ paths: responses: '200': description: The archive text file is sent as a response + '404': + description: If the archive dir is not found, 404 is sent as a response security: - Auth query parameter: [] /api/updaterStatus: @@ -1094,16 +1096,11 @@ components: $ref: '#/components/schemas/FileType' is_playlist: type: boolean - inline_response_200_5: - required: - - success - type: object - properties: - success: - type: boolean - body_5: + SubscribeRequest: required: + - name - url + - streamingOnly type: object properties: name: @@ -1114,46 +1111,32 @@ components: type: string streamingOnly: type: boolean - inline_response_200_6: + audioOnly: + type: boolean + customArgs: + type: string + customFileOutput: + type: string + SubscribeResponse: required: - new_sub type: object properties: new_sub: - $ref: '#/components/schemas/inline_response_200_6_new_sub' + $ref: '#/components/schemas/Subscription' error: type: string - apiunsubscribe_sub: - required: - - id - - name - - url - - videos - type: object - properties: - name: - type: string - url: - type: string - id: - type: string - streamingOnly: - type: boolean - videos: - type: array - items: - type: object - body_6: + UnsubscribeRequest: required: - sub type: object properties: sub: - $ref: '#/components/schemas/apiunsubscribe_sub' + $ref: '#/components/schemas/SubscriptionRequestData' deleteMode: type: boolean description: Defaults to false - inline_response_200_7: + UnsubscribeResponse: required: - success type: object @@ -1162,30 +1145,7 @@ components: type: boolean error: type: string - apideleteSubscriptionFile_sub: - required: - - id - - isPlaylist - - name - - url - - videos - type: object - properties: - name: - type: string - url: - type: string - id: - type: string - streamingOnly: - type: boolean - isPlaylist: - type: boolean - videos: - type: array - items: - type: object - body_7: + DeleteSubscriptionFileRequest: required: - file - sub @@ -1193,50 +1153,41 @@ components: properties: file: type: string + file_uid: + type: string sub: - $ref: '#/components/schemas/apideleteSubscriptionFile_sub' + $ref: '#/components/schemas/SubscriptionRequestData' deleteForever: type: boolean description: 'If true, does not remove id from archive. Only valid if youtube-dl archive is enabled in settings.' - inline_response_200_8: - type: object - properties: - success: - type: boolean - body_8: + GetSubscriptionRequest: required: - - subID + - id type: object properties: - subID: + id: type: string - inline_response_200_9: + description: Subscription ID + GetSubscriptionResponse: required: - files - subscription type: object properties: subscription: - $ref: '#/components/schemas/inline_response_200_9_subscription' + $ref: '#/components/schemas/Subscription' files: type: array items: type: object - body_9: + DownloadVideosForSubscriptionRequest: required: - subID type: object properties: subID: type: string - inline_response_200_10: - required: - - success - type: object - properties: - success: - type: number - inline_response_200_11: + GetAllSubscriptionsResponse: required: - subscriptions type: object @@ -1244,7 +1195,7 @@ components: subscriptions: type: array items: - $ref: '#/components/schemas/inline_response_200_11_subscriptions' + $ref: '#/components/schemas/Subscription' body_10: required: - fileNames @@ -1356,20 +1307,18 @@ components: type: string type: $ref: '#/components/schemas/FileType' - apidownloadArchive_sub: - required: - - archive_dir - type: object - properties: - archive_dir: - type: string - body_17: + DownloadArchiveRequest: required: - sub type: object properties: sub: - $ref: '#/components/schemas/apidownloadArchive_sub' + required: + - archive_dir + type: object + properties: + archive_dir: + type: string inline_response_200_14: required: - details @@ -1541,11 +1490,32 @@ components: type: array items: type: string - inline_response_200_6_new_sub: + SubscriptionRequestData: + required: + - id + - name + type: object + properties: + name: + type: string + id: + type: string + type: + $ref: '#/components/schemas/FileType' + isPlaylist: + type: boolean + archive: + type: string + Subscription: required: - id - name - url + - type + - user_uid + - streamingOnly + - isPlaylist + - videos type: object properties: name: @@ -1554,53 +1524,27 @@ components: type: string id: type: string + type: + $ref: '#/components/schemas/FileType' + user_uid: + type: string + nullable: true streamingOnly: type: boolean + isPlaylist: + type: boolean + archive: + type: string timerange: type: string - inline_response_200_9_subscription: - required: - - archive - - id - - isPlaylist - - name - - url - type: object - properties: - name: + custom_args: type: string - url: - type: string - id: - type: string - streamingOnly: - type: boolean - isPlaylist: - type: boolean - archive: - type: string - inline_response_200_11_subscriptions: - required: - - archive - - id - - isPlaylist - - name - - streamingOnly - - url - type: object - properties: - name: - type: string - url: - type: string - id: - type: string - streamingOnly: - type: boolean - isPlaylist: - type: boolean - archive: + custom_output: type: string + videos: + type: array + items: + type: object inline_response_200_12_new_playlist: required: - fileNames diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index c6784f5..92b2b43 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -10,10 +10,32 @@ import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; -import type { DeleteMp3Mp4Request, FileType, GetAllFilesResponse, GetFileRequest, GetFileResponse, GetMp3sResponse, GetMp4sResponse, Mp3DownloadRequest, Mp3DownloadResponse, Mp4DownloadRequest, Mp4DownloadResponse } from 'api-types'; -import { GetAllDownloadsResponse } from 'api-types/models/GetAllDownloadsResponse'; -import { GetDownloadResponse } from 'api-types/models/GetDownloadResponse'; -import { GetDownloadRequest } from 'api-types/models/GetDownloadRequest'; +import { + DeleteMp3Mp4Request, + DeleteSubscriptionFileRequest, + FileType, + GetAllDownloadsResponse, + GetAllFilesResponse, + GetAllSubscriptionsResponse, + GetDownloadResponse, + GetDownloadRequest, + GetFileRequest, + GetFileResponse, + GetMp3sResponse, + GetMp4sResponse, + GetSubscriptionRequest, + GetSubscriptionResponse, + Mp3DownloadRequest, + Mp3DownloadResponse, + Mp4DownloadRequest, + Mp4DownloadResponse, + SubscribeRequest, + SubscribeResponse, + SubscriptionRequestData, + SuccessObject, + UnsubscribeRequest, + UnsubscribeResponse +} from 'api-types'; @Injectable() export class PostsService implements CanActivate { @@ -306,30 +328,34 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions); } - createSubscription(url, name, timerange = null, streamingOnly = false, audioOnly = false, customArgs = null, customFileOutput = null) { - return this.http.post(this.path + 'subscribe', {url: url, name: name, timerange: timerange, streamingOnly: streamingOnly, - audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}, this.httpOptions); + createSubscription(url: string, name: string, timerange: string = null, streamingOnly = false, audioOnly = false, customArgs: string = null, customFileOutput: string = null) { + const body: SubscribeRequest = {url: url, name: name, timerange: timerange, streamingOnly: streamingOnly, + audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}; + return this.http.post(this.path + 'subscribe', body, this.httpOptions); } updateSubscription(subscription) { return this.http.post(this.path + 'updateSubscription', {subscription: subscription}, this.httpOptions); } - unsubscribe(sub, deleteMode = false) { - return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions) + unsubscribe(sub: SubscriptionRequestData, deleteMode = false) { + const body: UnsubscribeRequest = {sub: sub, deleteMode: deleteMode}; + return this.http.post(this.path + 'unsubscribe', body, this.httpOptions) } - deleteSubscriptionFile(sub, file, deleteForever, file_uid) { - return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever, - file_uid: file_uid}, this.httpOptions) + deleteSubscriptionFile(sub: SubscriptionRequestData, file: string, deleteForever: boolean, file_uid: string) { + const body: DeleteSubscriptionFileRequest = {sub: sub, file: file, deleteForever: deleteForever, + file_uid: file_uid}; + return this.http.post(this.path + 'deleteSubscriptionFile', body, this.httpOptions) } - getSubscription(id) { - return this.http.post(this.path + 'getSubscription', {id: id}, this.httpOptions); + getSubscription(id: string) { + const body: GetSubscriptionRequest = {id: id}; + return this.http.post(this.path + 'getSubscription', body, this.httpOptions); } getAllSubscriptions() { - return this.http.post(this.path + 'getAllSubscriptions', {}, this.httpOptions); + return this.http.post(this.path + 'getAllSubscriptions', {}, this.httpOptions); } // current downloads From 4d74c375f4c5ad188b28e3ae243abab158987244 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Thu, 24 Sep 2020 15:39:37 -0700 Subject: [PATCH 004/209] Add playlist types --- Public API v1.yaml | 114 +++++++++++++++++------------ src/app/main/main.component.ts | 5 +- src/app/player/player.component.ts | 5 +- src/app/posts.services.ts | 60 +++++++++------ 4 files changed, 112 insertions(+), 72 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 589e91f..96ba54d 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -148,7 +148,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/disableSharing: @@ -170,7 +170,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/subscribe: @@ -311,14 +311,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_10' + $ref: '#/components/schemas/CreatePlaylistRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_12' + $ref: '#/components/schemas/CreatePlaylistResponse' security: - Auth query parameter: [] /api/getPlaylist: @@ -332,28 +332,49 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_11' + $ref: '#/components/schemas/GetPlaylistRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_13' + $ref: '#/components/schemas/GetPlaylistResponse' security: - Auth query parameter: [] /api/updatePlaylist: post: tags: - playlists - summary: Update playlist files - description: Updates the list of filenames in the playlist object + summary: Update playlist + description: Updates the playlist object operationId: post-api-updatePlaylist requestBody: content: application/json: schema: - $ref: '#/components/schemas/body_12' + $ref: '#/components/schemas/UpdatePlaylistRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessObject' + security: + - Auth query parameter: [] + /api/updatePlaylistFiles: + post: + tags: + - playlists + summary: Update playlist files + description: Updates the list of filenames in the playlist object + operationId: post-api-updatePlaylistFiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePlaylistFilesRequest' responses: '200': description: OK @@ -374,14 +395,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_13' + $ref: '#/components/schemas/DeletePlaylistRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/deleteMp4: @@ -495,7 +516,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/isPinSet: @@ -532,7 +553,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/setPin: @@ -552,7 +573,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] description: '' @@ -625,7 +646,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_5' + $ref: '#/components/schemas/SuccessObject' security: - Auth query parameter: [] /api/downloads: @@ -1196,12 +1217,13 @@ components: type: array items: $ref: '#/components/schemas/Subscription' - body_10: + CreatePlaylistRequest: required: - fileNames - playlistName - thumbnailURL - type + - duration type: object properties: playlistName: @@ -1211,30 +1233,33 @@ components: items: type: string type: - type: string + $ref: '#/components/schemas/FileType' thumbnailURL: type: string - inline_response_200_12: + duration: + type: number + CreatePlaylistResponse: required: - new_playlist - success type: object properties: new_playlist: - $ref: '#/components/schemas/inline_response_200_12_new_playlist' + $ref: '#/components/schemas/Playlist' success: type: boolean - body_11: + GetPlaylistRequest: required: - playlistID - - type type: object properties: playlistID: type: string type: + $ref: '#/components/schemas/FileType' + uuid: type: string - inline_response_200_13: + GetPlaylistResponse: required: - playlist - success @@ -1244,10 +1269,17 @@ components: playlist: $ref: '#/components/schemas/Playlist' type: - type: string + $ref: '#/components/schemas/FileType' success: type: boolean - body_12: + UpdatePlaylistRequest: + required: + - playlist + type: object + properties: + playlist: + $ref: '#/components/schemas/Playlist' + UpdatePlaylistFilesRequest: required: - fileNames - playlistID @@ -1261,8 +1293,8 @@ components: items: type: string type: - type: string - body_13: + $ref: '#/components/schemas/FileType' + DeletePlaylistRequest: required: - playlistID - type @@ -1271,7 +1303,7 @@ components: playlistID: type: string type: - type: string + $ref: '#/components/schemas/FileType' body_15: required: - fileNames @@ -1435,6 +1467,9 @@ components: - id - name - thumbnailURL + - type + - registered + - duration type: object properties: name: @@ -1447,6 +1482,12 @@ components: type: string thumbnailURL: type: string + type: + $ref: '#/components/schemas/FileType' + registered: + type: number + duration: + type: number Download: required: - uid @@ -1545,25 +1586,6 @@ components: type: array items: type: object - inline_response_200_12_new_playlist: - required: - - fileNames - - id - - name - - thumbnailURL - - type - type: object - properties: - name: - type: string - fileNames: - type: string - id: - type: string - thumbnailURL: - type: string - type: - type: string User: title: user type: object diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 3625449..28e672c 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -20,6 +20,7 @@ import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.com import { Platform } from '@angular/cdk/platform'; import { v4 as uuid } from 'uuid'; import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; +import type { FileType } from 'api-types'; export let audioFilesMouseHovering = false; export let videoFilesMouseHovering = false; @@ -453,7 +454,7 @@ export class MainComponent implements OnInit { } public removePlaylistMp3(playlistID, index) { - this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => { + this.postsService.removePlaylist(playlistID, 'audio' as FileType).subscribe(res => { if (res['success']) { this.playlists.audio.splice(index, 1); this.openSnackBar('Playlist successfully removed.', ''); @@ -472,7 +473,7 @@ export class MainComponent implements OnInit { } public removePlaylistMp4(playlistID, index) { - this.postsService.removePlaylist(playlistID, 'video').subscribe(res => { + this.postsService.removePlaylist(playlistID, 'video' as FileType).subscribe(res => { if (res['success']) { this.playlists.video.splice(index, 1); this.openSnackBar('Playlist successfully removed.', ''); diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 3640d7ad..79eb371 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -7,6 +7,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { InputDialogComponent } from 'app/input-dialog/input-dialog.component'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { ShareMediaDialogComponent } from '../dialogs/share-media-dialog/share-media-dialog.component'; +import type { FileType } from 'api-types'; export interface IMedia { title: string; @@ -360,7 +361,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { // Eventually do additional checks on name if (name) { const fileNames = this.getFileNames(); - this.postsService.createPlaylist(name, fileNames, this.type, null).subscribe(res => { + this.postsService.createPlaylist(name, fileNames, this.type as FileType, null).subscribe(res => { if (res['success']) { dialogRef.close(); const new_playlist = res['new_playlist']; @@ -401,7 +402,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { updatePlaylist() { const fileNames = this.getFileNames(); this.playlist_updating = true; - this.postsService.updatePlaylistFiles(this.id, fileNames, this.type).subscribe(res => { + this.postsService.updatePlaylistFiles(this.id, fileNames, this.type as FileType).subscribe(res => { this.playlist_updating = false; if (res['success']) { const fileNamesEncoded = fileNames.join('|nvr|'); diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 92b2b43..c7e7322 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -11,7 +11,10 @@ import { BehaviorSubject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; import { + CreatePlaylistRequest, + CreatePlaylistResponse, DeleteMp3Mp4Request, + DeletePlaylistRequest, DeleteSubscriptionFileRequest, FileType, GetAllDownloadsResponse, @@ -23,18 +26,24 @@ import { GetFileResponse, GetMp3sResponse, GetMp4sResponse, + GetPlaylistRequest, + GetPlaylistResponse, GetSubscriptionRequest, GetSubscriptionResponse, Mp3DownloadRequest, Mp3DownloadResponse, Mp4DownloadRequest, Mp4DownloadResponse, + Playlist, + SharingToggle, SubscribeRequest, SubscribeResponse, SubscriptionRequestData, SuccessObject, UnsubscribeRequest, - UnsubscribeResponse + UnsubscribeResponse, + UpdatePlaylistFilesRequest, + UpdatePlaylistRequest, } from 'api-types'; @Injectable() @@ -293,39 +302,46 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'generateNewAPIKey', {}, this.httpOptions); } - enableSharing(uid, type, is_playlist) { - return this.http.post(this.path + 'enableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions); + enableSharing(uid: string, type: FileType, is_playlist: boolean) { + const body: SharingToggle = {uid: uid, type: type, is_playlist: is_playlist}; + return this.http.post(this.path + 'enableSharing', body, this.httpOptions); } - disableSharing(uid, type, is_playlist) { - return this.http.post(this.path + 'disableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions); + disableSharing(uid: string, type: FileType, is_playlist: boolean) { + const body: SharingToggle = {uid: uid, type: type, is_playlist: is_playlist}; + return this.http.post(this.path + 'disableSharing', body, this.httpOptions); } - createPlaylist(playlistName, fileNames, type, thumbnailURL, duration = null) { - return this.http.post(this.path + 'createPlaylist', {playlistName: playlistName, - fileNames: fileNames, - type: type, - thumbnailURL: thumbnailURL, - duration: duration}, this.httpOptions); + createPlaylist(playlistName: string, fileNames: string[], type: FileType, thumbnailURL: string, duration: number = null) { + const body: CreatePlaylistRequest = {playlistName: playlistName, + fileNames: fileNames, + type: type, + thumbnailURL: thumbnailURL, + duration: duration}; + return this.http.post(this.path + 'createPlaylist', body, this.httpOptions); } - getPlaylist(playlistID, type, uuid = null) { - return this.http.post(this.path + 'getPlaylist', {playlistID: playlistID, - type: type, uuid: uuid}, this.httpOptions); + getPlaylist(playlistID: string, type: FileType, uuid: string = null) { + const body: GetPlaylistRequest = {playlistID: playlistID, + type: type, uuid: uuid}; + return this.http.post(this.path + 'getPlaylist', body, this.httpOptions); } - updatePlaylist(playlist) { - return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions); + updatePlaylist(playlist: Playlist) { + const body: UpdatePlaylistRequest = {playlist: playlist}; + return this.http.post(this.path + 'updatePlaylist', body, this.httpOptions); } - updatePlaylistFiles(playlistID, fileNames, type) { - return this.http.post(this.path + 'updatePlaylistFiles', {playlistID: playlistID, - fileNames: fileNames, - type: type}, this.httpOptions); + updatePlaylistFiles(playlistID: string, fileNames: string[], type: FileType) { + const body: UpdatePlaylistFilesRequest = {playlistID: playlistID, + fileNames: fileNames, + type: type}; + return this.http.post(this.path + 'updatePlaylistFiles', body, this.httpOptions); } - removePlaylist(playlistID, type) { - return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions); + removePlaylist(playlistID: string, type: FileType) { + const body: DeletePlaylistRequest = {playlistID: playlistID, type: type}; + return this.http.post(this.path + 'deletePlaylist', body, this.httpOptions); } createSubscription(url: string, name: string, timerange: string = null, streamingOnly = false, audioOnly = false, customArgs: string = null, customFileOutput: string = null) { From fe7a3075d6dca48058518815e1f37b7fa216e237 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Fri, 25 Sep 2020 10:26:05 -0700 Subject: [PATCH 005/209] Rename last few active API routes --- Public API v1.yaml | 75 +++++++++++-------- .../recent-videos/recent-videos.component.ts | 9 ++- .../update-progress-dialog.component.ts | 3 +- src/app/main/main.component.ts | 8 +- src/app/player/player.component.ts | 8 +- src/app/posts.services.ts | 72 +++++++++++------- .../subscription/subscription.component.ts | 3 +- 7 files changed, 106 insertions(+), 72 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 96ba54d..e508fd5 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -437,11 +437,7 @@ paths: content: application/json: schema: - type: object - properties: {} - application/xml: - schema: - $ref: '#/components/schemas/body_15' + $ref: '#/components/schemas/DownloadFileRequest' responses: '200': description: 'The file itself is in the response, as well as an options object.' @@ -474,7 +470,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_17' + $ref: '#/components/schemas/DownloadArchiveRequest' responses: '200': description: The archive text file is sent as a response @@ -495,7 +491,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_14' + $ref: '#/components/schemas/UpdaterStatus' security: - Auth query parameter: [] /api/updateServer: @@ -509,7 +505,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_18' + $ref: '#/components/schemas/UpdateServerRequest' responses: '200': description: OK @@ -590,7 +586,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_16' + $ref: '#/components/schemas/GenerateNewApiKeyResponse' security: - Auth query parameter: [] /api/deleteMp3: @@ -627,7 +623,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/inline_response_200_17' + $ref: '#/components/schemas/ConfigResponse' security: - Auth query parameter: [] /api/setConfig: @@ -639,7 +635,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/body_22' + $ref: '#/components/schemas/SetConfigRequest' responses: '200': description: OK @@ -647,6 +643,8 @@ paths: application/json: schema: $ref: '#/components/schemas/SuccessObject' + '404': + description: Tried to save invalid config file. security: - Auth query parameter: [] /api/downloads: @@ -937,6 +935,13 @@ components: enum: - audio - video + Config: + required: + - YoutubeDLMaterial + type: object + properties: + YoutubeDLMaterial: + type: object BaseDownloadRequest: required: - url @@ -1304,29 +1309,33 @@ components: type: string type: $ref: '#/components/schemas/FileType' - body_15: + DownloadFileRequest: required: - fileNames - type type: object properties: fileNames: - type: array - description: Array of 1 or more files to download - items: - type: string + oneOf: + - type: string + - type: array + description: Array of 1 or more files to download + items: + type: string zip_mode: type: boolean type: - type: string + $ref: '#/components/schemas/FileType' outputName: type: string fullPathProvided: + type: boolean + uuid: type: string subscriptionName: type: boolean description: Only used for subscriptions - subscriptionPlaylist: + subPlaylist: type: boolean description: Only used for subscriptions DeleteFileRequest: @@ -1351,7 +1360,7 @@ components: properties: archive_dir: type: string - inline_response_200_14: + UpdaterStatus: required: - details - updating @@ -1361,7 +1370,11 @@ components: type: boolean details: type: string - body_18: + error: + type: boolean + UpdateServerRequest: + required: + - tag type: object properties: tag: @@ -1387,7 +1400,7 @@ components: properties: unhashed_pin: type: string - inline_response_200_16: + GenerateNewApiKeyResponse: required: - new_api_key type: object @@ -1404,23 +1417,23 @@ components: type: string blacklistMode: type: boolean - inline_response_200_17: + ConfigResponse: required: - config_file - success type: object properties: config_file: - type: object + $ref: '#/components/schemas/Config' success: type: boolean - body_22: + SetConfigRequest: required: - new_config_file type: object properties: new_config_file: - type: object + $ref: '#/components/schemas/Config' DatabaseFile: required: - duration @@ -1621,7 +1634,7 @@ components: subscriptions: type: array items: - $ref: '#/components/schemas/inline_response_200_9_subscription' + $ref: '#/components/schemas/Subscription' created: type: number role: @@ -1643,6 +1656,11 @@ components: - sharing - advanced_download - downloads_manager + YesNo: + type: string + enum: + - 'yes' + - 'no' BaseChangePermissionsRequest: required: - permission @@ -1652,10 +1670,7 @@ components: permission: $ref: '#/components/schemas/UserPermission' new_value: - type: string - enum: - - 'yes' - - 'no' + $ref: '#/components/schemas/YesNo' ChangeUserPermissionsRequest: allOf: - $ref: '#/components/schemas/BaseChangePermissionsRequest' diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 8333f79..11a6dcc 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { Router } from '@angular/router'; +import type { FileType } from '../../../api-types'; @Component({ selector: 'app-recent-videos', @@ -161,7 +162,7 @@ export class RecentVideosComponent implements OnInit { // normal subscriptions !new_tab ? this.router.navigate(['/player', {fileNames: file.id, type: file.isAudio ? 'audio' : 'video', subscriptionName: sub.name, - subPlaylist: sub.isPlaylist}]) + subPlaylist: sub.isPlaylist}]) : window.open(`/#/player;fileNames=${file.id};type=${file.isAudio ? 'audio' : 'video'};subscriptionName=${sub.name};subPlaylist=${sub.isPlaylist}`); } } else { @@ -186,7 +187,7 @@ export class RecentVideosComponent implements OnInit { } downloadSubscriptionFile(file) { - const type = file.isAudio ? 'audio' : 'video'; + const type = (file.isAudio ? 'audio' : 'video') as FileType; const ext = type === 'audio' ? '.mp3' : '.mp4' const sub = this.postsService.getSubscriptionByID(file.sub_id); this.postsService.downloadFileFromServer(file.id, type, null, null, sub.name, sub.isPlaylist, @@ -199,7 +200,7 @@ export class RecentVideosComponent implements OnInit { } downloadNormalFile(file) { - const type = file.isAudio ? 'audio' : 'video'; + const type = (file.isAudio ? 'audio' : 'video') as FileType; const ext = type === 'audio' ? '.mp3' : '.mp4' const name = file.id; this.downloading_content[type][name] = true; @@ -276,7 +277,7 @@ export class RecentVideosComponent implements OnInit { const result = b.registered - a.registered; return result; } - + durationStringToNumber(dur_str) { let num_sum = 0; const dur_str_parts = dur_str.split(':'); diff --git a/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts b/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts index fe1fbf4..9ca066f 100644 --- a/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts +++ b/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { MatSnackBar } from '@angular/material/snack-bar'; +import type { UpdaterStatus } from '../../../api-types'; @Component({ selector: 'app-update-progress-dialog', @@ -9,7 +10,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; }) export class UpdateProgressDialogComponent implements OnInit { - updateStatus = null; + updateStatus: UpdaterStatus = null; updateInterval = 250; errored = false; diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 28e672c..c0bb894 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -20,7 +20,7 @@ import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.com import { Platform } from '@angular/cdk/platform'; import { v4 as uuid } from 'uuid'; import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; -import type { FileType } from 'api-types'; +import type { FileType } from '../../api-types'; export let audioFilesMouseHovering = false; export let videoFilesMouseHovering = false; @@ -34,7 +34,7 @@ export interface Download { percent_complete: number; downloading: boolean; is_playlist: boolean; - error: boolean | string; + error?: boolean | string; fileNames?: string[]; complete?: boolean; timestamp_start?: number; @@ -740,7 +740,7 @@ export class MainComponent implements OnInit { downloadAudioFile(name) { this.downloading_content['audio'][name] = true; - this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => { + this.postsService.downloadFileFromServer(name, 'audio' as FileType).subscribe(res => { this.downloading_content['audio'][name] = false; const blob: Blob = res; saveAs(blob, decodeURIComponent(name) + '.mp3'); @@ -757,7 +757,7 @@ export class MainComponent implements OnInit { downloadVideoFile(name) { this.downloading_content['video'][name] = true; - this.postsService.downloadFileFromServer(name, 'video').subscribe(res => { + this.postsService.downloadFileFromServer(name, 'video' as FileType).subscribe(res => { this.downloading_content['video'][name] = false; const blob: Blob = res; saveAs(blob, decodeURIComponent(name) + '.mp4'); diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 79eb371..57e2ea4 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -7,7 +7,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { InputDialogComponent } from 'app/input-dialog/input-dialog.component'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { ShareMediaDialogComponent } from '../dialogs/share-media-dialog/share-media-dialog.component'; -import type { FileType } from 'api-types'; +import type { FileType } from '../../api-types'; export interface IMedia { title: string; @@ -35,7 +35,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { // params fileNames: string[]; - type: string; + type: FileType; id = null; // used for playlists (not subscription) uid = null; // used for non-subscription files (audio, video, playlist) subscriptionName = null; @@ -74,7 +74,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit(): void { this.innerWidth = window.innerWidth; - this.type = this.route.snapshot.paramMap.get('type'); + this.type = this.route.snapshot.paramMap.get('type') as FileType; this.id = this.route.snapshot.paramMap.get('id'); this.uid = this.route.snapshot.paramMap.get('uid'); this.subscriptionName = this.route.snapshot.paramMap.get('subscriptionName'); @@ -160,7 +160,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { if (!this.id) { // regular video/audio file (not playlist) this.fileNames = [this.db_file['id']]; - this.type = this.db_file['isAudio'] ? 'audio' : 'video'; + this.type = (this.db_file['isAudio'] ? 'audio' : 'video') as FileType; if (!already_has_filenames) { this.parseFileNames(); } } } diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index c7e7322..d0fad49 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -10,13 +10,19 @@ import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; -import { +import type { + ChangeRolePermissionsRequest, + ChangeUserPermissionsRequest, + ConfigResponse, CreatePlaylistRequest, CreatePlaylistResponse, DeleteMp3Mp4Request, DeletePlaylistRequest, DeleteSubscriptionFileRequest, + DownloadArchiveRequest, + DownloadFileRequest, FileType, + GenerateNewApiKeyResponse, GetAllDownloadsResponse, GetAllFilesResponse, GetAllSubscriptionsResponse, @@ -35,16 +41,21 @@ import { Mp4DownloadRequest, Mp4DownloadResponse, Playlist, + SetConfigRequest, SharingToggle, SubscribeRequest, SubscribeResponse, SubscriptionRequestData, SuccessObject, + UpdaterStatus, UnsubscribeRequest, UnsubscribeResponse, UpdatePlaylistFilesRequest, UpdatePlaylistRequest, -} from 'api-types'; + UpdateServerRequest, + UserPermission, + YesNo, +} from '../api-types'; @Injectable() export class PostsService implements CanActivate { @@ -224,7 +235,7 @@ export class PostsService implements CanActivate { if (isDevMode()) { return this.http.get('./assets/default.json'); } else { - return this.http.get(this.path + 'config', this.httpOptions); + return this.http.get(this.path + 'config', this.httpOptions); } } @@ -233,7 +244,8 @@ export class PostsService implements CanActivate { } setConfig(config) { - return this.http.post(this.path + 'setConfig', {new_config_file: config}, this.httpOptions); + const body: SetConfigRequest = {new_config_file: config}; + return this.http.post(this.path + 'setConfig', body, this.httpOptions); } deleteFile(uid: string, isAudio: boolean, blacklistMode = false) { @@ -262,19 +274,19 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions); } - downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null, - uid = null, uuid = null, id = null) { - return this.http.post(this.path + 'downloadFile', {fileNames: fileName, - type: type, - zip_mode: Array.isArray(fileName), - outputName: outputName, - fullPathProvided: fullPathProvided, - subscriptionName: subscriptionName, - subPlaylist: subPlaylist, - uuid: uuid, - uid: uid, - id: id - }, + downloadFileFromServer(fileName: string | string[], type: FileType, outputName: string = null, fullPathProvided: boolean = null, subscriptionName: boolean = null, subPlaylist: boolean = null, + uid = null, uuid: string = null, id = null) { + const body: DownloadFileRequest = {fileNames: fileName, + type: type, + zip_mode: Array.isArray(fileName), + outputName: outputName, + fullPathProvided: fullPathProvided, + subscriptionName: subscriptionName, + subPlaylist: subPlaylist, + uuid: uuid, + id: id, + }; + return this.http.post(this.path + 'downloadFile', body, {responseType: 'blob', params: this.httpOptions.params}); } @@ -283,7 +295,8 @@ export class PostsService implements CanActivate { } downloadArchive(sub) { - return this.http.post(this.path + 'downloadArchive', {sub: sub}, {responseType: 'blob', params: this.httpOptions.params}); + const body: DownloadArchiveRequest = {sub: sub}; + return this.http.post(this.path + 'downloadArchive', body, {responseType: 'blob', params: this.httpOptions.params}); } getFileInfo(fileNames, type, urlMode) { @@ -299,7 +312,7 @@ export class PostsService implements CanActivate { } generateNewAPIKey() { - return this.http.post(this.path + 'generateNewAPIKey', {}, this.httpOptions); + return this.http.post(this.path + 'generateNewAPIKey', {}, this.httpOptions); } enableSharing(uid: string, type: FileType, is_playlist: boolean) { @@ -393,12 +406,13 @@ export class PostsService implements CanActivate { } // updates the server to the latest version - updateServer(tag) { - return this.http.post(this.path + 'updateServer', {tag: tag}, this.httpOptions); + updateServer(tag: string) { + const body: UpdateServerRequest = {tag: tag}; + return this.http.post(this.path + 'updateServer', body, this.httpOptions); } getUpdaterStatus() { - return this.http.get(this.path + 'updaterStatus', this.httpOptions); + return this.http.get(this.path + 'updaterStatus', this.httpOptions); } // gets tag of the latest version of youtubedl-material @@ -534,11 +548,11 @@ export class PostsService implements CanActivate { } changeUser(change_obj) { - return this.http.post(this.path + 'updateUser', {change_object: change_obj}, this.httpOptions); + return this.http.post(this.path + 'updateUser', {change_object: change_obj}, this.httpOptions); } deleteUser(uid) { - return this.http.post(this.path + 'deleteUser', {uid: uid}, this.httpOptions); + return this.http.post(this.path + 'deleteUser', {uid: uid}, this.httpOptions); } changeUserPassword(user_uid, new_password) { @@ -553,13 +567,15 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'getRoles', {}, this.httpOptions); } - setUserPermission(user_uid, permission, new_value) { - return this.http.post(this.path + 'changeUserPermissions', {user_uid: user_uid, permission: permission, new_value: new_value}, + setUserPermission(user_uid: string, permission: UserPermission, new_value: YesNo) { + const body: ChangeUserPermissionsRequest = {user_uid: user_uid, permission: permission, new_value: new_value}; + return this.http.post(this.path + 'changeUserPermissions', body, this.httpOptions); } - setRolePermission(role_name, permission, new_value) { - return this.http.post(this.path + 'changeRolePermissions', {role: role_name, permission: permission, new_value: new_value}, + setRolePermission(role_name: string, permission: UserPermission, new_value: YesNo) { + const body: ChangeRolePermissionsRequest = {role: role_name, permission: permission, new_value: new_value}; + return this.http.post(this.path + 'changeRolePermissions', body, this.httpOptions); } diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 794a268..eb2f35b 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -3,6 +3,7 @@ import { PostsService } from 'app/posts.services'; import { ActivatedRoute, Router, ParamMap } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component'; +import type { FileType } from '../../../api-types'; @Component({ selector: 'app-subscription', @@ -151,7 +152,7 @@ export class SubscriptionComponent implements OnInit { } this.downloading = true; - this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => { + this.postsService.downloadFileFromServer(fileNames, 'video' as FileType, this.subscription.name, true).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, this.subscription.name + '.zip'); From 70d1afce762f71684823b9f66c558df83d804e4c Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Fri, 25 Sep 2020 10:46:55 -0700 Subject: [PATCH 006/209] Move user schemas so they can be imported --- Public API v1.yaml | 191 +++++++++++++++++++++----------------- src/app/posts.services.ts | 46 +++++---- 2 files changed, 134 insertions(+), 103 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index e508fd5..70d56c5 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -695,35 +695,13 @@ paths: content: application/json: schema: - type: object - properties: - user: - $ref: '#/components/schemas/User' - token: - type: string - permissions: - type: array - items: - type: string - available_permissions: - type: array - items: - type: string + $ref: '#/components/schemas/LoginResponse' description: Use this method to log into a user using their username and password and receive a jwt auth token so you can send per-user requests. requestBody: content: application/json: schema: - type: object - properties: - userid: - type: string - description: 'This is the username, not the user uid' - password: - type: string - required: - - userid - - password + $ref: '#/components/schemas/LoginRequest' security: - Auth query parameter: [] tags: @@ -738,27 +716,13 @@ paths: content: application/json: schema: - type: object - properties: - user: - $ref: '#/components/schemas/User' + $ref: '#/components/schemas/RegisterResponse' description: Use this endpoint to register a user. It will only work if registration is enabled. requestBody: content: application/json: schema: - type: object - properties: - userid: - type: string - username: - type: string - password: - type: string - required: - - userid - - username - - password + $ref: '#/components/schemas/RegisterRequest' security: - Auth query parameter: [] tags: @@ -778,21 +742,7 @@ paths: content: application/json: schema: - type: object - properties: - change_object: - required: - - uid - type: object - properties: - uid: - type: string - name: - type: string - role: - type: string - required: - - change_object + $ref: '#/components/schemas/UpdateUserRequest' description: Updates certain properties for a user. Only two are possible right now. security: - Auth query parameter: [] @@ -814,12 +764,7 @@ paths: content: application/json: schema: - type: object - properties: - uid: - type: string - required: - - uid + $ref: '#/components/schemas/DeleteUserRequest' security: - Auth query parameter: [] tags: @@ -834,25 +779,7 @@ paths: content: application/json: schema: - type: object - properties: - roles: - type: object - properties: - admin: - type: object - properties: - permissions: - type: array - items: - $ref: '#/components/schemas/UserPermission' - user: - type: object - properties: - permissions: - type: array - items: - $ref: '#/components/schemas/UserPermission' + $ref: '#/components/schemas/GetRolesResponse' description: Gets the available roles and their permissions security: - Auth query parameter: [] @@ -910,12 +837,7 @@ paths: content: application/json: schema: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' + $ref: '#/components/schemas/GetUsersResponse' description: 'Gets all users, returns a list of the user objects including their user permissions, videos, playlists, subscriptions, etc.' security: - Auth query parameter: [] @@ -1695,6 +1617,103 @@ components: properties: id: type: string + RegisterRequest: + required: + - userid + - username + - password + type: object + properties: + userid: + type: string + username: + type: string + password: + type: string + RegisterResponse: + type: object + properties: + user: + $ref: '#/components/schemas/User' + LoginRequest: + required: + - username + - password + type: object + properties: + username: + type: string + password: + type: string + LoginResponse: + type: object + properties: + user: + $ref: '#/components/schemas/User' + token: + type: string + permissions: + type: array + items: + $ref: '#/components/schemas/UserPermission' + available_permissions: + type: array + items: + $ref: '#/components/schemas/UserPermission' + UpdateUserRequest: + required: + - change_object + type: object + properties: + change_object: + required: + - uid + type: object + properties: + uid: + type: string + name: + type: string + role: + type: string + DeleteUserRequest: + required: + - uid + type: object + properties: + uid: + type: string + GetUsersResponse: + required: + - users + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + GetRolesResponse: + required: + - roles + type: object + properties: + roles: + type: object + properties: + admin: + type: object + properties: + permissions: + type: array + items: + $ref: '#/components/schemas/UserPermission' + user: + type: object + properties: + permissions: + type: array + items: + $ref: '#/components/schemas/UserPermission' securitySchemes: Auth query parameter: name: apiKey diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index d0fad49..976298c 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -19,6 +19,7 @@ import type { DeleteMp3Mp4Request, DeletePlaylistRequest, DeleteSubscriptionFileRequest, + DeleteUserRequest, DownloadArchiveRequest, DownloadFileRequest, FileType, @@ -34,13 +35,19 @@ import type { GetMp4sResponse, GetPlaylistRequest, GetPlaylistResponse, + GetRolesResponse, GetSubscriptionRequest, GetSubscriptionResponse, + GetUsersResponse, + LoginRequest, + LoginResponse, Mp3DownloadRequest, Mp3DownloadResponse, Mp4DownloadRequest, Mp4DownloadResponse, Playlist, + RegisterRequest, + RegisterResponse, SetConfigRequest, SharingToggle, SubscribeRequest, @@ -53,6 +60,7 @@ import type { UpdatePlaylistFilesRequest, UpdatePlaylistRequest, UpdateServerRequest, + UpdateUserRequest, UserPermission, YesNo, } from '../api-types'; @@ -444,9 +452,9 @@ export class PostsService implements CanActivate { } // user methods - login(username, password) { - const call = this.http.post(this.path + 'auth/login', {username: username, password: password}, this.httpOptions); - return call; + login(username: string, password: string) { + const body: LoginRequest = {username: username, password: password}; + return this.http.post(this.path + 'auth/login', body, this.httpOptions); } // user methods @@ -481,10 +489,11 @@ export class PostsService implements CanActivate { } // user methods - register(username, password) { - const call = this.http.post(this.path + 'auth/register', {userid: username, - username: username, - password: password}, this.httpOptions); + register(username: string, password: string) { + const body: RegisterRequest = {userid: username, + username: username, + password: password} + const call = this.http.post(this.path + 'auth/register', body, this.httpOptions); return call; } @@ -529,10 +538,11 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'auth/adminExists', {}, this.httpOptions); } - createAdminAccount(password) { - return this.http.post(this.path + 'auth/register', {userid: 'admin', - username: 'admin', - password: password}, this.httpOptions); + createAdminAccount(password: string) { + const body: RegisterRequest = {userid: 'admin', + username: 'admin', + password: password}; + return this.http.post(this.path + 'auth/register', body, this.httpOptions); } checkAdminCreationStatus(force_show = false) { @@ -547,12 +557,14 @@ export class PostsService implements CanActivate { }); } - changeUser(change_obj) { - return this.http.post(this.path + 'updateUser', {change_object: change_obj}, this.httpOptions); + changeUser(change_obj: UpdateUserRequest['change_object']) { + const body: UpdateUserRequest = {change_object: change_obj}; + return this.http.post(this.path + 'updateUser', body, this.httpOptions); } - deleteUser(uid) { - return this.http.post(this.path + 'deleteUser', {uid: uid}, this.httpOptions); + deleteUser(uid: string) { + const body: DeleteUserRequest = {uid: uid}; + return this.http.post(this.path + 'deleteUser', body, this.httpOptions); } changeUserPassword(user_uid, new_password) { @@ -560,11 +572,11 @@ export class PostsService implements CanActivate { } getUsers() { - return this.http.post(this.path + 'getUsers', {}, this.httpOptions); + return this.http.post(this.path + 'getUsers', {}, this.httpOptions); } getRoles() { - return this.http.post(this.path + 'getRoles', {}, this.httpOptions); + return this.http.post(this.path + 'getRoles', {}, this.httpOptions); } setUserPermission(user_uid: string, permission: UserPermission, new_value: YesNo) { From 111254824631891d53d91b9ad05a5f176c3376d9 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Fri, 25 Sep 2020 10:47:25 -0700 Subject: [PATCH 007/209] Commit api types --- .gitignore | 1 - src/api-types/index.ts | 70 +++++++++++++++++++ .../models/BaseChangePermissionsRequest.ts | 11 +++ src/api-types/models/BaseDownloadRequest.ts | 29 ++++++++ src/api-types/models/BaseDownloadResponse.ts | 9 +++ .../models/ChangeRolePermissionsRequest.ts | 9 +++ .../models/ChangeUserPermissionsRequest.ts | 9 +++ src/api-types/models/Config.ts | 8 +++ src/api-types/models/ConfigResponse.ts | 10 +++ src/api-types/models/CreatePlaylistRequest.ts | 13 ++++ .../models/CreatePlaylistResponse.ts | 10 +++ src/api-types/models/DatabaseFile.ts | 22 ++++++ src/api-types/models/DeleteFileRequest.ts | 10 +++ src/api-types/models/DeleteMp3Mp4Request.ts | 9 +++ src/api-types/models/DeletePlaylistRequest.ts | 10 +++ .../models/DeleteSubscriptionFileRequest.ts | 15 ++++ src/api-types/models/DeleteUserRequest.ts | 8 +++ src/api-types/models/Dictionary.ts | 7 ++ src/api-types/models/Download.ts | 23 ++++++ .../models/DownloadArchiveRequest.ts | 10 +++ src/api-types/models/DownloadFileRequest.ts | 22 ++++++ .../DownloadVideosForSubscriptionRequest.ts | 8 +++ src/api-types/models/File.ts | 8 +++ src/api-types/models/FileType.ts | 9 +++ .../models/GenerateNewApiKeyResponse.ts | 8 +++ .../models/GetAllDownloadsResponse.ts | 13 ++++ src/api-types/models/GetAllFilesResponse.ts | 14 ++++ .../models/GetAllSubscriptionsResponse.ts | 9 +++ src/api-types/models/GetDownloadRequest.ts | 9 +++ src/api-types/models/GetDownloadResponse.ts | 9 +++ src/api-types/models/GetFileRequest.ts | 17 +++++ src/api-types/models/GetFileResponse.ts | 10 +++ src/api-types/models/GetMp3sResponse.ts | 14 ++++ src/api-types/models/GetMp4sResponse.ts | 14 ++++ src/api-types/models/GetPlaylistRequest.ts | 11 +++ src/api-types/models/GetPlaylistResponse.ts | 12 ++++ src/api-types/models/GetRolesResponse.ts | 16 +++++ .../models/GetSubscriptionRequest.ts | 11 +++ .../models/GetSubscriptionResponse.ts | 10 +++ src/api-types/models/GetUsersResponse.ts | 9 +++ src/api-types/models/LoginRequest.ts | 9 +++ src/api-types/models/LoginResponse.ts | 13 ++++ src/api-types/models/Mp3DownloadRequest.ts | 12 ++++ src/api-types/models/Mp3DownloadResponse.ts | 9 +++ src/api-types/models/Mp4DownloadRequest.ts | 12 ++++ src/api-types/models/Mp4DownloadResponse.ts | 9 +++ src/api-types/models/Playlist.ts | 15 ++++ src/api-types/models/RegisterRequest.ts | 10 +++ src/api-types/models/RegisterResponse.ts | 9 +++ src/api-types/models/SetConfigRequest.ts | 9 +++ src/api-types/models/SharingToggle.ts | 11 +++ src/api-types/models/SubscribeRequest.ts | 14 ++++ src/api-types/models/SubscribeResponse.ts | 10 +++ src/api-types/models/Subscription.ts | 20 ++++++ .../models/SubscriptionRequestData.ts | 13 ++++ src/api-types/models/SuccessObject.ts | 8 +++ src/api-types/models/UnsubscribeRequest.ts | 13 ++++ src/api-types/models/UnsubscribeResponse.ts | 9 +++ .../models/UpdatePlaylistFilesRequest.ts | 11 +++ src/api-types/models/UpdatePlaylistRequest.ts | 9 +++ src/api-types/models/UpdateServerRequest.ts | 8 +++ src/api-types/models/UpdateUserRequest.ts | 12 ++++ src/api-types/models/UpdaterStatus.ts | 10 +++ src/api-types/models/User.ts | 25 +++++++ src/api-types/models/UserPermission.ts | 13 ++++ src/api-types/models/YesNo.ts | 9 +++ src/api-types/models/body_19.ts | 8 +++ src/api-types/models/body_20.ts | 8 +++ .../models/inline_response_200_15.ts | 8 +++ 69 files changed, 851 insertions(+), 1 deletion(-) create mode 100644 src/api-types/index.ts create mode 100644 src/api-types/models/BaseChangePermissionsRequest.ts create mode 100644 src/api-types/models/BaseDownloadRequest.ts create mode 100644 src/api-types/models/BaseDownloadResponse.ts create mode 100644 src/api-types/models/ChangeRolePermissionsRequest.ts create mode 100644 src/api-types/models/ChangeUserPermissionsRequest.ts create mode 100644 src/api-types/models/Config.ts create mode 100644 src/api-types/models/ConfigResponse.ts create mode 100644 src/api-types/models/CreatePlaylistRequest.ts create mode 100644 src/api-types/models/CreatePlaylistResponse.ts create mode 100644 src/api-types/models/DatabaseFile.ts create mode 100644 src/api-types/models/DeleteFileRequest.ts create mode 100644 src/api-types/models/DeleteMp3Mp4Request.ts create mode 100644 src/api-types/models/DeletePlaylistRequest.ts create mode 100644 src/api-types/models/DeleteSubscriptionFileRequest.ts create mode 100644 src/api-types/models/DeleteUserRequest.ts create mode 100644 src/api-types/models/Dictionary.ts create mode 100644 src/api-types/models/Download.ts create mode 100644 src/api-types/models/DownloadArchiveRequest.ts create mode 100644 src/api-types/models/DownloadFileRequest.ts create mode 100644 src/api-types/models/DownloadVideosForSubscriptionRequest.ts create mode 100644 src/api-types/models/File.ts create mode 100644 src/api-types/models/FileType.ts create mode 100644 src/api-types/models/GenerateNewApiKeyResponse.ts create mode 100644 src/api-types/models/GetAllDownloadsResponse.ts create mode 100644 src/api-types/models/GetAllFilesResponse.ts create mode 100644 src/api-types/models/GetAllSubscriptionsResponse.ts create mode 100644 src/api-types/models/GetDownloadRequest.ts create mode 100644 src/api-types/models/GetDownloadResponse.ts create mode 100644 src/api-types/models/GetFileRequest.ts create mode 100644 src/api-types/models/GetFileResponse.ts create mode 100644 src/api-types/models/GetMp3sResponse.ts create mode 100644 src/api-types/models/GetMp4sResponse.ts create mode 100644 src/api-types/models/GetPlaylistRequest.ts create mode 100644 src/api-types/models/GetPlaylistResponse.ts create mode 100644 src/api-types/models/GetRolesResponse.ts create mode 100644 src/api-types/models/GetSubscriptionRequest.ts create mode 100644 src/api-types/models/GetSubscriptionResponse.ts create mode 100644 src/api-types/models/GetUsersResponse.ts create mode 100644 src/api-types/models/LoginRequest.ts create mode 100644 src/api-types/models/LoginResponse.ts create mode 100644 src/api-types/models/Mp3DownloadRequest.ts create mode 100644 src/api-types/models/Mp3DownloadResponse.ts create mode 100644 src/api-types/models/Mp4DownloadRequest.ts create mode 100644 src/api-types/models/Mp4DownloadResponse.ts create mode 100644 src/api-types/models/Playlist.ts create mode 100644 src/api-types/models/RegisterRequest.ts create mode 100644 src/api-types/models/RegisterResponse.ts create mode 100644 src/api-types/models/SetConfigRequest.ts create mode 100644 src/api-types/models/SharingToggle.ts create mode 100644 src/api-types/models/SubscribeRequest.ts create mode 100644 src/api-types/models/SubscribeResponse.ts create mode 100644 src/api-types/models/Subscription.ts create mode 100644 src/api-types/models/SubscriptionRequestData.ts create mode 100644 src/api-types/models/SuccessObject.ts create mode 100644 src/api-types/models/UnsubscribeRequest.ts create mode 100644 src/api-types/models/UnsubscribeResponse.ts create mode 100644 src/api-types/models/UpdatePlaylistFilesRequest.ts create mode 100644 src/api-types/models/UpdatePlaylistRequest.ts create mode 100644 src/api-types/models/UpdateServerRequest.ts create mode 100644 src/api-types/models/UpdateUserRequest.ts create mode 100644 src/api-types/models/UpdaterStatus.ts create mode 100644 src/api-types/models/User.ts create mode 100644 src/api-types/models/UserPermission.ts create mode 100644 src/api-types/models/YesNo.ts create mode 100644 src/api-types/models/body_19.ts create mode 100644 src/api-types/models/body_20.ts create mode 100644 src/api-types/models/inline_response_200_15.ts diff --git a/.gitignore b/.gitignore index 32c9fd9..c99f53f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,7 +55,6 @@ backend/subscriptions/playlists/* backend/subscriptions/archives/* backend/*.exe src/assets/default.json -src/api-types backend/appdata/db.json backend/appdata/archives/archive_audio.txt backend/appdata/archives/archive_video.txt diff --git a/src/api-types/index.ts b/src/api-types/index.ts new file mode 100644 index 0000000..5a0b927 --- /dev/null +++ b/src/api-types/index.ts @@ -0,0 +1,70 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type { BaseChangePermissionsRequest } from './models/BaseChangePermissionsRequest'; +export type { BaseDownloadRequest } from './models/BaseDownloadRequest'; +export type { BaseDownloadResponse } from './models/BaseDownloadResponse'; +export type { body_19 } from './models/body_19'; +export type { body_20 } from './models/body_20'; +export type { ChangeRolePermissionsRequest } from './models/ChangeRolePermissionsRequest'; +export type { ChangeUserPermissionsRequest } from './models/ChangeUserPermissionsRequest'; +export type { Config } from './models/Config'; +export type { ConfigResponse } from './models/ConfigResponse'; +export type { CreatePlaylistRequest } from './models/CreatePlaylistRequest'; +export type { CreatePlaylistResponse } from './models/CreatePlaylistResponse'; +export type { DatabaseFile } from './models/DatabaseFile'; +export type { DeleteFileRequest } from './models/DeleteFileRequest'; +export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request'; +export type { DeletePlaylistRequest } from './models/DeletePlaylistRequest'; +export type { DeleteSubscriptionFileRequest } from './models/DeleteSubscriptionFileRequest'; +export type { DeleteUserRequest } from './models/DeleteUserRequest'; +export type { Download } from './models/Download'; +export type { DownloadArchiveRequest } from './models/DownloadArchiveRequest'; +export type { DownloadFileRequest } from './models/DownloadFileRequest'; +export type { DownloadVideosForSubscriptionRequest } from './models/DownloadVideosForSubscriptionRequest'; +export type { File } from './models/File'; +export { FileType } from './models/FileType'; +export type { GenerateNewApiKeyResponse } from './models/GenerateNewApiKeyResponse'; +export type { GetAllDownloadsResponse } from './models/GetAllDownloadsResponse'; +export type { GetAllFilesResponse } from './models/GetAllFilesResponse'; +export type { GetAllSubscriptionsResponse } from './models/GetAllSubscriptionsResponse'; +export type { GetDownloadRequest } from './models/GetDownloadRequest'; +export type { GetDownloadResponse } from './models/GetDownloadResponse'; +export type { GetFileRequest } from './models/GetFileRequest'; +export type { GetFileResponse } from './models/GetFileResponse'; +export type { GetMp3sResponse } from './models/GetMp3sResponse'; +export type { GetMp4sResponse } from './models/GetMp4sResponse'; +export type { GetPlaylistRequest } from './models/GetPlaylistRequest'; +export type { GetPlaylistResponse } from './models/GetPlaylistResponse'; +export type { GetRolesResponse } from './models/GetRolesResponse'; +export type { GetSubscriptionRequest } from './models/GetSubscriptionRequest'; +export type { GetSubscriptionResponse } from './models/GetSubscriptionResponse'; +export type { GetUsersResponse } from './models/GetUsersResponse'; +export type { inline_response_200_15 } from './models/inline_response_200_15'; +export type { LoginRequest } from './models/LoginRequest'; +export type { LoginResponse } from './models/LoginResponse'; +export type { Mp3DownloadRequest } from './models/Mp3DownloadRequest'; +export type { Mp3DownloadResponse } from './models/Mp3DownloadResponse'; +export type { Mp4DownloadRequest } from './models/Mp4DownloadRequest'; +export type { Mp4DownloadResponse } from './models/Mp4DownloadResponse'; +export type { Playlist } from './models/Playlist'; +export type { RegisterRequest } from './models/RegisterRequest'; +export type { RegisterResponse } from './models/RegisterResponse'; +export type { SetConfigRequest } from './models/SetConfigRequest'; +export type { SharingToggle } from './models/SharingToggle'; +export type { SubscribeRequest } from './models/SubscribeRequest'; +export type { SubscribeResponse } from './models/SubscribeResponse'; +export type { Subscription } from './models/Subscription'; +export type { SubscriptionRequestData } from './models/SubscriptionRequestData'; +export type { SuccessObject } from './models/SuccessObject'; +export type { UnsubscribeRequest } from './models/UnsubscribeRequest'; +export type { UnsubscribeResponse } from './models/UnsubscribeResponse'; +export type { UpdatePlaylistFilesRequest } from './models/UpdatePlaylistFilesRequest'; +export type { UpdatePlaylistRequest } from './models/UpdatePlaylistRequest'; +export type { UpdaterStatus } from './models/UpdaterStatus'; +export type { UpdateServerRequest } from './models/UpdateServerRequest'; +export type { UpdateUserRequest } from './models/UpdateUserRequest'; +export type { User } from './models/User'; +export { UserPermission } from './models/UserPermission'; +export { YesNo } from './models/YesNo'; diff --git a/src/api-types/models/BaseChangePermissionsRequest.ts b/src/api-types/models/BaseChangePermissionsRequest.ts new file mode 100644 index 0000000..6551d46 --- /dev/null +++ b/src/api-types/models/BaseChangePermissionsRequest.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { UserPermission } from './UserPermission'; +import { YesNo } from './YesNo'; + +export interface BaseChangePermissionsRequest { + permission: UserPermission; + new_value: YesNo; +} diff --git a/src/api-types/models/BaseDownloadRequest.ts b/src/api-types/models/BaseDownloadRequest.ts new file mode 100644 index 0000000..b6cf3fb --- /dev/null +++ b/src/api-types/models/BaseDownloadRequest.ts @@ -0,0 +1,29 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface BaseDownloadRequest { + url: string; + /** + * Video format code. Overrides other quality options. + */ + customQualityConfiguration?: string; + /** + * Custom command-line arguments for youtubedl. Overrides all other options, except url. + */ + customArgs?: string; + /** + * Custom output filename template. + */ + customOutput?: string; + /** + * Login with this account ID + */ + youtubeUsername?: string; + /** + * Account password + */ + youtubePassword?: string; + ui_uid?: string | null; +} diff --git a/src/api-types/models/BaseDownloadResponse.ts b/src/api-types/models/BaseDownloadResponse.ts new file mode 100644 index 0000000..83d848f --- /dev/null +++ b/src/api-types/models/BaseDownloadResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface BaseDownloadResponse { + uid: string; + file_names?: Array | null; +} diff --git a/src/api-types/models/ChangeRolePermissionsRequest.ts b/src/api-types/models/ChangeRolePermissionsRequest.ts new file mode 100644 index 0000000..a6f6ed9 --- /dev/null +++ b/src/api-types/models/ChangeRolePermissionsRequest.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest'; + +export interface ChangeRolePermissionsRequest extends BaseChangePermissionsRequest { + role: string; +} diff --git a/src/api-types/models/ChangeUserPermissionsRequest.ts b/src/api-types/models/ChangeUserPermissionsRequest.ts new file mode 100644 index 0000000..e6f044a --- /dev/null +++ b/src/api-types/models/ChangeUserPermissionsRequest.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest'; + +export interface ChangeUserPermissionsRequest extends BaseChangePermissionsRequest { + user_uid: string; +} diff --git a/src/api-types/models/Config.ts b/src/api-types/models/Config.ts new file mode 100644 index 0000000..509c42f --- /dev/null +++ b/src/api-types/models/Config.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface Config { + YoutubeDLMaterial: any; +} diff --git a/src/api-types/models/ConfigResponse.ts b/src/api-types/models/ConfigResponse.ts new file mode 100644 index 0000000..b493361 --- /dev/null +++ b/src/api-types/models/ConfigResponse.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Config } from './Config'; + +export interface ConfigResponse { + config_file: Config; + success: boolean; +} diff --git a/src/api-types/models/CreatePlaylistRequest.ts b/src/api-types/models/CreatePlaylistRequest.ts new file mode 100644 index 0000000..e497dff --- /dev/null +++ b/src/api-types/models/CreatePlaylistRequest.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface CreatePlaylistRequest { + playlistName: string; + fileNames: Array; + type: FileType; + thumbnailURL: string; + duration: number; +} diff --git a/src/api-types/models/CreatePlaylistResponse.ts b/src/api-types/models/CreatePlaylistResponse.ts new file mode 100644 index 0000000..7d5a98c --- /dev/null +++ b/src/api-types/models/CreatePlaylistResponse.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Playlist } from './Playlist'; + +export interface CreatePlaylistResponse { + new_playlist: Playlist; + success: boolean; +} diff --git a/src/api-types/models/DatabaseFile.ts b/src/api-types/models/DatabaseFile.ts new file mode 100644 index 0000000..d6009b2 --- /dev/null +++ b/src/api-types/models/DatabaseFile.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface DatabaseFile { + id: string; + title: string; + thumbnailURL: string; + isAudio: boolean; + /** + * In seconds + */ + duration: number; + url: string; + uploader: string; + size: number; + path: string; + upload_date: string; + uid: string; + sharingEnabled?: boolean; +} diff --git a/src/api-types/models/DeleteFileRequest.ts b/src/api-types/models/DeleteFileRequest.ts new file mode 100644 index 0000000..c64af6c --- /dev/null +++ b/src/api-types/models/DeleteFileRequest.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface DeleteFileRequest { + fileName: string; + type: FileType; +} diff --git a/src/api-types/models/DeleteMp3Mp4Request.ts b/src/api-types/models/DeleteMp3Mp4Request.ts new file mode 100644 index 0000000..f6d65d6 --- /dev/null +++ b/src/api-types/models/DeleteMp3Mp4Request.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface DeleteMp3Mp4Request { + uid: string; + blacklistMode?: boolean; +} diff --git a/src/api-types/models/DeletePlaylistRequest.ts b/src/api-types/models/DeletePlaylistRequest.ts new file mode 100644 index 0000000..25830bb --- /dev/null +++ b/src/api-types/models/DeletePlaylistRequest.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface DeletePlaylistRequest { + playlistID: string; + type: FileType; +} diff --git a/src/api-types/models/DeleteSubscriptionFileRequest.ts b/src/api-types/models/DeleteSubscriptionFileRequest.ts new file mode 100644 index 0000000..aebe91b --- /dev/null +++ b/src/api-types/models/DeleteSubscriptionFileRequest.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { SubscriptionRequestData } from './SubscriptionRequestData'; + +export interface DeleteSubscriptionFileRequest { + file: string; + file_uid?: string; + sub: SubscriptionRequestData; + /** + * If true, does not remove id from archive. Only valid if youtube-dl archive is enabled in settings. + */ + deleteForever?: boolean; +} diff --git a/src/api-types/models/DeleteUserRequest.ts b/src/api-types/models/DeleteUserRequest.ts new file mode 100644 index 0000000..a994369 --- /dev/null +++ b/src/api-types/models/DeleteUserRequest.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface DeleteUserRequest { + uid: string; +} diff --git a/src/api-types/models/Dictionary.ts b/src/api-types/models/Dictionary.ts new file mode 100644 index 0000000..8ba0443 --- /dev/null +++ b/src/api-types/models/Dictionary.ts @@ -0,0 +1,7 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Dictionary = { + [key: string]: T; +} diff --git a/src/api-types/models/Download.ts b/src/api-types/models/Download.ts new file mode 100644 index 0000000..b4d91d9 --- /dev/null +++ b/src/api-types/models/Download.ts @@ -0,0 +1,23 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface Download { + uid: string; + ui_uid: string; + downloading: boolean; + complete: boolean; + url: string; + type: string; + percent_complete: number; + is_playlist: boolean; + timestamp_start: number; + timestamp_end?: number; + filesize?: number | null; + /** + * Error text, set if download fails. + */ + error?: string; + fileNames?: Array; +} diff --git a/src/api-types/models/DownloadArchiveRequest.ts b/src/api-types/models/DownloadArchiveRequest.ts new file mode 100644 index 0000000..46c5c45 --- /dev/null +++ b/src/api-types/models/DownloadArchiveRequest.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface DownloadArchiveRequest { + sub: { + archive_dir: string, + }; +} diff --git a/src/api-types/models/DownloadFileRequest.ts b/src/api-types/models/DownloadFileRequest.ts new file mode 100644 index 0000000..18c6cac --- /dev/null +++ b/src/api-types/models/DownloadFileRequest.ts @@ -0,0 +1,22 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface DownloadFileRequest { + fileNames: ; + zip_mode?: boolean; + type: FileType; + outputName?: string; + fullPathProvided?: boolean; + uuid?: string; + /** + * Only used for subscriptions + */ + subscriptionName?: boolean; + /** + * Only used for subscriptions + */ + subPlaylist?: boolean; +} diff --git a/src/api-types/models/DownloadVideosForSubscriptionRequest.ts b/src/api-types/models/DownloadVideosForSubscriptionRequest.ts new file mode 100644 index 0000000..4b64af3 --- /dev/null +++ b/src/api-types/models/DownloadVideosForSubscriptionRequest.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface DownloadVideosForSubscriptionRequest { + subID: string; +} diff --git a/src/api-types/models/File.ts b/src/api-types/models/File.ts new file mode 100644 index 0000000..2e81613 --- /dev/null +++ b/src/api-types/models/File.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface File { + id?: string; +} diff --git a/src/api-types/models/FileType.ts b/src/api-types/models/FileType.ts new file mode 100644 index 0000000..e1b6f79 --- /dev/null +++ b/src/api-types/models/FileType.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export enum FileType { + AUDIO = 'audio', + VIDEO = 'video', +} \ No newline at end of file diff --git a/src/api-types/models/GenerateNewApiKeyResponse.ts b/src/api-types/models/GenerateNewApiKeyResponse.ts new file mode 100644 index 0000000..d96a464 --- /dev/null +++ b/src/api-types/models/GenerateNewApiKeyResponse.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface GenerateNewApiKeyResponse { + new_api_key: string; +} diff --git a/src/api-types/models/GetAllDownloadsResponse.ts b/src/api-types/models/GetAllDownloadsResponse.ts new file mode 100644 index 0000000..1f99a85 --- /dev/null +++ b/src/api-types/models/GetAllDownloadsResponse.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Dictionary } from './Dictionary'; +import { Download } from './Download'; + +export interface GetAllDownloadsResponse { + /** + * Map of Session ID to inner map + */ + downloads?: Dictionary>; +} diff --git a/src/api-types/models/GetAllFilesResponse.ts b/src/api-types/models/GetAllFilesResponse.ts new file mode 100644 index 0000000..79f7fc6 --- /dev/null +++ b/src/api-types/models/GetAllFilesResponse.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { DatabaseFile } from './DatabaseFile'; +import { Playlist } from './Playlist'; + +export interface GetAllFilesResponse { + files: Array; + /** + * All video playlists + */ + playlists: Array; +} diff --git a/src/api-types/models/GetAllSubscriptionsResponse.ts b/src/api-types/models/GetAllSubscriptionsResponse.ts new file mode 100644 index 0000000..fafb6f5 --- /dev/null +++ b/src/api-types/models/GetAllSubscriptionsResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Subscription } from './Subscription'; + +export interface GetAllSubscriptionsResponse { + subscriptions: Array; +} diff --git a/src/api-types/models/GetDownloadRequest.ts b/src/api-types/models/GetDownloadRequest.ts new file mode 100644 index 0000000..9ad5fdf --- /dev/null +++ b/src/api-types/models/GetDownloadRequest.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface GetDownloadRequest { + session_id: string; + download_id: string; +} diff --git a/src/api-types/models/GetDownloadResponse.ts b/src/api-types/models/GetDownloadResponse.ts new file mode 100644 index 0000000..b027cbf --- /dev/null +++ b/src/api-types/models/GetDownloadResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Download } from './Download'; + +export interface GetDownloadResponse { + download?: Download | null; +} diff --git a/src/api-types/models/GetFileRequest.ts b/src/api-types/models/GetFileRequest.ts new file mode 100644 index 0000000..0d85afb --- /dev/null +++ b/src/api-types/models/GetFileRequest.ts @@ -0,0 +1,17 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface GetFileRequest { + /** + * Video UID + */ + uid: string; + type?: FileType; + /** + * User UID + */ + uuid?: string; +} diff --git a/src/api-types/models/GetFileResponse.ts b/src/api-types/models/GetFileResponse.ts new file mode 100644 index 0000000..71b3563 --- /dev/null +++ b/src/api-types/models/GetFileResponse.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { DatabaseFile } from './DatabaseFile'; + +export interface GetFileResponse { + success: boolean; + file?: DatabaseFile; +} diff --git a/src/api-types/models/GetMp3sResponse.ts b/src/api-types/models/GetMp3sResponse.ts new file mode 100644 index 0000000..13f03dc --- /dev/null +++ b/src/api-types/models/GetMp3sResponse.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { DatabaseFile } from './DatabaseFile'; +import { Playlist } from './Playlist'; + +export interface GetMp3sResponse { + mp3s: Array; + /** + * All audio playlists + */ + playlists: Array; +} diff --git a/src/api-types/models/GetMp4sResponse.ts b/src/api-types/models/GetMp4sResponse.ts new file mode 100644 index 0000000..9ef2c1f --- /dev/null +++ b/src/api-types/models/GetMp4sResponse.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { DatabaseFile } from './DatabaseFile'; +import { Playlist } from './Playlist'; + +export interface GetMp4sResponse { + mp4s: Array; + /** + * All video playlists + */ + playlists: Array; +} diff --git a/src/api-types/models/GetPlaylistRequest.ts b/src/api-types/models/GetPlaylistRequest.ts new file mode 100644 index 0000000..ca86a2d --- /dev/null +++ b/src/api-types/models/GetPlaylistRequest.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface GetPlaylistRequest { + playlistID: string; + type?: FileType; + uuid?: string; +} diff --git a/src/api-types/models/GetPlaylistResponse.ts b/src/api-types/models/GetPlaylistResponse.ts new file mode 100644 index 0000000..c3bf7c9 --- /dev/null +++ b/src/api-types/models/GetPlaylistResponse.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; +import { Playlist } from './Playlist'; + +export interface GetPlaylistResponse { + playlist: Playlist; + type: FileType; + success: boolean; +} diff --git a/src/api-types/models/GetRolesResponse.ts b/src/api-types/models/GetRolesResponse.ts new file mode 100644 index 0000000..ce5e696 --- /dev/null +++ b/src/api-types/models/GetRolesResponse.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { UserPermission } from './UserPermission'; + +export interface GetRolesResponse { + roles: { + admin?: { + permissions?: Array, + }, + user?: { + permissions?: Array, + }, + }; +} diff --git a/src/api-types/models/GetSubscriptionRequest.ts b/src/api-types/models/GetSubscriptionRequest.ts new file mode 100644 index 0000000..99ee221 --- /dev/null +++ b/src/api-types/models/GetSubscriptionRequest.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface GetSubscriptionRequest { + /** + * Subscription ID + */ + id: string; +} diff --git a/src/api-types/models/GetSubscriptionResponse.ts b/src/api-types/models/GetSubscriptionResponse.ts new file mode 100644 index 0000000..9d2209a --- /dev/null +++ b/src/api-types/models/GetSubscriptionResponse.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Subscription } from './Subscription'; + +export interface GetSubscriptionResponse { + subscription: Subscription; + files: Array; +} diff --git a/src/api-types/models/GetUsersResponse.ts b/src/api-types/models/GetUsersResponse.ts new file mode 100644 index 0000000..f5f9eb0 --- /dev/null +++ b/src/api-types/models/GetUsersResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { User } from './User'; + +export interface GetUsersResponse { + users: Array; +} diff --git a/src/api-types/models/LoginRequest.ts b/src/api-types/models/LoginRequest.ts new file mode 100644 index 0000000..a06327f --- /dev/null +++ b/src/api-types/models/LoginRequest.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface LoginRequest { + username: string; + password: string; +} diff --git a/src/api-types/models/LoginResponse.ts b/src/api-types/models/LoginResponse.ts new file mode 100644 index 0000000..c94f444 --- /dev/null +++ b/src/api-types/models/LoginResponse.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { User } from './User'; +import { UserPermission } from './UserPermission'; + +export interface LoginResponse { + user?: User; + token?: string; + permissions?: Array; + available_permissions?: Array; +} diff --git a/src/api-types/models/Mp3DownloadRequest.ts b/src/api-types/models/Mp3DownloadRequest.ts new file mode 100644 index 0000000..b4b4f8f --- /dev/null +++ b/src/api-types/models/Mp3DownloadRequest.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { BaseDownloadRequest } from './BaseDownloadRequest'; + +export interface Mp3DownloadRequest extends BaseDownloadRequest { + /** + * Specify ffmpeg/avconv audio quality + */ + maxBitrate?: string; +} diff --git a/src/api-types/models/Mp3DownloadResponse.ts b/src/api-types/models/Mp3DownloadResponse.ts new file mode 100644 index 0000000..32924a6 --- /dev/null +++ b/src/api-types/models/Mp3DownloadResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { BaseDownloadResponse } from './BaseDownloadResponse'; + +export interface Mp3DownloadResponse extends BaseDownloadResponse { + audiopathEncoded: string; +} diff --git a/src/api-types/models/Mp4DownloadRequest.ts b/src/api-types/models/Mp4DownloadRequest.ts new file mode 100644 index 0000000..faeb6ab --- /dev/null +++ b/src/api-types/models/Mp4DownloadRequest.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { BaseDownloadRequest } from './BaseDownloadRequest'; + +export interface Mp4DownloadRequest extends BaseDownloadRequest { + /** + * Height of the video, if known + */ + selectedHeight?: string; +} diff --git a/src/api-types/models/Mp4DownloadResponse.ts b/src/api-types/models/Mp4DownloadResponse.ts new file mode 100644 index 0000000..0763c07 --- /dev/null +++ b/src/api-types/models/Mp4DownloadResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { BaseDownloadResponse } from './BaseDownloadResponse'; + +export interface Mp4DownloadResponse extends BaseDownloadResponse { + videopathEncoded: string; +} diff --git a/src/api-types/models/Playlist.ts b/src/api-types/models/Playlist.ts new file mode 100644 index 0000000..1afe993 --- /dev/null +++ b/src/api-types/models/Playlist.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface Playlist { + name: string; + fileNames: Array; + id: string; + thumbnailURL: string; + type: FileType; + registered: number; + duration: number; +} diff --git a/src/api-types/models/RegisterRequest.ts b/src/api-types/models/RegisterRequest.ts new file mode 100644 index 0000000..73bee22 --- /dev/null +++ b/src/api-types/models/RegisterRequest.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface RegisterRequest { + userid: string; + username: string; + password: string; +} diff --git a/src/api-types/models/RegisterResponse.ts b/src/api-types/models/RegisterResponse.ts new file mode 100644 index 0000000..4e45b65 --- /dev/null +++ b/src/api-types/models/RegisterResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { User } from './User'; + +export interface RegisterResponse { + user?: User; +} diff --git a/src/api-types/models/SetConfigRequest.ts b/src/api-types/models/SetConfigRequest.ts new file mode 100644 index 0000000..41ef52a --- /dev/null +++ b/src/api-types/models/SetConfigRequest.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Config } from './Config'; + +export interface SetConfigRequest { + new_config_file: Config; +} diff --git a/src/api-types/models/SharingToggle.ts b/src/api-types/models/SharingToggle.ts new file mode 100644 index 0000000..2de90fc --- /dev/null +++ b/src/api-types/models/SharingToggle.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface SharingToggle { + uid: string; + type: FileType; + is_playlist?: boolean; +} diff --git a/src/api-types/models/SubscribeRequest.ts b/src/api-types/models/SubscribeRequest.ts new file mode 100644 index 0000000..d475cb9 --- /dev/null +++ b/src/api-types/models/SubscribeRequest.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface SubscribeRequest { + name: string; + url: string; + timerange?: string; + streamingOnly: boolean; + audioOnly?: boolean; + customArgs?: string; + customFileOutput?: string; +} diff --git a/src/api-types/models/SubscribeResponse.ts b/src/api-types/models/SubscribeResponse.ts new file mode 100644 index 0000000..fa4c235 --- /dev/null +++ b/src/api-types/models/SubscribeResponse.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Subscription } from './Subscription'; + +export interface SubscribeResponse { + new_sub: Subscription; + error?: string; +} diff --git a/src/api-types/models/Subscription.ts b/src/api-types/models/Subscription.ts new file mode 100644 index 0000000..8cd369e --- /dev/null +++ b/src/api-types/models/Subscription.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface Subscription { + name: string; + url: string; + id: string; + type: FileType; + user_uid: string | null; + streamingOnly: boolean; + isPlaylist: boolean; + archive?: string; + timerange?: string; + custom_args?: string; + custom_output?: string; + videos: Array; +} diff --git a/src/api-types/models/SubscriptionRequestData.ts b/src/api-types/models/SubscriptionRequestData.ts new file mode 100644 index 0000000..6c99499 --- /dev/null +++ b/src/api-types/models/SubscriptionRequestData.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface SubscriptionRequestData { + name: string; + id: string; + type?: FileType; + isPlaylist?: boolean; + archive?: string; +} diff --git a/src/api-types/models/SuccessObject.ts b/src/api-types/models/SuccessObject.ts new file mode 100644 index 0000000..cd0a11e --- /dev/null +++ b/src/api-types/models/SuccessObject.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface SuccessObject { + success: boolean; +} diff --git a/src/api-types/models/UnsubscribeRequest.ts b/src/api-types/models/UnsubscribeRequest.ts new file mode 100644 index 0000000..598faee --- /dev/null +++ b/src/api-types/models/UnsubscribeRequest.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { SubscriptionRequestData } from './SubscriptionRequestData'; + +export interface UnsubscribeRequest { + sub: SubscriptionRequestData; + /** + * Defaults to false + */ + deleteMode?: boolean; +} diff --git a/src/api-types/models/UnsubscribeResponse.ts b/src/api-types/models/UnsubscribeResponse.ts new file mode 100644 index 0000000..0f0d991 --- /dev/null +++ b/src/api-types/models/UnsubscribeResponse.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface UnsubscribeResponse { + success: boolean; + error?: string; +} diff --git a/src/api-types/models/UpdatePlaylistFilesRequest.ts b/src/api-types/models/UpdatePlaylistFilesRequest.ts new file mode 100644 index 0000000..3fa95c2 --- /dev/null +++ b/src/api-types/models/UpdatePlaylistFilesRequest.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { FileType } from './FileType'; + +export interface UpdatePlaylistFilesRequest { + playlistID: string; + fileNames: Array; + type: FileType; +} diff --git a/src/api-types/models/UpdatePlaylistRequest.ts b/src/api-types/models/UpdatePlaylistRequest.ts new file mode 100644 index 0000000..1eaee52 --- /dev/null +++ b/src/api-types/models/UpdatePlaylistRequest.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Playlist } from './Playlist'; + +export interface UpdatePlaylistRequest { + playlist: Playlist; +} diff --git a/src/api-types/models/UpdateServerRequest.ts b/src/api-types/models/UpdateServerRequest.ts new file mode 100644 index 0000000..95db6e4 --- /dev/null +++ b/src/api-types/models/UpdateServerRequest.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface UpdateServerRequest { + tag: string; +} diff --git a/src/api-types/models/UpdateUserRequest.ts b/src/api-types/models/UpdateUserRequest.ts new file mode 100644 index 0000000..c6e2f05 --- /dev/null +++ b/src/api-types/models/UpdateUserRequest.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface UpdateUserRequest { + change_object: { + uid: string, + name?: string, + role?: string, + }; +} diff --git a/src/api-types/models/UpdaterStatus.ts b/src/api-types/models/UpdaterStatus.ts new file mode 100644 index 0000000..19e513c --- /dev/null +++ b/src/api-types/models/UpdaterStatus.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface UpdaterStatus { + updating: boolean; + details: string; + error?: boolean; +} diff --git a/src/api-types/models/User.ts b/src/api-types/models/User.ts new file mode 100644 index 0000000..8a49d3c --- /dev/null +++ b/src/api-types/models/User.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import { Subscription } from './Subscription'; +import { UserPermission } from './UserPermission'; + +export interface User { + uid?: string; + name?: string; + passhash?: string; + files?: { + audio?: Array, + video?: Array, + }; + playlists?: { + audio?: Array, + video?: Array, + }; + subscriptions?: Array; + created?: number; + role?: string; + permissions?: Array; + permission_overrides?: Array; +} diff --git a/src/api-types/models/UserPermission.ts b/src/api-types/models/UserPermission.ts new file mode 100644 index 0000000..b22ef87 --- /dev/null +++ b/src/api-types/models/UserPermission.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export enum UserPermission { + FILEMANAGER = 'filemanager', + SETTINGS = 'settings', + SUBSCRIPTIONS = 'subscriptions', + SHARING = 'sharing', + ADVANCED_DOWNLOAD = 'advanced_download', + DOWNLOADS_MANAGER = 'downloads_manager', +} \ No newline at end of file diff --git a/src/api-types/models/YesNo.ts b/src/api-types/models/YesNo.ts new file mode 100644 index 0000000..dbe9c62 --- /dev/null +++ b/src/api-types/models/YesNo.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export enum YesNo { + YES = 'yes', + NO = 'no', +} \ No newline at end of file diff --git a/src/api-types/models/body_19.ts b/src/api-types/models/body_19.ts new file mode 100644 index 0000000..b0ce9cb --- /dev/null +++ b/src/api-types/models/body_19.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface body_19 { + input_pin: string; +} diff --git a/src/api-types/models/body_20.ts b/src/api-types/models/body_20.ts new file mode 100644 index 0000000..84a7b47 --- /dev/null +++ b/src/api-types/models/body_20.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface body_20 { + unhashed_pin: string; +} diff --git a/src/api-types/models/inline_response_200_15.ts b/src/api-types/models/inline_response_200_15.ts new file mode 100644 index 0000000..bb98ac6 --- /dev/null +++ b/src/api-types/models/inline_response_200_15.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + + +export interface inline_response_200_15 { + is_set: boolean; +} From d1311d00ea2e06ed900017f91bf52f780a366078 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Sat, 26 Sep 2020 14:50:10 -0700 Subject: [PATCH 008/209] Use named arguments with download file --- Public API v1.yaml | 10 +++++++++- src/api-types/models/DownloadFileRequest.ts | 4 +++- .../custom-playlists.component.ts | 2 +- .../recent-videos/recent-videos.component.ts | 16 +++++++++------- src/app/main/main.component.ts | 2 +- src/app/player/player.component.ts | 12 ++++++++---- src/app/posts.services.ts | 14 ++++++++++---- .../subscription/subscription.component.ts | 2 +- 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 70d56c5..866493e 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -127,6 +127,8 @@ paths: application/json: schema: $ref: '#/components/schemas/GetFileResponse' + '401': + description: User is not authorized to view the file. security: - Auth query parameter: [] /api/enableSharing: @@ -441,6 +443,8 @@ paths: responses: '200': description: 'The file itself is in the response, as well as an options object.' + '401': + description: User is not authorized to view the file. security: - Auth query parameter: [] /api/deleteFile: @@ -1254,8 +1258,12 @@ components: type: boolean uuid: type: string + uid: + type: string + id: + type: string subscriptionName: - type: boolean + type: string description: Only used for subscriptions subPlaylist: type: boolean diff --git a/src/api-types/models/DownloadFileRequest.ts b/src/api-types/models/DownloadFileRequest.ts index 18c6cac..61816c9 100644 --- a/src/api-types/models/DownloadFileRequest.ts +++ b/src/api-types/models/DownloadFileRequest.ts @@ -11,10 +11,12 @@ export interface DownloadFileRequest { outputName?: string; fullPathProvided?: boolean; uuid?: string; + uid?: string; + id?: string; /** * Only used for subscriptions */ - subscriptionName?: boolean; + subscriptionName?: string; /** * Only used for subscriptions */ diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index d2d65d6..3212708 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -71,7 +71,7 @@ export class CustomPlaylistsComponent implements OnInit { } downloadPlaylist(fileNames, type, zipName = null, playlistID = null) { - this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => { + this.postsService.downloadFileFromServer(fileNames, type, {outputName: zipName}).subscribe(res => { if (playlistID) { this.downloading_content[type][playlistID] = false }; const blob: Blob = res; saveAs(blob, zipName + '.zip'); diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 11a6dcc..b90fee5 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -190,13 +190,15 @@ export class RecentVideosComponent implements OnInit { const type = (file.isAudio ? 'audio' : 'video') as FileType; const ext = type === 'audio' ? '.mp3' : '.mp4' const sub = this.postsService.getSubscriptionByID(file.sub_id); - this.postsService.downloadFileFromServer(file.id, type, null, null, sub.name, sub.isPlaylist, - this.postsService.user ? this.postsService.user.uid : null, null).subscribe(res => { - const blob: Blob = res; - saveAs(blob, file.id + ext); - }, err => { - console.log(err); - }); + this.postsService.downloadFileFromServer( + file.id, type, + {subscriptionName: sub.name, subPlaylist: sub.isPlaylist, uid: this.postsService.user ? this.postsService.user.uid : null} + ).subscribe(res => { + const blob: Blob = res; + saveAs(blob, file.id + ext); + }, err => { + console.log(err); + }); } downloadNormalFile(file) { diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index c0bb894..e524057 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -773,7 +773,7 @@ export class MainComponent implements OnInit { } downloadPlaylist(fileNames, type, zipName = null, playlistID = null) { - this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => { + this.postsService.downloadFileFromServer(fileNames, type, {outputName: zipName}).subscribe(res => { if (playlistID) { this.downloading_content[type][playlistID] = false }; const blob: Blob = res; saveAs(blob, zipName + '.zip'); diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 57e2ea4..c6fb53d 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -318,8 +318,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const zipName = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0]; this.downloading = true; - this.postsService.downloadFileFromServer(fileNames, this.type, zipName, null, null, null, null, - !this.uuid ? this.postsService.user.uid : this.uuid, this.id).subscribe(res => { + this.postsService.downloadFileFromServer( + fileNames, this.type, + {outputName: zipName, uuid: !this.uuid ? this.postsService.user.uid : this.uuid, id: this.id} + ).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, zipName + '.zip'); @@ -333,8 +335,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const ext = (this.type === 'audio') ? '.mp3' : '.mp4'; const filename = this.playlist[0].title; this.downloading = true; - this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist, - this.is_shared ? this.db_file['uid'] : null, this.uuid).subscribe(res => { + this.postsService.downloadFileFromServer( + filename, this.type, + {subscriptionName: this.subscriptionName, subPlaylist: this.subPlaylist, uid: this.is_shared ? this.db_file['uid'] : null, uuid: this.uuid} + ).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, filename + ext); diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 976298c..be2f39e 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -282,8 +282,14 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions); } - downloadFileFromServer(fileName: string | string[], type: FileType, outputName: string = null, fullPathProvided: boolean = null, subscriptionName: boolean = null, subPlaylist: boolean = null, - uid = null, uuid: string = null, id = null) { + downloadFileFromServer( + fileName: string | string[], type: FileType, + options: {outputName?: string, fullPathProvided?: boolean, subscriptionName?: string, subPlaylist?: boolean, uid?: string, uuid?: string, id?: string} = {} + ) { + const {outputName = null, fullPathProvided = null, + subscriptionName = null, subPlaylist = null, + uid = null, uuid = null, id = null} = options; + const body: DownloadFileRequest = {fileNames: fileName, type: type, zip_mode: Array.isArray(fileName), @@ -291,11 +297,11 @@ export class PostsService implements CanActivate { fullPathProvided: fullPathProvided, subscriptionName: subscriptionName, subPlaylist: subPlaylist, + uid: uid, uuid: uuid, id: id, }; - return this.http.post(this.path + 'downloadFile', body, - {responseType: 'blob', params: this.httpOptions.params}); + return this.http.post(this.path + 'downloadFile', body, {responseType: 'blob', params: this.httpOptions.params}); } uploadCookiesFile(fileFormData) { diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index eb2f35b..2c8f9dd 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -152,7 +152,7 @@ export class SubscriptionComponent implements OnInit { } this.downloading = true; - this.postsService.downloadFileFromServer(fileNames, 'video' as FileType, this.subscription.name, true).subscribe(res => { + this.postsService.downloadFileFromServer(fileNames, 'video' as FileType, {outputName: this.subscription.name, fullPathProvided: true}).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, this.subscription.name + '.zip'); From 389c5b5df327b2054478a33632406af6470aae64 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Sat, 26 Sep 2020 14:50:39 -0700 Subject: [PATCH 009/209] Take out import type --- .../components/custom-playlists/custom-playlists.component.ts | 2 +- src/app/components/recent-videos/recent-videos.component.ts | 2 +- .../update-progress-dialog/update-progress-dialog.component.ts | 2 +- src/app/main/main.component.ts | 2 +- src/app/player/player.component.ts | 2 +- src/app/posts.services.ts | 2 +- src/app/subscription/subscription/subscription.component.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index 3212708..d4fd013 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -91,7 +91,7 @@ export class CustomPlaylistsComponent implements OnInit { this.getAllPlaylists(); }); } - + editPlaylistDialog(args) { const playlist = args.playlist; const index = args.index; diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index b90fee5..2bbd96b 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { Router } from '@angular/router'; -import type { FileType } from '../../../api-types'; +import { FileType } from '../../../api-types'; @Component({ selector: 'app-recent-videos', diff --git a/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts b/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts index 9ca066f..4862839 100644 --- a/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts +++ b/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { MatSnackBar } from '@angular/material/snack-bar'; -import type { UpdaterStatus } from '../../../api-types'; +import { UpdaterStatus } from '../../../api-types'; @Component({ selector: 'app-update-progress-dialog', diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index e524057..376d573 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -20,7 +20,7 @@ import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.com import { Platform } from '@angular/cdk/platform'; import { v4 as uuid } from 'uuid'; import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; -import type { FileType } from '../../api-types'; +import { FileType } from '../../api-types'; export let audioFilesMouseHovering = false; export let videoFilesMouseHovering = false; diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index c6fb53d..ca845c1 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -7,7 +7,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { InputDialogComponent } from 'app/input-dialog/input-dialog.component'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { ShareMediaDialogComponent } from '../dialogs/share-media-dialog/share-media-dialog.component'; -import type { FileType } from '../../api-types'; +import { FileType } from '../../api-types'; export interface IMedia { title: string; diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index be2f39e..8258ef7 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -10,7 +10,7 @@ import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; -import type { +import { ChangeRolePermissionsRequest, ChangeUserPermissionsRequest, ConfigResponse, diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 2c8f9dd..ffbbde5 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -3,7 +3,7 @@ import { PostsService } from 'app/posts.services'; import { ActivatedRoute, Router, ParamMap } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component'; -import type { FileType } from '../../../api-types'; +import { FileType } from '../../../api-types'; @Component({ selector: 'app-subscription', From 2cf0c61facd04d3311b64fd33eeea8e456d906f8 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Sat, 26 Sep 2020 14:53:51 -0700 Subject: [PATCH 010/209] Default booleans to false --- src/app/posts.services.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 8258ef7..66c8a38 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -286,8 +286,8 @@ export class PostsService implements CanActivate { fileName: string | string[], type: FileType, options: {outputName?: string, fullPathProvided?: boolean, subscriptionName?: string, subPlaylist?: boolean, uid?: string, uuid?: string, id?: string} = {} ) { - const {outputName = null, fullPathProvided = null, - subscriptionName = null, subPlaylist = null, + const {outputName = null, fullPathProvided = false, + subscriptionName = null, subPlaylist = false, uid = null, uuid = null, id = null} = options; const body: DownloadFileRequest = {fileNames: fileName, From a1b32e2851824b1b644e9ee7a21e5f201b764d42 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 20 Jan 2021 08:32:16 -0500 Subject: [PATCH 011/209] Added yt-dlp support Simplified update youtube-dl code --- backend/app.js | 58 +++++++++++++++++------- src/app/settings/settings.component.html | 3 +- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/backend/app.js b/backend/app.js index 04edd6a..209906e 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1659,11 +1659,24 @@ async function startYoutubeDL() { // auto updates the underlying youtube-dl binary, not YoutubeDL-Material async function autoUpdateYoutubeDL() { + const download_sources = { + 'youtube-dl': { + 'tags_url': 'https://api.github.com/repos/ytdl-org/youtube-dl/tags', + 'func': downloadLatestYoutubeDLBinary + }, + 'youtube-dlc': { + 'tags_url': 'https://api.github.com/repos/blackjack4494/yt-dlc/tags', + 'func': downloadLatestYoutubeDLCBinary + }, + 'yt-dlp': { + 'tags_url': 'https://api.github.com/repos/pukkandan/yt-dlp/tags', + 'func': downloadLatestYoutubeDLPBinary + } + } return new Promise(async resolve => { const default_downloader = config_api.getConfigItem('ytdl_default_downloader'); const using_youtube_dlc = default_downloader === 'youtube-dlc'; - const youtube_dl_tags_url = 'https://api.github.com/repos/ytdl-org/youtube-dl/tags' - const youtube_dlc_tags_url = 'https://api.github.com/repos/blackjack4494/yt-dlc/tags' + const tags_url = download_sources[default_downloader]['tags_url']; // get current version let current_app_details_path = 'node_modules/youtube-dl/bin/details'; let current_app_details_exists = fs.existsSync(current_app_details_path); @@ -1674,6 +1687,7 @@ async function autoUpdateYoutubeDL() { } let current_app_details = JSON.parse(fs.readFileSync(current_app_details_path)); let current_version = current_app_details['version']; + let current_downloader = current_app_details['downloader']; let stored_binary_path = current_app_details['path']; if (!stored_binary_path || typeof stored_binary_path !== 'string') { // logger.info(`INFO: Failed to get youtube-dl binary path at location: ${current_app_details_path}, attempting to guess actual path...`); @@ -1690,15 +1704,9 @@ async function autoUpdateYoutubeDL() { } // got version, now let's check the latest version from the youtube-dl API - let youtubedl_api_path = using_youtube_dlc ? youtube_dlc_tags_url : youtube_dl_tags_url; - if (default_downloader === 'youtube-dl') { - await downloadLatestYoutubeDLBinary('unknown', 'unknown'); - resolve(true); - return; - } - fetch(youtubedl_api_path, {method: 'Get'}) + fetch(tags_url, {method: 'Get'}) .then(async res => res.json()) .then(async (json) => { // check if the versions are different @@ -1708,16 +1716,16 @@ async function autoUpdateYoutubeDL() { return false; } const latest_update_version = json[0]['name']; - if (current_version !== latest_update_version) { - // versions different, download new update + if (current_version !== latest_update_version || default_downloader !== current_downloader) { + // versions different or different downloader is being used, download new update logger.info(`Found new update for ${default_downloader}. Updating binary...`); try { await checkExistsWithTimeout(stored_binary_path, 10000); } catch(e) { logger.error(`Failed to update ${default_downloader} - ${e}`); } - if (using_youtube_dlc) await downloadLatestYoutubeDLCBinary(latest_update_version); - else await downloadLatestYoutubeDLBinary(current_version, latest_update_version); + + await download_sources[default_downloader]['func'](latest_update_version); resolve(true); } else { @@ -1731,7 +1739,7 @@ async function autoUpdateYoutubeDL() { }); } -async function downloadLatestYoutubeDLBinary(current_version, new_version) { +async function downloadLatestYoutubeDLBinary() { return new Promise(resolve => { let binary_path = 'node_modules/youtube-dl/bin'; downloader(binary_path, function error(err, done) { @@ -1741,6 +1749,7 @@ async function downloadLatestYoutubeDLBinary(current_version, new_version) { resolve(false); } logger.info(`youtube-dl successfully updated!`); + updateDetailsJSON(null, 'youtube-dl'); resolve(true); }); }); @@ -1754,10 +1763,25 @@ async function downloadLatestYoutubeDLCBinary(new_version) { await fetchFile(download_url, output_path, `youtube-dlc ${new_version}`); - const details_path = 'node_modules/youtube-dl/bin/details'; - const details_json = fs.readJSONSync('node_modules/youtube-dl/bin/details'); - details_json['version'] = new_version; + updateDetailsJSON(new_version, 'youtube-dlc'); +} +async function downloadLatestYoutubeDLPBinary(new_version) { + const file_ext = is_windows ? '.exe' : ''; + + const download_url = `https://github.com/pukkandan/yt-dlp/releases/latest/download/youtube-dlc${file_ext}`; + const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`; + + await fetchFile(download_url, output_path, `yt-dlp ${new_version}`); + + updateDetailsJSON(new_version, 'yt-dlp'); +} + +function updateDetailsJSON(new_version, downloader) { + const details_path = 'node_modules/youtube-dl/bin/details'; + const details_json = fs.readJSONSync(details_path); + if (new_version) details_json['version'] = new_version; + details_json['downloader'] = downloader; fs.writeJSONSync(details_path, details_json); } diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index bd085b1..fe7d81c 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -286,8 +286,9 @@ Select a downloader - youtube-dlc youtube-dl + youtube-dlc + yt-dlp From c660c284229be2c6f0132aa9c2ca8439d6871a17 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 22 Feb 2021 12:53:21 -0700 Subject: [PATCH 012/209] youtube-dl now updates in the same way as the other forks --- backend/app.js | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/backend/app.js b/backend/app.js index 209906e..e639ff1 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1681,9 +1681,8 @@ async function autoUpdateYoutubeDL() { let current_app_details_path = 'node_modules/youtube-dl/bin/details'; let current_app_details_exists = fs.existsSync(current_app_details_path); if (!current_app_details_exists) { - logger.error(`Failed to get youtube-dl binary details at location '${current_app_details_path}'. Cancelling update check.`); - resolve(false); - return; + logger.warn(`Failed to get youtube-dl binary details at location '${current_app_details_path}'. Generating file...`); + fs.writeJSONSync('node_modules/youtube-dl/bin/details', {"version":"2020.00.00", "downloader": default_downloader}); } let current_app_details = JSON.parse(fs.readFileSync(current_app_details_path)); let current_version = current_app_details['version']; @@ -1739,20 +1738,15 @@ async function autoUpdateYoutubeDL() { }); } -async function downloadLatestYoutubeDLBinary() { - return new Promise(resolve => { - let binary_path = 'node_modules/youtube-dl/bin'; - downloader(binary_path, function error(err, done) { - if (err) { - logger.error(`youtube-dl failed to update. Restart the server to try again.`); - logger.error(err); - resolve(false); - } - logger.info(`youtube-dl successfully updated!`); - updateDetailsJSON(null, 'youtube-dl'); - resolve(true); - }); - }); +async function downloadLatestYoutubeDLBinary(new_version) { + const file_ext = is_windows ? '.exe' : ''; + + const download_url = `https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl${file_ext}`; + const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`; + + await fetchFile(download_url, output_path, `youtube-dl ${new_version}`); + + updateDetailsJSON(new_version, 'youtube-dl'); } async function downloadLatestYoutubeDLCBinary(new_version) { From 1f0153b17edae9000cb0b0f58d12d082853b8291 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 16 Mar 2021 20:06:05 -0600 Subject: [PATCH 013/209] Subscription videos being downloaded will get registered into the database as they are added to avoid having to wait until the subscription completes --- backend/db.js | 24 ++++++++++++++++++++++++ backend/subscriptions.js | 13 ++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/db.js b/backend/db.js index 16a8d0c..c8ef8fb 100644 --- a/backend/db.js +++ b/backend/db.js @@ -213,6 +213,29 @@ async function importUnregisteredFiles() { } +async function preimportUnregisteredSubscriptionFile(sub, appendedBasePath) { + const preimported_file_paths = []; + + let dbPath = null; + if (sub.user_uid) + dbPath = users_db.get('users').find({uid: sub.user_uid}).get('subscriptions').find({id: sub.id}).get('videos'); + else + dbPath = db.get('subscriptions').find({id: sub.id}).get('videos'); + + const files = await utils.getDownloadedFilesByType(appendedBasePath, sub.type); + files.forEach(file => { + // check if file exists in db, if not add it + const file_is_registered = !!(dbPath.find({id: file.id}).value()) + if (!file_is_registered) { + // add additional info + registerFileDBManual(dbPath, file); + preimported_file_paths.push(file['path']); + logger.verbose(`Preemptively added subscription file to the database: ${file.id}`); + } + }); + return preimported_file_paths; +} + async function getVideo(file_uid, uuid, sub_id) { const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; const sub_db_path = sub_id ? base_db_path.get('subscriptions').find({id: sub_id}).get('videos') : base_db_path.get('files'); @@ -235,6 +258,7 @@ module.exports = { updatePlaylist: updatePlaylist, getFileDirectoriesAndDBs: getFileDirectoriesAndDBs, importUnregisteredFiles: importUnregisteredFiles, + preimportUnregisteredSubscriptionFile: preimportUnregisteredSubscriptionFile, getVideo: getVideo, setVideoProperty: setVideoProperty } diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 3a6e0a3..7ff1d06 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -277,6 +277,7 @@ async function getVideosForSub(sub, user_uid = null) { basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); let appendedBasePath = getAppendedBasePath(sub, basePath); + fs.ensureDirSync(appendedBasePath); let multiUserMode = null; if (user_uid) { @@ -292,8 +293,17 @@ async function getVideosForSub(sub, user_uid = null) { logger.verbose('Subscription: getting videos for subscription ' + sub.name); return new Promise(resolve => { + const preimported_file_paths = []; + const PREIMPORT_INTERVAL = 5000; + const preregister_check = setInterval(() => { + if (sub.streamingOnly) return; + db_api.preimportUnregisteredSubscriptionFile(sub, appendedBasePath); + }, PREIMPORT_INTERVAL); youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, async function(err, output) { + // cleanup updateSubscriptionProperty(sub, {downloading: false}, user_uid); + clearInterval(preregister_check); + logger.verbose('Subscription: finished check for ' + sub.name); if (err && !output) { logger.error(err.stderr ? err.stderr : err.message); @@ -337,7 +347,7 @@ async function getVideosForSub(sub, user_uid = null) { } const reset_videos = i === 0; - handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos); + handleOutputJSON(sub, sub_db, output_json, multiUserMode, preimported_file_paths, reset_videos); } if (config_api.getConfigItem('ytdl_subscriptions_redownload_fresh_uploads')) { @@ -351,6 +361,7 @@ async function getVideosForSub(sub, user_uid = null) { }, err => { logger.error(err); updateSubscriptionProperty(sub, {downloading: false}, user_uid); + clearInterval(preregister_check); }); } From d11f77a6c9a6c8e420fb99153dbf383be70da58b Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 16 Mar 2021 22:16:57 -0600 Subject: [PATCH 014/209] Updated yt-dlp paths --- backend/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index e639ff1..29781e7 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1669,7 +1669,7 @@ async function autoUpdateYoutubeDL() { 'func': downloadLatestYoutubeDLCBinary }, 'yt-dlp': { - 'tags_url': 'https://api.github.com/repos/pukkandan/yt-dlp/tags', + 'tags_url': 'https://api.github.com/repos/yt-dlp/yt-dlp/tags', 'func': downloadLatestYoutubeDLPBinary } } @@ -1763,7 +1763,7 @@ async function downloadLatestYoutubeDLCBinary(new_version) { async function downloadLatestYoutubeDLPBinary(new_version) { const file_ext = is_windows ? '.exe' : ''; - const download_url = `https://github.com/pukkandan/yt-dlp/releases/latest/download/youtube-dlc${file_ext}`; + const download_url = `https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp${file_ext}`; const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`; await fetchFile(download_url, output_path, `yt-dlp ${new_version}`); From 4643efbae04a6c00a1a29be2d96cb7e0dc12c836 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 16 Mar 2021 22:41:07 -0600 Subject: [PATCH 015/209] Added ability to restart the server from the frontend Dockerfile/entrypoint.sh now uses nodemon enabling restarting from the UI in a container --- Dockerfile | 2 +- backend/app.js | 21 ++++++++++++++------- backend/entrypoint.sh | 2 +- backend/package.json | 3 ++- src/app/posts.services.ts | 4 ++++ src/app/settings/settings.component.html | 8 ++++++++ src/app/settings/settings.component.ts | 8 ++++++++ 7 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 75b22d3..477ca3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,4 +40,4 @@ COPY --chown=$UID:$GID [ "/backend/", "/app/" ] EXPOSE 17442 ENTRYPOINT [ "/app/entrypoint.sh" ] -CMD [ "node", "app.js" ] +CMD [ "npm", "start" ] diff --git a/backend/app.js b/backend/app.js index 888ccf7..f62d86c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -142,15 +142,17 @@ var timestamp_server_start = Date.now(); if (debugMode) logger.info('YTDL-Material in debug mode!'); // check if just updated -const just_restarted = fs.existsSync('restart.json'); -if (just_restarted) { +const just_updated = fs.existsSync('restart_update.json'); +if (just_updated) { updaterStatus = { updating: false, details: 'Update complete! You are now on ' + CONSTS['CURRENT_VERSION'] } - fs.unlinkSync('restart.json'); + fs.unlinkSync('restart_update.json'); } +if (fs.existsSync('restart_general.json')) fs.unlinkSync('restart_general.json'); + // updates & starts youtubedl (commented out b/c of repo takedown) // startYoutubeDL(); @@ -332,7 +334,7 @@ async function startServer() { }); } -async function restartServer() { +async function restartServer(is_update = false) { const restartProcess = () => { spawn('node', ['app.js'], { detached: true, @@ -340,10 +342,11 @@ async function restartServer() { }).unref() process.exit() } - logger.info('Update complete! Restarting server...'); + + logger.info(`${is_update ? 'Update complete! ' : ''}Restarting server...`); // the following line restarts the server through nodemon - fs.writeFileSync('restart.json', 'internal use only'); + fs.writeFileSync(`restart${is_update ? '_update' : '_general'}.json`, 'internal use only'); } async function updateServer(tag) { @@ -386,7 +389,7 @@ async function updateServer(tag) { updating: true, 'details': 'Update complete! Restarting server...' } - restartServer(); + restartServer(true); }, err => { updaterStatus = { updating: false, @@ -1898,7 +1901,11 @@ app.post('/api/setConfig', optionalJwt, function(req, res) { logger.error('Tried to save invalid config file!') res.sendStatus(400); } +}); +app.post('/api/restartServer', optionalJwt, (req, res) => { + restartServer(); + res.send({success: true}); }); app.post('/api/tomp3', optionalJwt, async function(req, res) { diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index d32ec93..16c37ce 100755 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -CMD="node app.js" +CMD="npm start" # if the first arg starts with "-" pass it to program if [ "${1#-}" != "$1" ]; then diff --git a/backend/package.json b/backend/package.json index 862a09c..496159e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,7 +14,8 @@ "public/*" ], "watch": [ - "restart.json" + "restart_update.json", + "restart_general.json" ] }, "repository": { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index a6c2954..1fd7929 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -199,6 +199,10 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'killAllDownloads', {}, this.httpOptions); } + restartServer() { + return this.http.post(this.path + 'restartServer', {}, this.httpOptions); + } + loadNavItems() { if (isDevMode()) { return this.http.get('./assets/default.json'); diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index a7d69d6..346797c 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -353,6 +353,14 @@
+ +
+
+
+ +
+
+
diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 2ef36fa..0537103 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -255,6 +255,14 @@ export class SettingsComponent implements OnInit { }); } + restartServer() { + this.postsService.restartServer().subscribe(res => { + this.postsService.openSnackBar('Restarting!'); + }, err => { + this.postsService.openSnackBar('Failed to restart the server.'); + }); + } + // snackbar helper public openSnackBar(message: string, action: string = '') { this.snackBar.open(message, action, { From 4c06bc750ceb386c963c14cfe4d735ca6e1f59fe Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 17 Mar 2021 19:13:52 -0600 Subject: [PATCH 016/209] Fixed issue where on some Docker environments the container failed to start due to the error "nodemon update check failed" --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 477ca3e..db776d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,8 @@ ENV UID=1000 \ GID=1000 \ USER=youtube +ENV NO_UPDATE_NOTIFIER=true + RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID RUN apk add --no-cache \ From 4c1f975eaed45f908096539fe9057b2d7cf522df Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 18 Mar 2021 19:29:03 -0600 Subject: [PATCH 017/209] Force nodemon to install during the container setup Docker now starts through nodemon directly --- Dockerfile | 4 ++-- backend/entrypoint.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index db776d4..2a36777 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:3.12 as frontend RUN apk add --no-cache \ npm -RUN npm install -g @angular/cli +RUN npm install -g @angular/cli nodemon WORKDIR /build COPY [ "package.json", "package-lock.json", "/build/" ] @@ -42,4 +42,4 @@ COPY --chown=$UID:$GID [ "/backend/", "/app/" ] EXPOSE 17442 ENTRYPOINT [ "/app/entrypoint.sh" ] -CMD [ "npm", "start" ] +CMD [ "nodemon", "app.js" ] diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 16c37ce..d30d4fc 100755 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -CMD="npm start" +CMD="nodemon app.js" # if the first arg starts with "-" pass it to program if [ "${1#-}" != "$1" ]; then From aefdde5401e2a11824406b8ccd48f8fcfebf1a83 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 18 Mar 2021 20:59:46 -0600 Subject: [PATCH 018/209] Fixed issue (hopefully) where nodemon is not properly installed on Docker --- Dockerfile | 2 +- backend/package.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2a36777..7414477 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:3.12 as frontend RUN apk add --no-cache \ npm -RUN npm install -g @angular/cli nodemon +RUN npm install -g @angular/cli WORKDIR /build COPY [ "package.json", "package-lock.json", "/build/" ] diff --git a/backend/package.json b/backend/package.json index 496159e..2f2e21f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,8 +4,9 @@ "description": "backend for YoutubeDL-Material", "main": "index.js", "scripts": { + "preinstall": "npm i nodemon -g", "test": "echo \"Error: no test specified\" && exit 1", - "start": "nodemon -q app.js" + "start": "nodemon app.js" }, "nodemonConfig": { "ignore": [ @@ -48,7 +49,7 @@ "multer": "^1.4.2", "node-fetch": "^2.6.1", "node-id3": "^0.1.14", - "nodemon": "^2.0.2", + "nodemon": "^2.0.7", "passport": "^0.4.1", "passport-http": "^0.3.0", "passport-jwt": "^4.0.0", From addd54fefd062382b65efe33d9bacb0ee3b80182 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 20 Mar 2021 16:22:59 -0600 Subject: [PATCH 019/209] Switched nodemon to foreverjs to hopefully enable restarting internally and fix runtime errors --- Dockerfile | 3 ++- backend/app.js | 9 +-------- backend/entrypoint.sh | 2 +- backend/package.json | 1 - 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7414477..a17794b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,7 @@ RUN apk add --no-cache \ WORKDIR /app COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ] +RUN npm install forever -g RUN npm install && chown -R $UID:$GID ./ COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ] @@ -42,4 +43,4 @@ COPY --chown=$UID:$GID [ "/backend/", "/app/" ] EXPOSE 17442 ENTRYPOINT [ "/app/entrypoint.sh" ] -CMD [ "nodemon", "app.js" ] +CMD [ "forever", "app.js" ] diff --git a/backend/app.js b/backend/app.js index f62d86c..c5e2d11 100644 --- a/backend/app.js +++ b/backend/app.js @@ -335,18 +335,11 @@ async function startServer() { } async function restartServer(is_update = false) { - const restartProcess = () => { - spawn('node', ['app.js'], { - detached: true, - stdio: 'inherit' - }).unref() - process.exit() - } - logger.info(`${is_update ? 'Update complete! ' : ''}Restarting server...`); // the following line restarts the server through nodemon fs.writeFileSync(`restart${is_update ? '_update' : '_general'}.json`, 'internal use only'); + process.exit(1); } async function updateServer(tag) { diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index d30d4fc..611bcc8 100755 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -CMD="nodemon app.js" +CMD="forever app.js" # if the first arg starts with "-" pass it to program if [ "${1#-}" != "$1" ]; then diff --git a/backend/package.json b/backend/package.json index 2f2e21f..a38315d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,7 +4,6 @@ "description": "backend for YoutubeDL-Material", "main": "index.js", "scripts": { - "preinstall": "npm i nodemon -g", "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon app.js" }, From 4ba471074140f99b07c83fb65b6883b7bfa0a240 Mon Sep 17 00:00:00 2001 From: Ben Ashby Date: Fri, 26 Mar 2021 09:46:20 -0600 Subject: [PATCH 020/209] Added helm chart --- ${HOME}/.helm/starters/sitewards/chart | 1 + chart/.helmignore | 23 ++++ chart/Chart.yaml | 24 ++++ chart/templates/NOTES.txt | 22 +++ chart/templates/_helpers.tpl | 62 +++++++++ chart/templates/appdata-pvc.yaml | 21 +++ chart/templates/audio-pvc.yaml | 21 +++ chart/templates/deployment.yaml | 59 ++++++++ chart/templates/ingress.yaml | 41 ++++++ chart/templates/service.yaml | 15 +++ chart/templates/serviceaccount.yaml | 12 ++ chart/templates/subscriptions-pvc.yaml | 21 +++ chart/templates/tests/test-connection.yaml | 15 +++ chart/templates/users-pvc.yaml | 21 +++ chart/templates/video-pvc.yaml | 21 +++ chart/values.yaml | 148 +++++++++++++++++++++ 16 files changed, 527 insertions(+) create mode 160000 ${HOME}/.helm/starters/sitewards/chart create mode 100644 chart/.helmignore create mode 100644 chart/Chart.yaml create mode 100644 chart/templates/NOTES.txt create mode 100644 chart/templates/_helpers.tpl create mode 100644 chart/templates/appdata-pvc.yaml create mode 100644 chart/templates/audio-pvc.yaml create mode 100644 chart/templates/deployment.yaml create mode 100644 chart/templates/ingress.yaml create mode 100644 chart/templates/service.yaml create mode 100644 chart/templates/serviceaccount.yaml create mode 100644 chart/templates/subscriptions-pvc.yaml create mode 100644 chart/templates/tests/test-connection.yaml create mode 100644 chart/templates/users-pvc.yaml create mode 100644 chart/templates/video-pvc.yaml create mode 100644 chart/values.yaml diff --git a/${HOME}/.helm/starters/sitewards/chart b/${HOME}/.helm/starters/sitewards/chart new file mode 160000 index 0000000..bde4ce4 --- /dev/null +++ b/${HOME}/.helm/starters/sitewards/chart @@ -0,0 +1 @@ +Subproject commit bde4ce4a00cedaaa88d13fd123a6e2a6b5827427 diff --git a/chart/.helmignore b/chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..a86aa60 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: youtubedl-material +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "4.2" diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt new file mode 100644 index 0000000..bf07841 --- /dev/null +++ b/chart/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "youtubedl-material.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "youtubedl-material.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "youtubedl-material.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "youtubedl-material.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..883b89e --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "youtubedl-material.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "youtubedl-material.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "youtubedl-material.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "youtubedl-material.labels" -}} +helm.sh/chart: {{ include "youtubedl-material.chart" . }} +{{ include "youtubedl-material.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "youtubedl-material.selectorLabels" -}} +app.kubernetes.io/name: {{ include "youtubedl-material.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "youtubedl-material.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "youtubedl-material.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/chart/templates/appdata-pvc.yaml b/chart/templates/appdata-pvc.yaml new file mode 100644 index 0000000..0cd5e0e --- /dev/null +++ b/chart/templates/appdata-pvc.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.persistence.appData.enabled (not .Values.persistence.appData.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "youtubedl-material.fullname" . }}-appdata + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.appData.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.appData.size | quote }} + {{- if .Values.persistence.appData.storageClass }} + {{- if (eq "-" .Values.persistence.appData.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.appData.storageClass }}" + {{- end }} + {{- end }} + {{- end -}} diff --git a/chart/templates/audio-pvc.yaml b/chart/templates/audio-pvc.yaml new file mode 100644 index 0000000..2de3d03 --- /dev/null +++ b/chart/templates/audio-pvc.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.persistence.audio.enabled (not .Values.persistence.audio.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "youtubedl-material.fullname" . }}-audio + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.audio.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.audio.size | quote }} + {{- if .Values.persistence.audio.storageClass }} + {{- if (eq "-" .Values.persistence.audio.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.audio.storageClass }}" + {{- end }} + {{- end }} + {{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml new file mode 100644 index 0000000..02f2fae --- /dev/null +++ b/chart/templates/deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "youtubedl-material.fullname" . }} + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "youtubedl-material.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "youtubedl-material.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "youtubedl-material.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 17442 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 0000000..79b9ece --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "youtubedl-material.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + backend: + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 0000000..01df5d2 --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "youtubedl-material.fullname" . }} + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "youtubedl-material.selectorLabels" . | nindent 4 }} diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml new file mode 100644 index 0000000..e04cc5e --- /dev/null +++ b/chart/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "youtubedl-material.serviceAccountName" . }} + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/chart/templates/subscriptions-pvc.yaml b/chart/templates/subscriptions-pvc.yaml new file mode 100644 index 0000000..ad5768c --- /dev/null +++ b/chart/templates/subscriptions-pvc.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.persistence.subscriptions.enabled (not .Values.persistence.subscriptions.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "youtubedl-material.fullname" . }}-subscriptions + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.subscriptions.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.subscriptions.size | quote }} + {{- if .Values.persistence.subscriptions.storageClass }} + {{- if (eq "-" .Values.persistence.subscriptions.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.subscriptions.storageClass }}" + {{- end }} + {{- end }} + {{- end -}} diff --git a/chart/templates/tests/test-connection.yaml b/chart/templates/tests/test-connection.yaml new file mode 100644 index 0000000..3e4b1ba --- /dev/null +++ b/chart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "youtubedl-material.fullname" . }}-test-connection" + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "youtubedl-material.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/chart/templates/users-pvc.yaml b/chart/templates/users-pvc.yaml new file mode 100644 index 0000000..c12c116 --- /dev/null +++ b/chart/templates/users-pvc.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.persistence.users.enabled (not .Values.persistence.users.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "youtubedl-material.fullname" . }}-users + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.users.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.users.size | quote }} + {{- if .Values.persistence.users.storageClass }} + {{- if (eq "-" .Values.persistence.users.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.users.storageClass }}" + {{- end }} + {{- end }} + {{- end -}} diff --git a/chart/templates/video-pvc.yaml b/chart/templates/video-pvc.yaml new file mode 100644 index 0000000..92718ee --- /dev/null +++ b/chart/templates/video-pvc.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.persistence.video.enabled (not .Values.persistence.video.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "youtubedl-material.fullname" . }}-video + labels: + {{- include "youtubedl-material.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.video.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.video.size | quote }} + {{- if .Values.persistence.video.storageClass }} + {{- if (eq "-" .Values.persistence.video.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.video.storageClass }}" + {{- end }} + {{- end }} + {{- end -}} diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..fcb8b99 --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,148 @@ +# Default values for youtubedl-material. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: tzahi12345/youtubedl-material + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 17442 + +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: [] + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +persistence: + appData: + enabled: true + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + accessMode: ReadWriteOnce + size: 1Gi + audio: + enabled: true + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + ## + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + accessMode: ReadWriteOnce + size: 50Gi + video: + enabled: true + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + ## + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + accessMode: ReadWriteOnce + size: 50Gi + subscriptions: + enabled: true + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + ## + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + accessMode: ReadWriteOnce + size: 50Gi + users: + enabled: true + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + ## + ## If you want to reuse an existing claim, you can pass the name of the PVC using + ## the existingClaim variable + # existingClaim: your-claim + accessMode: ReadWriteOnce + size: 50Gi + +nodeSelector: {} + +tolerations: [] + +affinity: {} From 59c9237be56101332df6f29915efe1060dddd2d6 Mon Sep 17 00:00:00 2001 From: Ben Ashby Date: Fri, 26 Mar 2021 09:59:02 -0600 Subject: [PATCH 021/209] integrated pvc's --- chart/templates/appdata-pvc.yaml | 12 +++---- chart/templates/deployment.yaml | 62 ++++++++++++++++++++++++++++++++ chart/values.yaml | 7 +++- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/chart/templates/appdata-pvc.yaml b/chart/templates/appdata-pvc.yaml index 0cd5e0e..e426650 100644 --- a/chart/templates/appdata-pvc.yaml +++ b/chart/templates/appdata-pvc.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.persistence.appData.enabled (not .Values.persistence.appData.existingClaim) }} +{{- if and .Values.persistence.appdata.enabled (not .Values.persistence.appdata.existingClaim) }} kind: PersistentVolumeClaim apiVersion: v1 metadata: @@ -7,15 +7,15 @@ metadata: {{- include "youtubedl-material.labels" . | nindent 4 }} spec: accessModes: - - {{ .Values.persistence.appData.accessMode | quote }} + - {{ .Values.persistence.appdata.accessMode | quote }} resources: requests: - storage: {{ .Values.persistence.appData.size | quote }} - {{- if .Values.persistence.appData.storageClass }} - {{- if (eq "-" .Values.persistence.appData.storageClass) }} + storage: {{ .Values.persistence.appdata.size | quote }} + {{- if .Values.persistence.appdata.storageClass }} + {{- if (eq "-" .Values.persistence.appdata.storageClass) }} storageClassName: "" {{- else }} - storageClassName: "{{ .Values.persistence.appData.storageClass }}" + storageClassName: "{{ .Values.persistence.appdata.storageClass }}" {{- end }} {{- end }} {{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 02f2fae..4d37b75 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -45,6 +45,68 @@ spec: port: http resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - mountPath: /app/appdata + name: appdata + {{- if .Values.persistence.appdata.subPath }} + subPath: {{ .Values.persistence.appdata.subPath }} + {{- end }} + - mountPath: /app/audio + name: audio + {{- if .Values.persistence.audio.subPath }} + subPath: {{ .Values.persistence.audio.subPath }} + {{- end }} + - mountPath: /app/video + name: video + {{- if .Values.persistence.video.subPath }} + subPath: {{ .Values.persistence.video.subPath }} + {{- end }} + - mountPath: /app/subscriptions + name: subscriptions + {{- if .Values.persistence.subscriptions.subPath }} + subPath: {{ .Values.persistence.subscriptions.subPath }} + {{- end }} + - mountPath: /app/users + name: users + {{- if .Values.persistence.users.subPath }} + subPath: {{ .Values.persistence.users.subPath }} + {{- end }} + volumes: + - name: appdata + {{- if .Values.persistence.appdata.enabled}} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.appdata.existingClaim }}{{ .Values.persistence.appdata.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-appdata{{- end }} + {{- else }} + emptyDir: {} + {{- end }} + - name: audio + {{- if .Values.persistence.audio.enabled}} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.audio.existingClaim }}{{ .Values.persistence.audio.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-audio{{- end }} + {{- else }} + emptyDir: {} + {{- end }} + - name: subscriptions + {{- if .Values.persistence.subscriptions.enabled}} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.subscriptions.existingClaim }}{{ .Values.persistence.subscriptions.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-subscriptions{{- end }} + {{- else }} + emptyDir: {} + {{- end }} + - name: users + {{- if .Values.persistence.users.enabled}} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.users.existingClaim }}{{ .Values.persistence.users.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-users{{- end }} + {{- else }} + emptyDir: {} + {{- end }} + - name: video + {{- if .Values.persistence.video.enabled}} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.video.existingClaim }}{{ .Values.persistence.video.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-video{{- end }} + {{- else }} + emptyDir: {} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/chart/values.yaml b/chart/values.yaml index fcb8b99..b192e16 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -66,7 +66,7 @@ resources: {} # memory: 128Mi persistence: - appData: + appdata: enabled: true ## If defined, storageClassName: ## If set to "-", storageClassName: "", which disables dynamic provisioning @@ -78,6 +78,7 @@ persistence: ## If you want to reuse an existing claim, you can pass the name of the PVC using ## the existingClaim variable # existingClaim: your-claim + # subPath: some-subpath accessMode: ReadWriteOnce size: 1Gi audio: @@ -93,6 +94,7 @@ persistence: ## If you want to reuse an existing claim, you can pass the name of the PVC using ## the existingClaim variable # existingClaim: your-claim + # subPath: some-subpath accessMode: ReadWriteOnce size: 50Gi video: @@ -108,6 +110,7 @@ persistence: ## If you want to reuse an existing claim, you can pass the name of the PVC using ## the existingClaim variable # existingClaim: your-claim + # subPath: some-subpath accessMode: ReadWriteOnce size: 50Gi subscriptions: @@ -123,6 +126,7 @@ persistence: ## If you want to reuse an existing claim, you can pass the name of the PVC using ## the existingClaim variable # existingClaim: your-claim + # subPath: some-subpath accessMode: ReadWriteOnce size: 50Gi users: @@ -138,6 +142,7 @@ persistence: ## If you want to reuse an existing claim, you can pass the name of the PVC using ## the existingClaim variable # existingClaim: your-claim + # subPath: some-subpath accessMode: ReadWriteOnce size: 50Gi From 4e07440ed2172e54c2ccb068e8f39b81cc07bd45 Mon Sep 17 00:00:00 2001 From: Ben Ashby Date: Sat, 27 Mar 2021 16:34:14 -0600 Subject: [PATCH 022/209] Removed Accidental Dir --- ${HOME}/.helm/starters/sitewards/chart | 1 - 1 file changed, 1 deletion(-) delete mode 160000 ${HOME}/.helm/starters/sitewards/chart diff --git a/${HOME}/.helm/starters/sitewards/chart b/${HOME}/.helm/starters/sitewards/chart deleted file mode 160000 index bde4ce4..0000000 --- a/${HOME}/.helm/starters/sitewards/chart +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bde4ce4a00cedaaa88d13fd123a6e2a6b5827427 From 356a807cad1fc1cdd6530a6ec85cd0af11451c50 Mon Sep 17 00:00:00 2001 From: s55ma Date: Sun, 28 Mar 2021 17:33:47 +0200 Subject: [PATCH 023/209] Update README.md Some packages are missing for Ubuntu/Debian install, especially python. Without python package, you get the following error when trying to download from youtube: 2021-03-28T15:28:30.461Z ERROR: Error while retrieving info on video with URL https://www.youtube.com/watch?v=[some_ID] with the following message: Error: Command failed with exit code 127: /root/youtubedl-material/node_modules/youtube-dl/bin/youtube-dl --dump-json -o video/%(title)s.mp4 --write-info-json --print-json -f bestvideo+bestaudio --merge-output-format mp4 --write-thumbnail http://www.youtube.com/watch?v=[some_ID] 2021-03-28T15:28:30.461Z ERROR: /usr/bin/env: 'python': No such file or directory --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c073161..1c4dcae 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ NOTE: If you would like to use Docker, you can skip down to the [Docker](#Docker Debian/Ubuntu: ```bash -sudo apt-get install nodejs youtube-dl ffmpeg +sudo apt-get install nodejs youtube-dl ffmpeg unzip python npm ``` CentOS 7: From 49925848ffb2b46d0c815424118af6ada12d0f64 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 28 Mar 2021 15:51:53 -0400 Subject: [PATCH 024/209] Material Icons are now hosted locally to avoid requesting them from Google for proxied users --- package-lock.json | 7 ++++++- package.json | 1 + src/index.html | 1 - src/styles.scss | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0e3621..7ca348b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "youtube-dl-material", - "version": "4.1.0", + "version": "4.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -7775,6 +7775,11 @@ } } }, + "material-icons": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-0.5.4.tgz", + "integrity": "sha512-5ycazkNmIOtV78Ff3WgvxQESoJuujdRm0cNbf18fmyJN20jHyqp9rpwi4EfQyGimag0ZLElxtVg3H9enIKdOOw==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", diff --git a/package.json b/package.json index ab15811..e55369d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "file-saver": "^2.0.2", "filesize": "^6.1.0", "fingerprintjs2": "^2.1.0", + "material-icons": "^0.5.4", "nan": "^2.14.1", "ng-lazyload-image": "^7.0.1", "ngx-avatar": "^4.0.0", diff --git a/src/index.html b/src/index.html index 69fa759..4bc4bbb 100644 --- a/src/index.html +++ b/src/index.html @@ -10,7 +10,6 @@ - diff --git a/src/styles.scss b/src/styles.scss index 9a776a1..41ef9ea 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,5 +1,7 @@ /* You can add global styles to this file, and also import other style files */ +@import '~material-icons/iconfont/material-icons.css'; + @import '@angular/material/prebuilt-themes/indigo-pink.css'; //@import './app-theme'; From de154a9c3eaf55e037cc42d0fbd72a78afc05584 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 12 May 2021 21:56:21 -0600 Subject: [PATCH 025/209] Updated dockerfile to fix UID/GID bug related to forever.js --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index a17794b..68d648d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ ENV UID=1000 \ USER=youtube ENV NO_UPDATE_NOTIFIER=true +ENV FOREVER_ROOT=/app/.forever RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID From b3744e616d548738c999c26a9510cb85420873ba Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 12 May 2021 22:52:46 -0600 Subject: [PATCH 026/209] Users can now stream videos concurrently with other users with the new concurrent stream component --- src/app/app.module.ts | 4 +- .../concurrent-stream.component.html | 6 + .../concurrent-stream.component.scss | 7 + .../concurrent-stream.component.spec.ts | 25 ++++ .../concurrent-stream.component.ts | 140 ++++++++++++++++++ 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/app/components/concurrent-stream/concurrent-stream.component.html create mode 100644 src/app/components/concurrent-stream/concurrent-stream.component.scss create mode 100644 src/app/components/concurrent-stream/concurrent-stream.component.spec.ts create mode 100644 src/app/components/concurrent-stream/concurrent-stream.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0ae868f..ec638bd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -86,6 +86,7 @@ import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component'; import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component'; import { H401Interceptor } from './http.interceptor'; +import { ConcurrentStreamComponent } from './components/concurrent-stream/concurrent-stream.component'; registerLocaleData(es, 'es'); @@ -134,7 +135,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible CustomPlaylistsComponent, EditCategoryDialogComponent, TwitchChatComponent, - SeeMoreComponent + SeeMoreComponent, + ConcurrentStreamComponent ], imports: [ CommonModule, diff --git a/src/app/components/concurrent-stream/concurrent-stream.component.html b/src/app/components/concurrent-stream/concurrent-stream.component.html new file mode 100644 index 0000000..414c4ac --- /dev/null +++ b/src/app/components/concurrent-stream/concurrent-stream.component.html @@ -0,0 +1,6 @@ +
+ + + + +
\ No newline at end of file diff --git a/src/app/components/concurrent-stream/concurrent-stream.component.scss b/src/app/components/concurrent-stream/concurrent-stream.component.scss new file mode 100644 index 0000000..d3b74be --- /dev/null +++ b/src/app/components/concurrent-stream/concurrent-stream.component.scss @@ -0,0 +1,7 @@ +.buttons-container { + display: flex; + align-items: center; + justify-content: center; + margin-top: 15px; + margin-bottom: 15px; +} \ No newline at end of file diff --git a/src/app/components/concurrent-stream/concurrent-stream.component.spec.ts b/src/app/components/concurrent-stream/concurrent-stream.component.spec.ts new file mode 100644 index 0000000..a881ec8 --- /dev/null +++ b/src/app/components/concurrent-stream/concurrent-stream.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConcurrentStreamComponent } from './concurrent-stream.component'; + +describe('ConcurrentStreamComponent', () => { + let component: ConcurrentStreamComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ConcurrentStreamComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConcurrentStreamComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/concurrent-stream/concurrent-stream.component.ts b/src/app/components/concurrent-stream/concurrent-stream.component.ts new file mode 100644 index 0000000..6c2cc67 --- /dev/null +++ b/src/app/components/concurrent-stream/concurrent-stream.component.ts @@ -0,0 +1,140 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { PostsService } from 'app/posts.services'; + +@Component({ + selector: 'app-concurrent-stream', + templateUrl: './concurrent-stream.component.html', + styleUrls: ['./concurrent-stream.component.scss'] +}) +export class ConcurrentStreamComponent implements OnInit { + + @Input() server_mode = false; + @Input() playback_timestamp; + @Input() playing; + @Input() uid; + + @Output() setPlaybackTimestamp = new EventEmitter(); + @Output() togglePlayback = new EventEmitter(); + @Output() setPlaybackRate = new EventEmitter(); + + started = false; + server_started = false; + watch_together_clicked = false; + + server_already_exists = null; + + check_timeout: any; + update_timeout: any; + + PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_PLAYBACK_MODIFICATION = 0.5; + PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_SKIP = 2; + + PLAYBACK_MODIFIER = 0.1; + + playback_rate_modified = false; + + constructor(private postsService: PostsService) { } + + // flow: click start watching -> check for available stream to enable join button and if user, display "start stream" + // users who join a stream will send continuous requests for info on playback + + ngOnInit(): void { + + } + + startServer() { + this.started = true; + this.server_started = true; + this.update_timeout = setInterval(() => { + this.updateStream(); + }, 1000); + } + + updateStream() { + this.postsService.updateConcurrentStream(this.uid, this.playback_timestamp, Date.now()/1000, this.playing).subscribe(res => { + }); + } + + startClient() { + this.started = true; + } + + checkStream() { + if (this.server_started) { return; } + const current_playback_timestamp = this.playback_timestamp; + const current_unix_timestamp = Date.now()/1000; + this.postsService.checkConcurrentStream(this.uid).subscribe(res => { + const stream = res['stream']; + + if (!stream) { + this.server_already_exists = false; + return; + } + + this.server_already_exists = true; + + // check whether client has joined the stream + if (!this.started) { return; } + + if (!stream['playing'] && this.playing) { + // tell client to pause and set the timestamp to sync + this.togglePlayback.emit(false); + this.setPlaybackTimestamp.emit(stream['playback_timestamp']); + } else if (stream['playing']) { + // sync unpause state + if (!this.playing) { this.togglePlayback.emit(true); } + + // sync time + const zeroed_local_unix_timestamp = current_unix_timestamp - current_playback_timestamp; + const zeroed_server_unix_timestamp = stream['unix_timestamp'] - stream['playback_timestamp']; + + const seconds_behind_locally = zeroed_local_unix_timestamp - zeroed_server_unix_timestamp; + + if (Math.abs(seconds_behind_locally) > this.PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_SKIP) { + // skip to playback timestamp because the difference is too high + this.setPlaybackTimestamp.emit(this.playback_timestamp + seconds_behind_locally + 0.3); + this.playback_rate_modified = false; + } else if (!this.playback_rate_modified && Math.abs(seconds_behind_locally) > this.PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_PLAYBACK_MODIFICATION) { + // increase playback speed to avoid skipping + let seconds_to_wait = (Math.abs(seconds_behind_locally)/this.PLAYBACK_MODIFIER); + seconds_to_wait += 0.3/this.PLAYBACK_MODIFIER; + + this.playback_rate_modified = true; + + if (seconds_behind_locally > 0) { + // increase speed + this.setPlaybackRate.emit(1 + this.PLAYBACK_MODIFIER); + setTimeout(() => { + this.setPlaybackRate.emit(1); + this.playback_rate_modified = false; + }, seconds_to_wait * 1000); + } else { + // decrease speed + this.setPlaybackRate.emit(1 - this.PLAYBACK_MODIFIER); + setTimeout(() => { + this.setPlaybackRate.emit(1); + this.playback_rate_modified = false; + }, seconds_to_wait * 1000); + } + } + } + }); + } + + startWatching() { + this.watch_together_clicked = true; + this.check_timeout = setInterval(() => { + this.checkStream(); + }, 1000); + } + + stop() { + if (this.check_timeout) { clearInterval(this.check_timeout); } + if (this.update_timeout) { clearInterval(this.update_timeout); } + this.started = false; + this.server_started = false; + this.watch_together_clicked = false; + } + + +} From 46f85794391d981123b30ce7efd885af344f55f6 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 12 May 2021 22:56:16 -0600 Subject: [PATCH 027/209] Refactored player component to utilize uids instead of fileNames to improve maintainability, consistency, and reliability Playlists now use uids instead of fileNames Added generic getPlaylist and updatePlaylist functions --- backend/app.js | 164 +++++++------ backend/db.js | 86 ++++--- .../custom-playlists.component.ts | 16 +- .../recent-videos/recent-videos.component.ts | 5 +- .../create-playlist.component.html | 2 +- .../share-media-dialog.component.ts | 2 +- src/app/main/main.component.ts | 34 +-- src/app/player/player.component.html | 8 +- src/app/player/player.component.ts | 225 ++++++------------ src/app/posts.services.ts | 45 ++-- .../subscription-file-card.component.ts | 2 +- .../subscription/subscription.component.ts | 14 +- 12 files changed, 288 insertions(+), 315 deletions(-) diff --git a/backend/app.js b/backend/app.js index c5e2d11..24b5664 100644 --- a/backend/app.js +++ b/backend/app.js @@ -139,6 +139,8 @@ var updaterStatus = null; var timestamp_server_start = Date.now(); +const concurrentStreams = {}; + if (debugMode) logger.info('YTDL-Material in debug mode!'); // check if just updated @@ -1849,14 +1851,14 @@ const optionalJwt = function (req, res, next) { const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); if (multiUserMode && ((req.body && req.body.uuid) || (req.query && req.query.uuid)) && (req.path.includes('/api/getFile') || req.path.includes('/api/stream') || + req.path.includes('/api/getPlaylist') || req.path.includes('/api/downloadFile'))) { // check if shared video const using_body = req.body && req.body.uuid; const uuid = using_body ? req.body.uuid : req.query.uuid; const uid = using_body ? req.body.uid : req.query.uid; - const type = using_body ? req.body.type : req.query.type; - const playlist_id = using_body ? req.body.id : req.query.id; - const file = !playlist_id ? auth_api.getUserVideo(uuid, uid, type, true, req.body) : auth_api.getUserPlaylist(uuid, playlist_id, null, false); + const playlist_id = using_body ? req.body.playlist_id : req.query.playlist_id; + const file = !playlist_id ? auth_api.getUserVideo(uuid, uid, true) : db_api.getPlaylist(playlist_id, uuid, true); if (file) { req.can_watch = true; return next(); @@ -2118,6 +2120,34 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { }); }); +app.post('/api/checkConcurrentStream', async (req, res) => { + const uid = req.body.uid; + + const DEAD_SERVER_THRESHOLD = 10; + + if (concurrentStreams[uid] && Date.now()/1000 - concurrentStreams[uid]['unix_timestamp'] > DEAD_SERVER_THRESHOLD) { + logger.verbose( `Killing dead stream on ${uid}`); + delete concurrentStreams[uid]; + } + + res.send({stream: concurrentStreams[uid]}) +}); + +app.post('/api/updateConcurrentStream', optionalJwt, async (req, res) => { + const uid = req.body.uid; + const playback_timestamp = req.body.playback_timestamp; + const unix_timestamp = req.body.unix_timestamp; + const playing = req.body.playing; + + concurrentStreams[uid] = { + playback_timestamp: playback_timestamp, + unix_timestamp: unix_timestamp, + playing: playing + } + + res.send({stream: concurrentStreams[uid]}) +}); + app.post('/api/getFullTwitchChat', optionalJwt, async (req, res) => { var id = req.body.id; var type = req.body.type; @@ -2174,7 +2204,7 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) { // single-user mode try { success = true; - if (!is_playlist && type !== 'subscription') { + if (!is_playlist) { db.get(`files`) .find({uid: uid}) .assign({sharingEnabled: true}) @@ -2184,7 +2214,7 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) { .find({id: uid}) .assign({sharingEnabled: true}) .write(); - } else if (type === 'subscription') { + } else if (false) { // TODO: Implement. Main blocker right now is subscription videos are not stored in the DB, they are searched for every // time they are requested from the subscription directory. } else { @@ -2193,6 +2223,7 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) { } } catch(err) { + logger.error(err); success = false; } @@ -2525,14 +2556,14 @@ app.post('/api/getSubscriptions', optionalJwt, async (req, res) => { app.post('/api/createPlaylist', optionalJwt, async (req, res) => { let playlistName = req.body.playlistName; - let fileNames = req.body.fileNames; + let uids = req.body.uids; let type = req.body.type; let thumbnailURL = req.body.thumbnailURL; let duration = req.body.duration; let new_playlist = { name: playlistName, - fileNames: fileNames, + uids: uids, id: shortid.generate(), thumbnailURL: thumbnailURL, type: type, @@ -2556,15 +2587,19 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => { }); app.post('/api/getPlaylist', optionalJwt, async (req, res) => { - let playlistID = req.body.playlistID; - let uuid = req.body.uuid; + let playlist_id = req.body.playlist_id; + let uuid = req.body.uuid ? req.body.uuid : (req.user && req.user.uid ? req.user.uid : null); + let include_file_metadata = req.body.include_file_metadata; - let playlist = null; + const playlist = await db_api.getPlaylist(playlist_id, uuid); - if (req.isAuthenticated()) { - playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID); - } else { - playlist = db.get(`playlists`).find({id: playlistID}).value(); + if (playlist && include_file_metadata) { + playlist['file_objs'] = []; + for (let i = 0; i < playlist['uids'].length; i++) { + const uid = playlist['uids'][i]; + const file_obj = await db_api.getVideo(uid, uuid); + playlist['file_objs'].push(file_obj); + } } res.send({ @@ -2576,16 +2611,16 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => { app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => { let playlistID = req.body.playlistID; - let fileNames = req.body.fileNames; + let uids = req.body.uids; let success = false; try { if (req.isAuthenticated()) { - auth_api.updatePlaylistFiles(req.user.uid, playlistID, fileNames); + auth_api.updatePlaylistFiles(req.user.uid, playlistID, uids); } else { db.get(`playlists`) .find({id: playlistID}) - .assign({fileNames: fileNames}) + .assign({uids: uids}) .write(); } @@ -2664,51 +2699,36 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { }); app.post('/api/downloadFile', optionalJwt, async (req, res) => { - let fileNames = req.body.fileNames; - let zip_mode = req.body.zip_mode; - let type = req.body.type; - let outputName = req.body.outputName; - let fullPathProvided = req.body.fullPathProvided; - let subscriptionName = req.body.subscriptionName; - let subscriptionPlaylist = req.body.subPlaylist; - let file = null; - if (!zip_mode) { - fileNames = decodeURIComponent(fileNames); - const is_audio = type === 'audio'; - const fileFolderPath = is_audio ? audioFolderPath : videoFolderPath; - const ext = is_audio ? '.mp3' : '.mp4'; + let uid = req.body.uid; + let is_playlist = req.body.is_playlist; + let uuid = req.body.uuid; - let base_path = fileFolderPath; - let usersFileFolder = null; - const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); - if (multiUserMode && (req.body.uuid || req.user.uid)) { - usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); - base_path = path.join(usersFileFolder, req.body.uuid ? req.body.uuid : req.user.uid, type); - } - if (!subscriptionName) { - file = path.join(__dirname, base_path, fileNames + ext); - } else { - let basePath = null; - if (usersFileFolder) - basePath = path.join(usersFileFolder, req.user.uid, 'subscriptions'); - else - basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); + let file_path_to_download = null; - file = path.join(__dirname, basePath, (subscriptionPlaylist === true || subscriptionPlaylist === 'true' ? 'playlists' : 'channels'), subscriptionName, fileNames + ext); + if (!uuid && req.user) uuid = req.user.uid; + if (is_playlist) { + const playlist_files_to_download = []; + const playlist = db_api.getPlaylist(uid, uuid); + for (let i = 0; i < playlist['uids'].length; i++) { + const uid = playlist['uids'][i]; + const file_obj = await db_api.getVideo(uid, uuid); + playlist_files_to_download.push(file_obj.path); } + + // generate zip + file_path_to_download = await createPlaylistZipFile(playlist_files_to_download, playlist.type, playlist.name); } else { - for (let i = 0; i < fileNames.length; i++) { - fileNames[i] = decodeURIComponent(fileNames[i]); - } - file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided, req.body.uuid || req.user.uid); - if (!path.isAbsolute(file)) file = path.join(__dirname, file); + const file_obj = await db_api.getVideo(uid, uuid) + file_path_to_download = file_obj.path; } - res.sendFile(file, function (err) { + if (!path.isAbsolute(file_path_to_download)) file_path_to_download = path.join(__dirname, file_path_to_download); + res.sendFile(file_path_to_download, function (err) { if (err) { logger.error(err); - } else if (fullPathProvided) { + } else if (is_playlist) { try { - fs.unlinkSync(file); + // delete generated zip file + fs.unlinkSync(file_path_to_download); } catch(e) { logger.error("Failed to remove file", file); } @@ -2783,31 +2803,21 @@ app.post('/api/generateNewAPIKey', function (req, res) { // Streaming API calls -app.get('/api/stream/:id', optionalJwt, (req, res) => { +app.get('/api/stream', optionalJwt, async (req, res) => { const type = req.query.type; const ext = type === 'audio' ? '.mp3' : '.mp4'; const mimetype = type === 'audio' ? 'audio/mp3' : 'video/mp4'; var head; let optionalParams = url_api.parse(req.url,true).query; - let id = decodeURIComponent(req.params.id); - let file_path = req.query.file_path ? decodeURIComponent(req.query.file_path.split('?')[0]) : null; - if (!file_path && (req.isAuthenticated() || req.can_watch)) { - let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); - if (optionalParams['subName']) { - const isPlaylist = optionalParams['subPlaylist']; - file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + ext) - } else { - file_path = path.join(usersFileFolder, req.query.uuid ? req.query.uuid : req.user.uid, type, id + ext); - } - } else if (!file_path && optionalParams['subName']) { - let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); - const isPlaylist = optionalParams['subPlaylist']; - basePath += (isPlaylist === 'true' ? 'playlists/' : 'channels/'); - file_path = basePath + optionalParams['subName'] + '/' + id + ext; - } + let uid = decodeURIComponent(req.query.uid); - if (!file_path) { - file_path = path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext); + let file_path = null; + + const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); + if (!multiUserMode || req.isAuthenticated() || req.can_watch) { + const file_obj = await db_api.getVideo(uid, req.query.uuid ? req.query.uuid : (req.user ? req.user.uid : null), req.query.sub_id); + if (file_obj) file_path = file_obj['path']; + else file_path = null; } const stat = fs.statSync(file_path) @@ -2821,11 +2831,11 @@ app.get('/api/stream/:id', optionalJwt, (req, res) => { : fileSize-1 const chunksize = (end-start)+1 const file = fs.createReadStream(file_path, {start, end}) - if (config_api.descriptors[id]) config_api.descriptors[id].push(file); - else config_api.descriptors[id] = [file]; + if (config_api.descriptors[uid]) config_api.descriptors[uid].push(file); + else config_api.descriptors[uid] = [file]; file.on('close', function() { - let index = config_api.descriptors[id].indexOf(file); - config_api.descriptors[id].splice(index, 1); + let index = config_api.descriptors[uid].indexOf(file); + config_api.descriptors[uid].splice(index, 1); logger.debug('Successfully closed stream and removed file reference.'); }); head = { diff --git a/backend/db.js b/backend/db.js index c8ef8fb..061280b 100644 --- a/backend/db.js +++ b/backend/db.js @@ -10,12 +10,12 @@ var users_db = null; function setDB(input_db, input_users_db) { db = input_db; users_db = input_users_db } function setLogger(input_logger) { logger = input_logger; } -function initialize(input_db, input_users_db, input_logger) { +exports.initialize = (input_db, input_users_db, input_logger) => { setDB(input_db, input_users_db); setLogger(input_logger); } -function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null, category = null, cropFileSettings = null) { +exports.registerFileDB = (file_path, type, multiUserMode = null, sub = null, customPath = null, category = null, cropFileSettings = null) => { let db_path = null; const file_id = utils.removeFileExtension(file_path); const file_object = generateFileObject(file_id, type, customPath || multiUserMode && multiUserMode.file_path, sub); @@ -107,23 +107,11 @@ function generateFileObject(id, type, customPath = null, sub = null) { return file_obj; } -function updatePlaylist(playlist, user_uid) { - let playlistID = playlist.id; - let db_loc = null; - if (user_uid) { - db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}); - } else { - db_loc = db.get(`playlists`).find({id: playlistID}); - } - db_loc.assign(playlist).write(); - return true; -} - function getAppendedBasePathSub(sub, base_path) { return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name); } -function getFileDirectoriesAndDBs() { +exports.getFileDirectoriesAndDBs = () => { let dirs_to_check = []; let subscriptions_to_check = []; const subscriptions_base_path = config_api.getConfigItem('ytdl_subscriptions_base_path'); // only for single-user mode @@ -192,8 +180,8 @@ function getFileDirectoriesAndDBs() { return dirs_to_check; } -async function importUnregisteredFiles() { - const dirs_to_check = getFileDirectoriesAndDBs(); +exports.importUnregisteredFiles = async () => { + const dirs_to_check = exports.getFileDirectoriesAndDBs(); // run through check list and check each file to see if it's missing from the db for (const dir_to_check of dirs_to_check) { @@ -213,7 +201,7 @@ async function importUnregisteredFiles() { } -async function preimportUnregisteredSubscriptionFile(sub, appendedBasePath) { +exports.preimportUnregisteredSubscriptionFile = async (sub, appendedBasePath) => { const preimported_file_paths = []; let dbPath = null; @@ -236,13 +224,60 @@ async function preimportUnregisteredSubscriptionFile(sub, appendedBasePath) { return preimported_file_paths; } -async function getVideo(file_uid, uuid, sub_id) { +exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = false) => { + let playlist = null + if (user_uid) { + playlist = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlist_id}).value(); + + // prevent unauthorized users from accessing the file info + if (require_sharing && !playlist['sharingEnabled']) return null; + } else { + playlist = db.get(`playlists`).find({id: playlist_id}).value(); + } + + // converts playlists to new UID-based schema + if (playlist && playlist['fileNames'] && !playlist['uids']) { + playlist['uids'] = []; + logger.verbose(`Converting playlist ${playlist['name']} to new UID-based schema.`); + for (let i = 0; i < playlist['fileNames'].length; i++) { + const fileName = playlist['fileNames'][i]; + const uid = exports.getVideoUIDByID(fileName, user_uid); + if (uid) playlist['uids'].push(uid); + else logger.warn(`Failed to convert file with name ${fileName} to its UID while converting playlist ${playlist['name']} to the new UID-based schema. The original file is likely missing/deleted and it will be skipped.`); + } + delete playlist['fileNames']; + exports.updatePlaylist(playlist, user_uid); + } + + return playlist; +} + +exports.updatePlaylist = (playlist, user_uid = null) => { + let playlistID = playlist.id; + let db_loc = null; + if (user_uid) { + db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}); + } else { + db_loc = db.get(`playlists`).find({id: playlistID}); + } + db_loc.assign(playlist).write(); + return true; +} + +// Video ID is basically just the file name without the base path and file extension - this method helps us get away from that +exports.getVideoUIDByID = (file_id, uuid = null) => { + const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; + const file_obj = base_db_path.get('files').find({id: file_id}).value(); + return file_obj ? file_obj['uid'] : null; +} + +exports.getVideo = async (file_uid, uuid, sub_id) => { const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; const sub_db_path = sub_id ? base_db_path.get('subscriptions').find({id: sub_id}).get('videos') : base_db_path.get('files'); return sub_db_path.find({uid: file_uid}).value(); } -async function setVideoProperty(file_uid, assignment_obj, uuid, sub_id) { +exports.setVideoProperty = async (file_uid, assignment_obj, uuid, sub_id) => { const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; const sub_db_path = sub_id ? base_db_path.get('subscriptions').find({id: sub_id}).get('videos') : base_db_path.get('files'); const file_db_path = sub_db_path.find({uid: file_uid}); @@ -251,14 +286,3 @@ async function setVideoProperty(file_uid, assignment_obj, uuid, sub_id) { } sub_db_path.find({uid: file_uid}).assign(assignment_obj).write(); } - -module.exports = { - initialize: initialize, - registerFileDB: registerFileDB, - updatePlaylist: updatePlaylist, - getFileDirectoriesAndDBs: getFileDirectoriesAndDBs, - importUnregisteredFiles: importUnregisteredFiles, - preimportUnregisteredSubscriptionFile: preimportUnregisteredSubscriptionFile, - getVideo: getVideo, - setVideoProperty: setVideoProperty -} diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index 73e3036..8870a8e 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -57,12 +57,11 @@ export class CustomPlaylistsComponent implements OnInit { if (playlist) { if (this.postsService.config['Extra']['download_only_mode']) { - this.downloading_content[type][playlistID] = true; - this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID); + this.downloadPlaylist(playlist.id, playlist.name); } else { localStorage.setItem('player_navigator', this.router.url); const fileNames = playlist.fileNames; - this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID, uid: playlistID, auto: playlist.auto}]); + this.router.navigate(['/player', {playlist_id: playlistID, auto: playlist.auto}]); } } else { // playlist not found @@ -70,11 +69,12 @@ export class CustomPlaylistsComponent implements OnInit { } } - downloadPlaylist(fileNames, type, zipName = null, playlistID = null) { - this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => { - if (playlistID) { this.downloading_content[type][playlistID] = false }; - const blob: Blob = res; - saveAs(blob, zipName + '.zip'); + downloadPlaylist(playlist_id, playlist_name) { + this.downloading_content[playlist_id] = true; + this.postsService.downloadPlaylistFromServer(playlist_id).subscribe(res => { + this.downloading_content[playlist_id] = false; + const blob: any = res; + saveAs(blob, playlist_name + '.zip'); }); } diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 6aec7f2..2b8fe94 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -201,8 +201,7 @@ export class RecentVideosComponent implements OnInit { const type = file.isAudio ? 'audio' : 'video'; const ext = type === 'audio' ? '.mp3' : '.mp4' const sub = this.postsService.getSubscriptionByID(file.sub_id); - this.postsService.downloadFileFromServer(file.id, type, null, null, sub.name, sub.isPlaylist, - this.postsService.user ? this.postsService.user.uid : null, null).subscribe(res => { + this.postsService.downloadFileFromServer(file.uid).subscribe(res => { const blob: Blob = res; saveAs(blob, file.id + ext); }, err => { @@ -215,7 +214,7 @@ export class RecentVideosComponent implements OnInit { const ext = type === 'audio' ? '.mp3' : '.mp4' const name = file.id; this.downloading_content[type][name] = true; - this.postsService.downloadFileFromServer(name, type).subscribe(res => { + this.postsService.downloadFileFromServer(file.uid).subscribe(res => { this.downloading_content[type][name] = false; const blob: Blob = res; saveAs(blob, decodeURIComponent(name) + ext); diff --git a/src/app/create-playlist/create-playlist.component.html b/src/app/create-playlist/create-playlist.component.html index 8027983..d9f108a 100644 --- a/src/app/create-playlist/create-playlist.component.html +++ b/src/app/create-playlist/create-playlist.component.html @@ -19,7 +19,7 @@ Audio files Videos - {{file.id}} + {{file.id}} {{file.id}} {{file.id}} diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts index 137e8fc..9b687ff 100644 --- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts +++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts @@ -33,7 +33,7 @@ export class ShareMediaDialogComponent implements OnInit { this.is_playlist = this.data.is_playlist; this.current_timestamp = (this.data.current_timestamp / 1000).toFixed(2); - const arg = (this.is_playlist ? ';id=' : ';uid='); + const arg = (this.is_playlist ? ';playlist_id=' : ';uid='); this.default_share_url = window.location.href.split(';')[0] + arg + this.uid; if (this.uuid) { this.default_share_url += ';uuid=' + this.uuid; diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 51a90ce..e37d041 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -355,7 +355,7 @@ export class MainComponent implements OnInit { if (playlist) { if (this.downloadOnlyMode) { this.downloading_content[type][playlistID] = true; - this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID); + this.downloadPlaylist(playlist); } else { localStorage.setItem('player_navigator', this.router.url); const fileNames = playlist.fileNames; @@ -626,41 +626,41 @@ export class MainComponent implements OnInit { } } - downloadAudioFile(name) { - this.downloading_content['audio'][name] = true; - this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => { - this.downloading_content['audio'][name] = false; + downloadAudioFile(file) { + this.downloading_content['audio'][file.id] = true; + this.postsService.downloadFileFromServer(file.uid).subscribe(res => { + this.downloading_content['audio'][file.id] = false; const blob: Blob = res; - saveAs(blob, decodeURIComponent(name) + '.mp3'); + saveAs(blob, decodeURIComponent(file.id) + '.mp3'); if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded - this.postsService.deleteFile(name, 'video').subscribe(delRes => { + this.postsService.deleteFile(file.uid).subscribe(delRes => { }); } }); } - downloadVideoFile(name) { - this.downloading_content['video'][name] = true; - this.postsService.downloadFileFromServer(name, 'video').subscribe(res => { - this.downloading_content['video'][name] = false; + downloadVideoFile(file) { + this.downloading_content['video'][file.id] = true; + this.postsService.downloadFileFromServer(file.uid).subscribe(res => { + this.downloading_content['video'][file.id] = false; const blob: Blob = res; - saveAs(blob, decodeURIComponent(name) + '.mp4'); + saveAs(blob, decodeURIComponent(file.id) + '.mp4'); if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded - this.postsService.deleteFile(name, 'audio').subscribe(delRes => { + this.postsService.deleteFile(file.uid).subscribe(delRes => { }); } }); } - downloadPlaylist(fileNames, type, zipName = null, playlistID = null) { - this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => { - if (playlistID) { this.downloading_content[type][playlistID] = false }; + downloadPlaylist(playlist) { + this.postsService.downloadFileFromServer(playlist.id, null, true).subscribe(res => { + if (playlist.id) { this.downloading_content[playlist.type][playlist.id] = false }; const blob: Blob = res; - saveAs(blob, zipName + '.zip'); + saveAs(blob, playlist.name + '.zip'); }); } diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 7f8d928..9de791a 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -29,12 +29,11 @@
- - + - + @@ -47,6 +46,9 @@ {{playlist_item.label}}
+ + + diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index b3660a5..601271d 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -36,18 +36,16 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { api_ready = false; // params - fileNames: string[]; + uids: string[]; type: string; - id = null; // used for playlists (not subscription) + playlist_id = null; // used for playlists (not subscription) uid = null; // used for non-subscription files (audio, video, playlist) subscription = null; - subscriptionName = null; + sub_id = null; subPlaylist = null; uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video timestamp = null; - is_shared = false; - db_playlist = null; db_file = null; @@ -56,8 +54,6 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { videoFolderPath = null; subscriptionFolderPath = null; - sharingEnabled = null; - // url-mode params url = null; name = null; @@ -79,11 +75,9 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit(): void { this.innerWidth = window.innerWidth; - this.type = this.route.snapshot.paramMap.get('type'); - this.id = this.route.snapshot.paramMap.get('id'); + this.playlist_id = this.route.snapshot.paramMap.get('playlist_id'); this.uid = this.route.snapshot.paramMap.get('uid'); - this.subscriptionName = this.route.snapshot.paramMap.get('subscriptionName'); - this.subPlaylist = this.route.snapshot.paramMap.get('subPlaylist'); + this.sub_id = this.route.snapshot.paramMap.get('sub_id'); this.url = this.route.snapshot.paramMap.get('url'); this.name = this.route.snapshot.paramMap.get('name'); this.uuid = this.route.snapshot.paramMap.get('uuid'); @@ -120,19 +114,14 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.audioFolderPath = this.postsService.config['Downloader']['path-audio']; this.videoFolderPath = this.postsService.config['Downloader']['path-video']; this.subscriptionFolderPath = this.postsService.config['Subscriptions']['subscriptions_base_path']; - this.fileNames = this.route.snapshot.paramMap.get('fileNames') ? this.route.snapshot.paramMap.get('fileNames').split('|nvr|') : null; - if (!this.fileNames && !this.type) { - this.is_shared = true; - } - - if (this.uid && !this.id) { - this.getFile(); - } else if (this.id) { - this.getPlaylistFiles(); - } else if (this.subscriptionName) { + if (this.sub_id) { this.getSubscription(); - } + } else if (this.playlist_id) { + this.getPlaylistFiles(); + } else if (this.uid) { + this.getFile(); + } if (this.url) { // if a url is given, just stream the URL @@ -147,14 +136,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.currentItem = this.playlist[0]; this.currentIndex = 0; this.show_player = true; - } else if (this.fileNames && !this.subscriptionName) { - this.show_player = true; - this.parseFileNames(); } } getFile() { - const already_has_filenames = !!this.fileNames; this.postsService.getFile(this.uid, null, this.uuid).subscribe(res => { this.db_file = res['file']; if (!this.db_file) { @@ -165,45 +150,32 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { console.error('Failed to increment view count'); console.error(err); }); - this.sharingEnabled = this.db_file.sharingEnabled; - if (!this.fileNames) { - // means it's a shared video - if (!this.id) { - // regular video/audio file (not playlist) - this.fileNames = [this.db_file['id']]; - this.type = this.db_file['isAudio'] ? 'audio' : 'video'; - if (!already_has_filenames) { this.parseFileNames(); } - } - } - if (this.db_file['sharingEnabled'] || !this.uuid) { - this.show_player = true; - } else if (!already_has_filenames) { - this.openSnackBar('Error: Sharing has been disabled for this video!', 'Dismiss'); - } + // regular video/audio file (not playlist) + this.uids = [this.db_file['uid']]; + this.type = this.db_file['isAudio'] ? 'audio' : 'video'; + this.parseFileNames(); }); } getSubscription() { - this.postsService.getSubscription(null, this.subscriptionName).subscribe(res => { + this.postsService.getSubscription(this.sub_id).subscribe(res => { const subscription = res['subscription']; this.subscription = subscription; - if (this.fileNames) { - subscription.videos.forEach(video => { - if (video['id'] === this.fileNames[0]) { - this.db_file = video; - this.postsService.incrementViewCount(this.db_file['uid'], this.subscription['id'], this.uuid).subscribe(res => {}, err => { - console.error('Failed to increment view count'); - console.error(err); - }); - this.show_player = true; - this.parseFileNames(); - } - }); - } else { - console.log('no file name specified'); - } + this.type === this.subscription.type; + subscription.videos.forEach(video => { + if (video['uid'] === this.uid) { + this.db_file = video; + this.postsService.incrementViewCount(this.db_file['uid'], this.sub_id, this.uuid).subscribe(res => {}, err => { + console.error('Failed to increment view count'); + console.error(err); + }); + this.uids = this.db_file['uid']; + this.show_player = true; + this.parseFileNames(); + } + }); }, err => { - this.openSnackBar(`Failed to find subscription ${this.subscriptionName}`, 'Dismiss'); + this.openSnackBar(`Failed to find subscription ${this.sub_id}`, 'Dismiss'); }); } @@ -212,10 +184,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.show_player = true; return; } - this.postsService.getPlaylist(this.id, null, this.uuid).subscribe(res => { + this.postsService.getPlaylist(this.playlist_id, this.uuid, true).subscribe(res => { if (res['playlist']) { this.db_playlist = res['playlist']; - this.fileNames = this.db_playlist['fileNames']; + this.uids = this.db_playlist.uids; this.type = res['type']; this.show_player = true; this.parseFileNames(); @@ -231,60 +203,43 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { let fileType = null; if (this.type === 'audio') { fileType = 'audio/mp3'; - } else if (this.type === 'video') { - fileType = 'video/mp4'; } else { - // error - console.error('Must have valid file type! Use \'audio\', \'video\', or \'subscription\'.'); + fileType = 'video/mp4'; } this.playlist = []; - for (let i = 0; i < this.fileNames.length; i++) { - const fileName = this.fileNames[i]; - let baseLocation = null; - let fullLocation = null; + for (let i = 0; i < this.uids.length; i++) { + const uid = this.uids[i]; - // adds user token if in multi-user-mode - const uuid_str = this.uuid ? `&uuid=${this.uuid}` : ''; - const uid_str = (this.id || !this.db_file) ? '' : `&uid=${this.db_file.uid}`; - const type_str = (this.type || !this.db_file) ? `&type=${this.type}` : `&type=${this.db_file.type}` - const id_str = this.id ? `&id=${this.id}` : ''; - const file_path_str = (!this.db_file) ? '' : `&file_path=${encodeURIComponent(this.db_file.path)}`; + const file_obj = this.playlist_id ? this.db_playlist['file_objs'][i] : this.db_file; - if (!this.subscriptionName) { - baseLocation = 'stream/'; - fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + `?test=test${type_str}${file_path_str}`; - } else { - // default to video but include subscription name param - baseLocation = 'stream/'; - fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName + - '&subPlaylist=' + this.subPlaylist + `${file_path_str}${type_str}`; - } + let baseLocation = 'stream/'; + let fullLocation = this.baseStreamPath + baseLocation + `?test=test&uid=${file_obj['uid']}`; if (this.postsService.isLoggedIn) { - fullLocation += (this.subscriptionName ? '&' : '&') + `jwt=${this.postsService.token}`; - if (this.is_shared) { fullLocation += `${uuid_str}${uid_str}${type_str}${id_str}`; } - } else if (this.is_shared) { - fullLocation += (this.subscriptionName ? '&' : '?') + `test=test${uuid_str}${uid_str}${type_str}${id_str}`; + fullLocation += `&jwt=${this.postsService.token}`; } - // if it has a slash (meaning it's in a directory), only get the file name for the label - let label = null; - const decodedName = decodeURIComponent(fileName); - const hasSlash = decodedName.includes('/') || decodedName.includes('\\'); - if (hasSlash) { - label = decodedName.replace(/^.*[\\\/]/, ''); - } else { - label = decodedName; + + if (this.uuid) { + fullLocation += `&uuid=${this.uuid}`; } + + if (this.sub_id) { + fullLocation += `&sub_id=${this.sub_id}`; + } else if (this.playlist_id) { + fullLocation += `&playlist_id=${this.playlist_id}`; + } + const mediaObject: IMedia = { - title: fileName, + title: file_obj['title'], src: fullLocation, type: fileType, - label: label + label: file_obj['title'] } this.playlist.push(mediaObject); } this.currentItem = this.playlist[this.currentIndex]; this.original_playlist = JSON.stringify(this.playlist); + this.show_player = true; } onPlayerReady(api: VgApiService) { @@ -361,8 +316,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const zipName = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0]; this.downloading = true; - this.postsService.downloadFileFromServer(fileNames, this.type, zipName, null, null, null, null, - !this.uuid ? this.postsService.user.uid : this.uuid, this.id).subscribe(res => { + this.postsService.downloadFileFromServer(this.playlist_id, this.uuid, true).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, zipName + '.zip'); @@ -376,8 +330,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const ext = (this.type === 'audio') ? '.mp3' : '.mp4'; const filename = this.playlist[0].title; this.downloading = true; - this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist, - this.is_shared ? this.db_file['uid'] : null, this.uuid).subscribe(res => { + this.postsService.downloadFileFromServer(this.uid, this.uuid, false).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, filename + ext); @@ -387,50 +340,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { }); } - namePlaylistDialog() { - const done = new EventEmitter(); - const dialogRef = this.dialog.open(InputDialogComponent, { - width: '300px', - data: { - inputTitle: 'Name the playlist', - inputPlaceholder: 'Name', - submitText: 'Favorite', - doneEmitter: done - } - }); - - done.subscribe(name => { - - // Eventually do additional checks on name - if (name) { - const fileNames = this.getFileNames(); - this.postsService.createPlaylist(name, fileNames, this.type, null).subscribe(res => { - if (res['success']) { - dialogRef.close(); - const new_playlist = res['new_playlist']; - this.db_playlist = new_playlist; - this.openSnackBar('Playlist \'' + name + '\' successfully created!', '') - this.playlistPostCreationHandler(new_playlist.id); - } - }); - } - }); - } - - /* - createPlaylist(name) { - this.postsService.createPlaylist(name, this.fileNames, this.type, null).subscribe(res => { - if (res['success']) { - console.log('Success!'); - } - }); - } - */ - playlistPostCreationHandler(playlistID) { // changes the route without moving from the current view or // triggering a navigation event - this.id = playlistID; + this.playlist_id = playlistID; this.router.navigateByUrl(this.router.url + ';id=' + playlistID); } @@ -445,11 +358,11 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { updatePlaylist() { const fileNames = this.getFileNames(); this.playlist_updating = true; - this.postsService.updatePlaylistFiles(this.id, fileNames, this.type).subscribe(res => { + this.postsService.updatePlaylistFiles(this.playlist_id, fileNames, this.type).subscribe(res => { this.playlist_updating = false; if (res['success']) { const fileNamesEncoded = fileNames.join('|nvr|'); - this.router.navigate(['/player', {fileNames: fileNamesEncoded, type: this.type, id: this.id}]); + this.router.navigate(['/player', {fileNames: fileNamesEncoded, type: this.type, id: this.playlist_id}]); this.openSnackBar('Successfully updated playlist.', ''); this.original_playlist = JSON.stringify(this.playlist); } else { @@ -461,10 +374,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { openShareDialog() { const dialogRef = this.dialog.open(ShareMediaDialogComponent, { data: { - uid: this.id ? this.id : this.uid, + uid: this.playlist_id ? this.playlist_id : this.uid, type: this.type, - sharing_enabled: this.id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled, - is_playlist: !!this.id, + sharing_enabled: this.playlist_id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled, + is_playlist: !!this.playlist_id, uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null, current_timestamp: this.api.time.current }, @@ -472,7 +385,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { }); dialogRef.afterClosed().subscribe(res => { - if (!this.id) { + if (!this.playlist_id) { this.getFile(); } else { this.getPlaylistFiles(); @@ -489,6 +402,22 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { }) } + setPlaybackTimestamp(time) { + this.api.seekTime(time); + } + + togglePlayback(to_play) { + if (to_play) { + this.api.play(); + } else { + this.api.pause(); + } + } + + setPlaybackRate(speed) { + this.api.playbackRate = speed; + } + // snackbar helper public openSnackBar(message: string, action: string) { this.snackBar.open(message, action, { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 1fd7929..cf04447 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -219,8 +219,8 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'setConfig', {new_config_file: config}, this.httpOptions); } - deleteFile(uid: string, type: string, blacklistMode = false) { - return this.http.post(this.path + 'deleteFile', {uid: uid, type: type, blacklistMode: blacklistMode}, this.httpOptions); + deleteFile(uid: string, blacklistMode = false) { + return this.http.post(this.path + 'deleteFile', {uid: uid, blacklistMode: blacklistMode}, this.httpOptions); } getMp3s() { @@ -247,22 +247,30 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid, sub: sub}, this.httpOptions); } - downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null, - uid = null, uuid = null, id = null) { - return this.http.post(this.path + 'downloadFile', {fileNames: fileName, - type: type, - zip_mode: Array.isArray(fileName), - outputName: outputName, - fullPathProvided: fullPathProvided, - subscriptionName: subscriptionName, - subPlaylist: subPlaylist, - uuid: uuid, + downloadFileFromServer(uid, uuid = null, is_playlist = false) { + return this.http.post(this.path + 'downloadFile', { uid: uid, - id: id + uuid: uuid, + is_playlist: is_playlist }, {responseType: 'blob', params: this.httpOptions.params}); } + downloadPlaylistFromServer(playlist_id, uuid = null) { + return this.http.post(this.path + 'downloadPlaylist', {playlist_id: playlist_id, uuid: uuid}); + } + + checkConcurrentStream(uid) { + return this.http.post(this.path + 'checkConcurrentStream', {uid: uid}, this.httpOptions); + } + + updateConcurrentStream(uid, playback_timestamp, unix_timestamp, playing) { + return this.http.post(this.path + 'updateConcurrentStream', {uid: uid, + playback_timestamp: playback_timestamp, + unix_timestamp: unix_timestamp, + playing: playing}, this.httpOptions); + } + uploadCookiesFile(fileFormData) { return this.http.post(this.path + 'uploadCookies', fileFormData, this.httpOptions); } @@ -299,17 +307,18 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'disableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions); } - createPlaylist(playlistName, fileNames, type, thumbnailURL, duration = null) { + createPlaylist(playlistName, uids, type, thumbnailURL, duration = null) { return this.http.post(this.path + 'createPlaylist', {playlistName: playlistName, - fileNames: fileNames, + uids: uids, type: type, thumbnailURL: thumbnailURL, duration: duration}, this.httpOptions); } - getPlaylist(playlistID, type, uuid = null) { - return this.http.post(this.path + 'getPlaylist', {playlistID: playlistID, - type: type, uuid: uuid}, this.httpOptions); + getPlaylist(playlist_id, uuid = null, include_file_metadata = false) { + return this.http.post(this.path + 'getPlaylist', {playlist_id: playlist_id, + uuid: uuid, + include_file_metadata: include_file_metadata}, this.httpOptions); } updatePlaylist(playlist) { diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts index e500a7d..2257fd8 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts @@ -42,7 +42,7 @@ export class SubscriptionFileCardComponent implements OnInit { goToFile() { const emit_obj = { - name: this.file.id, + uid: this.file.uid, url: this.file.requested_formats ? this.file.requested_formats[0].url : this.file.url } this.goToFileEmit.emit(emit_obj); diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 461dcfc..af08088 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -103,15 +103,14 @@ export class SubscriptionComponent implements OnInit, OnDestroy { } goToFile(emit_obj) { - const name = emit_obj['name']; + const uid = emit_obj['uid']; const url = emit_obj['url']; localStorage.setItem('player_navigator', this.router.url); if (this.subscription.streamingOnly) { - this.router.navigate(['/player', {name: name, url: url}]); + this.router.navigate(['/player', {uid: uid, url: url}]); } else { - this.router.navigate(['/player', {fileNames: name, - type: this.subscription.type ? this.subscription.type : 'video', subscriptionName: this.subscription.name, - subPlaylist: this.subscription.isPlaylist}]); + this.router.navigate(['/player', {uid: uid, + sub_id: this.subscription.id}]); } } @@ -154,14 +153,15 @@ export class SubscriptionComponent implements OnInit, OnDestroy { } this.downloading = true; - this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => { + // TODO: add download subscription route + /*this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, this.subscription.name + '.zip'); }, err => { console.log(err); this.downloading = false; - }); + });*/ } editSubscription() { From 1d2ab0dc41e20215919ba31f8aae9367107c3389 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 12 May 2021 22:56:38 -0600 Subject: [PATCH 028/209] 401 errors will now not cause redirects in the /player route --- src/app/http.interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/http.interceptor.ts b/src/app/http.interceptor.ts index edde22b..b941a34 100644 --- a/src/app/http.interceptor.ts +++ b/src/app/http.interceptor.ts @@ -14,7 +14,7 @@ export class H401Interceptor implements HttpInterceptor { return next.handle(request).pipe(catchError(err => { if (err.status === 401) { localStorage.setItem('jwt_token', null); - if (this.router.url !== '/login') { + if (this.router.url !== '/login' && !this.router.url.includes('player')) { this.router.navigate(['/login']).then(() => { this.openSnackBar('Login expired, please login again.'); }); From 297a4a3f34541c405d5d7f3c06e384e62476f256 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 16 May 2021 02:53:36 -0600 Subject: [PATCH 029/209] Simplified streaming and file deletion functions --- backend/app.js | 142 ++++-------------- .../recent-videos/recent-videos.component.ts | 2 +- src/app/file-card/file-card.component.ts | 2 +- 3 files changed, 32 insertions(+), 114 deletions(-) diff --git a/backend/app.js b/backend/app.js index 24b5664..8ae57ac 100644 --- a/backend/app.js +++ b/backend/app.js @@ -888,18 +888,22 @@ async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvid return path.join(zipFolderPath,outputName + '.zip'); } -async function deleteAudioFile(name, customPath = null, blacklistMode = false) { - let filePath = customPath ? customPath : audioFolderPath; +// TODO: add to db_api and support multi-user mode +async function deleteFile(uid, uuid = null, blacklistMode = false) { + const file_obj = await db_api.getVideo(uid, uuid); + const type = file_obj.isAudio ? 'audio' : 'video'; + const folderPath = path.dirname(file_obj.path); + const ext = type === 'audio' ? 'mp3' : 'mp4'; + const name = file_obj.id; + const filePathNoExtension = utils.removeFileExtension(file_obj.path); - var jsonPath = path.join(filePath,name+'.mp3.info.json'); - var altJSONPath = path.join(filePath,name+'.info.json'); - var audioFilePath = path.join(filePath,name+'.mp3'); - var thumbnailPath = path.join(filePath,name+'.webp'); - var altThumbnailPath = path.join(filePath,name+'.jpg'); + var jsonPath = `${file_obj.path}.info.json`; + var altJSONPath = `${filePathNoExtension}.info.json`; + var thumbnailPath = `${filePathNoExtension}.webp`; + var altThumbnailPath = `${filePathNoExtension}.jpg`; jsonPath = path.join(__dirname, jsonPath); altJSONPath = path.join(__dirname, altJSONPath); - audioFilePath = path.join(__dirname, audioFilePath); let jsonExists = await fs.pathExists(jsonPath); let thumbnailExists = await fs.pathExists(thumbnailPath); @@ -918,7 +922,7 @@ async function deleteAudioFile(name, customPath = null, blacklistMode = false) { } } - let audioFileExists = await fs.pathExists(audioFilePath); + let fileExists = await fs.pathExists(file_obj.path); if (config_api.descriptors[name]) { try { @@ -932,18 +936,18 @@ async function deleteAudioFile(name, customPath = null, blacklistMode = false) { let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); if (useYoutubeDLArchive) { - const archive_path = path.join(archivePath, 'archive_audio.txt'); + const archive_path = path.join(archivePath, `archive_${type}.txt`); // get ID from JSON - var jsonobj = await utils.getJSONMp3(name, filePath); + var jsonobj = await (type === 'audio' ? utils.getJSONMp3(name, folderPath) : utils.getJSONMp4(name, folderPath)); let id = null; if (jsonobj) id = jsonobj.id; // use subscriptions API to remove video from the archive file, and write it to the blacklist if (await fs.pathExists(archive_path)) { const line = id ? await subscriptions_api.removeIDFromArchive(archive_path, id) : null; - if (blacklistMode && line) await writeToBlacklist('audio', line); + if (blacklistMode && line) await writeToBlacklist(type, line); } else { logger.info('Could not find archive file for audio files. Creating...'); await fs.close(await fs.open(archive_path, 'w')); @@ -952,84 +956,9 @@ async function deleteAudioFile(name, customPath = null, blacklistMode = false) { if (jsonExists) await fs.unlink(jsonPath); if (thumbnailExists) await fs.unlink(thumbnailPath); - if (audioFileExists) { - await fs.unlink(audioFilePath); - if (await fs.pathExists(jsonPath) || await fs.pathExists(audioFilePath)) { - return false; - } else { - return true; - } - } else { - // TODO: tell user that the file didn't exist - return true; - } -} - -async function deleteVideoFile(name, customPath = null, blacklistMode = false) { - let filePath = customPath ? customPath : videoFolderPath; - var jsonPath = path.join(filePath,name+'.info.json'); - - var altJSONPath = path.join(filePath,name+'.mp4.info.json'); - var videoFilePath = path.join(filePath,name+'.mp4'); - var thumbnailPath = path.join(filePath,name+'.webp'); - var altThumbnailPath = path.join(filePath,name+'.jpg'); - - jsonPath = path.join(__dirname, jsonPath); - videoFilePath = path.join(__dirname, videoFilePath); - - let jsonExists = await fs.pathExists(jsonPath); - let videoFileExists = await fs.pathExists(videoFilePath); - let thumbnailExists = await fs.pathExists(thumbnailPath); - - if (!jsonExists) { - if (await fs.pathExists(altJSONPath)) { - jsonExists = true; - jsonPath = altJSONPath; - } - } - - if (!thumbnailExists) { - if (await fs.pathExists(altThumbnailPath)) { - thumbnailExists = true; - thumbnailPath = altThumbnailPath; - } - } - - if (config_api.descriptors[name]) { - try { - for (let i = 0; i < config_api.descriptors[name].length; i++) { - config_api.descriptors[name][i].destroy(); - } - } catch(e) { - - } - } - - let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); - if (useYoutubeDLArchive) { - const archive_path = path.join(archivePath, 'archive_video.txt'); - - // get ID from JSON - - var jsonobj = await utils.getJSONMp4(name, filePath); - let id = null; - if (jsonobj) id = jsonobj.id; - - // use subscriptions API to remove video from the archive file, and write it to the blacklist - if (await fs.pathExists(archive_path)) { - const line = id ? await subscriptions_api.removeIDFromArchive(archive_path, id) : null; - if (blacklistMode && line) await writeToBlacklist('video', line); - } else { - logger.info('Could not find archive file for videos. Creating...'); - fs.closeSync(fs.openSync(archive_path, 'w')); - } - } - - if (jsonExists) await fs.unlink(jsonPath); - if (thumbnailExists) await fs.unlink(thumbnailPath); - if (videoFileExists) { - await fs.unlink(videoFilePath); - if (await fs.pathExists(jsonPath) || await fs.pathExists(videoFilePath)) { + if (fileExists) { + await fs.unlink(file_obj.path); + if (await fs.pathExists(jsonPath) || await fs.pathExists(file_obj.path)) { return false; } else { return true; @@ -1638,6 +1567,8 @@ async function cropFile(file_path, start, end, ext) { async function writeToBlacklist(type, line) { let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt'); // adds newline to the beginning of the line + line.replace('\n', ''); + line.replace('\r', ''); line = '\n' + line; await fs.appendFile(blacklistPath, line); } @@ -2668,9 +2599,8 @@ app.post('/api/deletePlaylist', optionalJwt, async (req, res) => { // deletes non-subscription files app.post('/api/deleteFile', optionalJwt, async (req, res) => { - var uid = req.body.uid; - var type = req.body.type; - var blacklistMode = req.body.blacklistMode; + const uid = req.body.uid; + const blacklistMode = req.body.blacklistMode; if (req.isAuthenticated()) { let success = await auth_api.deleteUserFile(req.user.uid, uid, blacklistMode); @@ -2678,24 +2608,10 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { return; } - var file_obj = db.get(`files`).find({uid: uid}).value(); - var name = file_obj.id; - var fullpath = file_obj ? file_obj.path : null; - var wasDeleted = false; - if (await fs.pathExists(fullpath)) - { - wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.dirname(fullpath), blacklistMode) : await deleteVideoFile(name, path.dirname(fullpath), blacklistMode); - db.get('files').remove({uid: uid}).write(); - wasDeleted = true; - res.send(wasDeleted); - } else if (file_obj) { - db.get('files').remove({uid: uid}).write(); - wasDeleted = true; - res.send(wasDeleted); - } else { - wasDeleted = false; - res.send(wasDeleted); - } + let wasDeleted = false; + wasDeleted = await deleteFile(uid, null, blacklistMode); + db.get('files').remove({uid: uid}).write(); + res.send(wasDeleted); }); app.post('/api/downloadFile', optionalJwt, async (req, res) => { @@ -2805,6 +2721,8 @@ app.post('/api/generateNewAPIKey', function (req, res) { app.get('/api/stream', optionalJwt, async (req, res) => { const type = req.query.type; + const uuid = req.query.uuid ? req.query.uuid : (req.user ? req.user.uid : null); + const sub_id = req.query.sub_id; const ext = type === 'audio' ? '.mp3' : '.mp4'; const mimetype = type === 'audio' ? 'audio/mp3' : 'video/mp4'; var head; @@ -2815,7 +2733,7 @@ app.get('/api/stream', optionalJwt, async (req, res) => { const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); if (!multiUserMode || req.isAuthenticated() || req.can_watch) { - const file_obj = await db_api.getVideo(uid, req.query.uuid ? req.query.uuid : (req.user ? req.user.uid : null), req.query.sub_id); + const file_obj = await db_api.getVideo(uid, uuid, sub_id); if (file_obj) file_path = file_obj['path']; else file_path = null; } diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 2b8fe94..31ed771 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -244,7 +244,7 @@ export class RecentVideosComponent implements OnInit { } deleteNormalFile(file, blacklistMode = false) { - this.postsService.deleteFile(file.uid, file.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => { + this.postsService.deleteFile(file.uid, blacklistMode).subscribe(result => { if (result) { this.postsService.openSnackBar('Delete success!', 'OK.'); this.removeFileCard(file); diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 68a8453..90b906c 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -56,7 +56,7 @@ export class FileCardComponent implements OnInit { deleteFile(blacklistMode = false) { if (!this.playlist) { - this.postsService.deleteFile(this.uid, this.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => { + this.postsService.deleteFile(this.uid, blacklistMode).subscribe(result => { if (result) { this.openSnackBar('Delete success!', 'OK.'); this.removeFile.emit(this.name); From a11445b80db7db8a4fb3f75c7ca5a10654dfc196 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 16 May 2021 02:54:15 -0600 Subject: [PATCH 030/209] Added backend tests and made authentication more testable --- backend/authentication/auth.js | 14 +- backend/package-lock.json | 1159 ++++++++++++++++++++++---------- backend/package.json | 1 + backend/test/tests.js | 94 +++ 4 files changed, 912 insertions(+), 356 deletions(-) create mode 100644 backend/test/tests.js diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index e7cf337..3af31e7 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -144,16 +144,18 @@ exports.registerUser = function(req, res) { ************************************************/ +exports.login = async (username, password) => { + const user = users_db.get('users').find({name: username}).value(); + if (!user) { logger.error(`User ${username} not found`); false } + if (user.auth_method && user.auth_method !== 'internal') { return false } + return await bcrypt.compare(password, user.passhash) ? user : false; +} + exports.passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password'}, async function(username, password, done) { - const user = users_db.get('users').find({name: username}).value(); - if (!user) { logger.error(`User ${username} not found`); return done(null, false); } - if (user.auth_method && user.auth_method !== 'internal') { return done(null, false); } - if (user) { - return done(null, (await bcrypt.compare(password, user.passhash)) ? user : false); - } + return done(null, await exports.login(username, password)); } )); diff --git a/backend/package-lock.json b/backend/package-lock.json index 4067dd9..74f76fa 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -4,6 +4,19 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -87,6 +100,11 @@ "@types/mime": "*" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -113,24 +131,56 @@ } }, "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", "requires": { - "string-width": "^2.0.0" + "string-width": "^3.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } } }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } } }, "any-promise": { @@ -209,6 +259,11 @@ } } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -306,9 +361,9 @@ } }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bl": { "version": "4.0.2", @@ -350,17 +405,18 @@ } }, "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" } }, "brace-expansion": { @@ -380,6 +436,11 @@ "fill-range": "^7.0.1" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, "buffer": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", @@ -462,15 +523,31 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caseless": { "version": "0.12.0", @@ -486,13 +563,27 @@ } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "charenc": { @@ -501,29 +592,62 @@ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" + "readdirp": "~3.5.0" } }, "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } }, "color": { "version": "3.0.0", @@ -673,16 +797,16 @@ } }, "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" } }, "connected-domain": { @@ -735,14 +859,6 @@ "readable-stream": "^3.4.0" } }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, "cross-spawn": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", @@ -759,9 +875,9 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" }, "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, "dashdash": { "version": "1.14.1", @@ -779,11 +895,29 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -841,12 +975,17 @@ } } }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + }, "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "requires": { - "is-obj": "^1.0.0" + "is-obj": "^2.0.0" } }, "dtrace-provider": { @@ -909,6 +1048,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, "enabled": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", @@ -935,15 +1079,25 @@ "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "etag": { "version": "1.8.1", @@ -1061,6 +1215,20 @@ "unpipe": "~1.0.0" } }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, "fluent-ffmpeg": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", @@ -1132,9 +1300,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "optional": true }, "fstream": { @@ -1148,6 +1316,11 @@ "rimraf": "2" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", @@ -1178,48 +1351,46 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" } }, "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", "requires": { - "ini": "^1.3.4" + "ini": "1.3.7" } }, "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", "requires": { - "create-error-class": "^3.0.0", + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" }, "dependencies": { "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } } } }, @@ -1228,6 +1399,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1247,6 +1423,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, "hashish": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz", @@ -1255,6 +1436,11 @@ "traverse": ">=0.2.4" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, "hh-mm-ss": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hh-mm-ss/-/hh-mm-ss-1.2.0.tgz", @@ -1263,6 +1449,11 @@ "zero-fill": "^2.2.3" } }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -1340,9 +1531,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" }, "ipaddr.js": { "version": "1.9.1", @@ -1368,11 +1559,11 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "requires": { - "ci-info": "^1.5.0" + "ci-info": "^2.0.0" } }, "is-extglob": { @@ -1394,18 +1585,18 @@ } }, "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" } }, "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" }, "is-number": { "version": "7.0.0", @@ -1413,33 +1604,25 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "^1.0.1" - } + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -1450,6 +1633,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1465,11 +1653,24 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "requires": { + "argparse": "^2.0.1" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -1556,6 +1757,14 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -1565,11 +1774,11 @@ } }, "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", "requires": { - "package-json": "^4.0.0" + "package-json": "^6.3.0" } }, "lazystream": { @@ -1667,6 +1876,14 @@ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", @@ -1727,6 +1944,38 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "logform": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", @@ -1763,21 +2012,19 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "requires": { - "pify": "^3.0.0" + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "md5": { @@ -1841,6 +2088,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1862,6 +2114,83 @@ "minimist": "^1.2.5" } }, + "mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -1993,9 +2322,9 @@ } }, "nodemon": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", - "integrity": "sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", + "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", "requires": { "chokidar": "^3.2.2", "debug": "^3.2.6", @@ -2005,22 +2334,22 @@ "semver": "^5.7.1", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^2.5.0" + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -2037,6 +2366,11 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -2089,20 +2423,48 @@ "mimic-fn": "^2.1.0" } }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, "p-finally": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==" }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "parseurl": { @@ -2184,16 +2546,16 @@ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2230,9 +2592,9 @@ "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" }, "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "process-nextick-args": { "version": "2.0.1", @@ -2261,20 +2623,15 @@ "table-parser": "^0.1.3" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "pstree.remy": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", - "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==" + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, "pump": { "version": "3.0.0", @@ -2290,11 +2647,27 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "requires": { + "escape-goat": "^2.0.0" + } + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2348,28 +2721,27 @@ } }, "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "requires": { - "picomatch": "^2.0.7" + "picomatch": "^2.2.1" } }, "registry-auth-token": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" + "rc": "^1.2.8" } }, "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", "requires": { - "rc": "^1.0.1" + "rc": "^1.2.8" } }, "request": { @@ -2411,6 +2783,19 @@ } } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -2441,11 +2826,18 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", "requires": { - "semver": "^5.0.3" + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "send": { @@ -2475,6 +2867,14 @@ } } }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -2578,12 +2978,38 @@ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "string_decoder": { @@ -2595,18 +3021,13 @@ } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -2646,87 +3067,9 @@ } }, "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "requires": { - "execa": "^0.7.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" }, "text-hex": { "version": "1.0.0", @@ -2749,10 +3092,10 @@ "thenify": ">= 3.1.0 < 4" } }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" }, "to-regex-range": { "version": "5.0.1", @@ -2807,6 +3150,11 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2821,6 +3169,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -2830,11 +3186,11 @@ } }, "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "^2.0.0" } }, "universalify": { @@ -2847,11 +3203,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, "unzipper": { "version": "0.10.10", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.10.tgz", @@ -2886,20 +3237,23 @@ } }, "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" } }, "uri-js": { @@ -2911,11 +3265,11 @@ } }, "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", "requires": { - "prepend-http": "^1.0.1" + "prepend-http": "^2.0.0" } }, "util-deprecate": { @@ -2987,12 +3341,44 @@ "isexe": "^2.0.0" } }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { - "string-width": "^2.1.1" + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" } }, "winston": { @@ -3051,35 +3437,108 @@ } } }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "requires": { - "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" + } + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "youtube-dl": { "version": "3.0.2", diff --git a/backend/package.json b/backend/package.json index a38315d..72607de 100644 --- a/backend/package.json +++ b/backend/package.json @@ -44,6 +44,7 @@ "lowdb": "^1.0.0", "md5": "^2.2.1", "merge-files": "^0.1.2", + "mocha": "^8.4.0", "moment": "^2.29.1", "multer": "^1.4.2", "node-fetch": "^2.6.1", diff --git a/backend/test/tests.js b/backend/test/tests.js new file mode 100644 index 0000000..c9726a0 --- /dev/null +++ b/backend/test/tests.js @@ -0,0 +1,94 @@ +var assert = require('assert'); +const low = require('lowdb') +var winston = require('winston'); + +process.chdir('./backend') + +const FileSync = require('lowdb/adapters/FileSync'); + +const adapter = new FileSync('./appdata/db.json'); +const db = low(adapter) + +const users_adapter = new FileSync('./appdata/users.json'); +const users_db = low(users_adapter); + +const defaultFormat = winston.format.printf(({ level, message, label, timestamp }) => { + return `${timestamp} ${level.toUpperCase()}: ${message}`; +}); + +let debugMode = process.env.YTDL_MODE === 'debug'; + +const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine(winston.format.timestamp(), defaultFormat), + defaultMeta: {}, + transports: [ + // + // - Write to all logs with level `info` and below to `combined.log` + // - Write all logs error (and below) to `error.log`. + // + new winston.transports.File({ filename: 'appdata/logs/error.log', level: 'error' }), + new winston.transports.File({ filename: 'appdata/logs/combined.log' }), + new winston.transports.Console({level: !debugMode ? 'info' : 'debug', name: 'console'}) + ] +}); + +var auth_api = require('../authentication/auth'); +var db_api = require('../db'); + +db_api.initialize(db, users_db, logger); +auth_api.initialize(db, users_db, logger); + +describe('Multi User', async function() { + let user = null; + const user_to_test = 'admin'; + before(async function() { + user = await auth_api.login('admin', 'pass'); + console.log('hi') + }); + describe('Authentication', function() { + it('login', async function() { + assert(user); + }); + }); + describe('Video player - normal', function() { + const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e'; + it('Get video', async function() { + const video_obj = db_api.getVideo(video_to_test, 'admin'); + assert(video_obj); + }); + + it('Video access - disallowed', async function() { + await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test); + const video_obj = auth_api.getUserVideo('admin', video_to_test, true); + assert(!video_obj); + }); + + it('Video access - allowed', async function() { + await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test); + const video_obj = auth_api.getUserVideo('admin', video_to_test, true); + assert(video_obj); + }); + }); + // describe('Video player - subscription', function() { + // const sub_to_test = ''; + // const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e'; + // it('Get video', async function() { + // const video_obj = db_api.getVideo(video_to_test, 'admin', ); + // assert(video_obj); + // }); + + // it('Video access - disallowed', async function() { + // await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test, sub_to_test); + // const video_obj = auth_api.getUserVideo('admin', video_to_test, true); + // assert(!video_obj); + // }); + + // it('Video access - allowed', async function() { + // await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test, sub_to_test); + // const video_obj = auth_api.getUserVideo('admin', video_to_test, true); + // assert(video_obj); + // }); + // }); + +}); \ No newline at end of file From 07b48a4da154094aef63b50d5e59d9733f28e3e0 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 16 May 2021 02:55:27 -0600 Subject: [PATCH 031/209] Fixed backend security issues with several dependencies --- backend/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 74f76fa..e7edcd2 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -366,9 +366,9 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1885,9 +1885,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.defaults": { "version": "4.2.0", From 419fe3c3c6a0739fdfc7cc7d3805a987aa26ca4c Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 16 May 2021 02:58:16 -0600 Subject: [PATCH 032/209] Fixed frontend security issues for several depepndencies --- package-lock.json | 316 +++++++++++++++++++--------------------------- 1 file changed, 131 insertions(+), 185 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ca348b..61b14b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -180,9 +180,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "semver": { @@ -316,6 +316,12 @@ "ms": "2.1.2" } }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, "resolve": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", @@ -432,9 +438,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "semver": { @@ -705,9 +711,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true } } @@ -784,9 +790,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true } } @@ -1592,9 +1598,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" } } }, @@ -1609,9 +1615,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" } } }, @@ -1760,6 +1766,12 @@ "semver-intersect": "1.4.0" }, "dependencies": { + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -2616,15 +2628,6 @@ "tweetnacl": "^0.14.3" } }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "requires": { - "callsite": "1.0.0" - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -3059,12 +3062,6 @@ "caller-callsite": "^2.0.0" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", @@ -4513,24 +4510,24 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -4582,37 +4579,37 @@ } }, "engine.io": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", - "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", + "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "0.3.1", + "cookie": "~0.4.1", "debug": "~4.1.0", "engine.io-parser": "~2.2.0", - "ws": "^7.1.2" + "ws": "~7.4.2" }, "dependencies": { "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "dev": true }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", "dev": true } } }, "engine.io-client": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz", - "integrity": "sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz", + "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==", "dev": true, "requires": { "component-emitter": "~1.3.0", @@ -4623,8 +4620,8 @@ "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~6.1.0", - "xmlhttprequest-ssl": "~1.5.4", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.6.2", "yeast": "0.1.2" }, "dependencies": { @@ -4643,26 +4640,11 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true } } }, @@ -5930,9 +5912,9 @@ } }, "hosted-git-info": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", - "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6338,9 +6320,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "inquirer": { @@ -6405,9 +6387,9 @@ "dev": true }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "supports-color": { @@ -7498,9 +7480,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.memoize": { "version": "4.1.2", @@ -7719,9 +7701,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -8289,9 +8271,9 @@ }, "dependencies": { "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true } } @@ -8440,9 +8422,9 @@ }, "dependencies": { "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "lru-cache": { @@ -8516,12 +8498,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -8942,9 +8918,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "lru-cache": { @@ -9008,9 +8984,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -9144,22 +9120,16 @@ } }, "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", + "dev": true }, "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", + "dev": true }, "parseurl": { "version": "1.3.3", @@ -11677,16 +11647,16 @@ } }, "socket.io": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", - "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", + "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", "dev": true, "requires": { "debug": "~4.1.0", - "engine.io": "~3.4.0", + "engine.io": "~3.5.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.3.0", + "socket.io-client": "2.4.0", "socket.io-parser": "~3.4.0" } }, @@ -11697,38 +11667,32 @@ "dev": true }, "socket.io-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", - "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", + "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", "dev": true, "requires": { "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "engine.io-client": "~3.4.0", + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "engine.io-client": "~3.5.0", "has-binary2": "~1.0.2", - "has-cors": "1.1.0", "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", + "parseqs": "0.0.6", + "parseuri": "0.0.6", "socket.io-parser": "~3.3.0", "to-array": "0.1.4" }, "dependencies": { - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } }, "isarray": { "version": "2.0.1", @@ -11743,31 +11707,14 @@ "dev": true }, "socket.io-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz", - "integrity": "sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", + "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", "dev": true, "requires": { "component-emitter": "~1.3.0", "debug": "~3.1.0", "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } } } @@ -12082,9 +12029,9 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" @@ -13074,9 +13021,9 @@ } }, "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", + "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", "dev": true, "requires": { "querystringify": "^2.1.1", @@ -13796,8 +13743,7 @@ }, "ssri": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "resolved": "", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -14538,9 +14484,9 @@ "dev": true }, "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.2.tgz", + "integrity": "sha512-tYOaldF/0BLfKuoA39QMwD4j2m8lq4DIncqj1yuNELX4vz9+z/ieG/vwmctjJce+boFHXstqhWnHSxc4W8f4qg==", "dev": true }, "xtend": { From b933af03e2f1eaf7825e9d9a28cedd34164ce785 Mon Sep 17 00:00:00 2001 From: Erwan Date: Sat, 22 May 2021 14:58:48 +0200 Subject: [PATCH 033/209] Update API docs links in settings --- src/app/settings/settings.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 346797c..10a7613 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -219,7 +219,7 @@
From e2c31319cf236bff1d5d1b26b74d101112f5f1b9 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 23 May 2021 03:59:38 -0600 Subject: [PATCH 034/209] Migrated playlist and subscription (per video and sub-wide) video downloading functionality to new schema Migrated modify playlist component to new schema Moved wait function and playlist generation function(s) to utils - added tests for zip generation --- backend/app.js | 95 ++++++------------- backend/db.js | 1 - backend/test/tests.js | 36 +++++++ backend/utils.js | 54 ++++++++++- .../custom-playlists.component.ts | 2 +- .../create-playlist.component.html | 4 +- .../modify-playlist.component.html | 54 ++++++----- .../modify-playlist.component.ts | 30 +++--- src/app/file-card/file-card.component.ts | 2 +- src/app/main/main.component.ts | 2 +- src/app/player/player.component.ts | 5 +- src/app/posts.services.ts | 18 +++- .../subscription/subscription.component.ts | 5 +- 13 files changed, 189 insertions(+), 119 deletions(-) diff --git a/backend/app.js b/backend/app.js index 8ae57ac..065b339 100644 --- a/backend/app.js +++ b/backend/app.js @@ -193,16 +193,6 @@ app.use(auth_api.passport.initialize()); // actual functions -/** - * setTimeout, but its a promise. - * @param {number} ms - */ -async function wait(ms) { - await new Promise(resolve => { - setTimeout(resolve, ms); - }); -} - async function checkMigrations() { // 3.5->3.6 migration const files_to_db_migration_complete = true; // migration phased out! previous code: db.get('files_to_db_migration_complete').value(); @@ -529,7 +519,7 @@ async function backupServerLite() { }); // wait a tiny bit for the zip to reload in fs - await wait(100); + await utils.wait(100); return true; } @@ -597,7 +587,7 @@ async function killAllDownloads() { async function setPortItemFromENV() { config_api.setConfigItem('ytdl_port', backendPort.toString()); - await wait(100); + await utils.wait(100); return true; } @@ -611,7 +601,7 @@ async function setConfigFromEnv() { let success = config_api.setConfigItems(config_items); if (success) { logger.info('Config items set using ENV variables.'); - await wait(100); + await utils.wait(100); return true; } else { logger.error('ERROR: Failed to set config items using ENV variables.'); @@ -847,47 +837,6 @@ function getVideoFormatID(name) } } -async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvided = null, user_uid = null) { - let zipFolderPath = null; - - if (!fullPathProvided) { - zipFolderPath = (type === 'audio') ? audioFolderPath : videoFolderPath - if (user_uid) zipFolderPath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, zipFolderPath); - } else { - zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path')); - } - - let ext = (type === 'audio') ? '.mp3' : '.mp4'; - - let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip')); - - var archive = archiver('zip', { - gzip: true, - zlib: { level: 9 } // Sets the compression level. - }); - - archive.on('error', function(err) { - logger.error(err); - throw err; - }); - - // pipe archive data to the output file - archive.pipe(output); - - for (let i = 0; i < fileNames.length; i++) { - let fileName = fileNames[i]; - let fileNamePathRemoved = path.parse(fileName).base; - let file_path = !fullPathProvided ? path.join(zipFolderPath, fileName + ext) : fileName; - archive.file(file_path, {name: fileNamePathRemoved + ext}) - } - - await archive.finalize(); - - // wait a tiny bit for the zip to reload in fs - await wait(100); - return path.join(zipFolderPath,outputName + '.zip'); -} - // TODO: add to db_api and support multi-user mode async function deleteFile(uid, uuid = null, blacklistMode = false) { const file_obj = await db_api.getVideo(uid, uuid); @@ -2523,18 +2472,19 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => { let include_file_metadata = req.body.include_file_metadata; const playlist = await db_api.getPlaylist(playlist_id, uuid); + const file_objs = []; if (playlist && include_file_metadata) { - playlist['file_objs'] = []; for (let i = 0; i < playlist['uids'].length; i++) { const uid = playlist['uids'][i]; const file_obj = await db_api.getVideo(uid, uuid); - playlist['file_objs'].push(file_obj); + file_objs.push(file_obj); } } res.send({ playlist: playlist, + file_objs: file_objs, type: playlist && playlist.type, success: !!playlist }); @@ -2616,32 +2566,47 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { app.post('/api/downloadFile', optionalJwt, async (req, res) => { let uid = req.body.uid; - let is_playlist = req.body.is_playlist; let uuid = req.body.uuid; + let playlist_id = req.body.playlist_id; + let sub_id = req.body.sub_id; let file_path_to_download = null; if (!uuid && req.user) uuid = req.user.uid; - if (is_playlist) { + + let zip_file_generated = false; + if (playlist_id) { + zip_file_generated = true; const playlist_files_to_download = []; - const playlist = db_api.getPlaylist(uid, uuid); + const playlist = await db_api.getPlaylist(playlist_id, uuid); for (let i = 0; i < playlist['uids'].length; i++) { - const uid = playlist['uids'][i]; - const file_obj = await db_api.getVideo(uid, uuid); - playlist_files_to_download.push(file_obj.path); + const playlist_file_uid = playlist['uids'][i]; + const file_obj = await db_api.getVideo(playlist_file_uid, uuid); + playlist_files_to_download.push(file_obj); } // generate zip - file_path_to_download = await createPlaylistZipFile(playlist_files_to_download, playlist.type, playlist.name); + file_path_to_download = await utils.createContainerZipFile(playlist, playlist_files_to_download); + } else if (sub_id && !uid) { + zip_file_generated = true; + const sub_files_to_download = []; + const sub = subscriptions_api.getSubscription(sub_id, uuid); + for (let i = 0; i < sub['videos'].length; i++) { + const sub_file = sub['videos'][i]; + sub_files_to_download.push(sub_file); + } + + // generate zip + file_path_to_download = await utils.createContainerZipFile(sub, sub_files_to_download); } else { - const file_obj = await db_api.getVideo(uid, uuid) + const file_obj = await db_api.getVideo(uid, uuid, sub_id) file_path_to_download = file_obj.path; } if (!path.isAbsolute(file_path_to_download)) file_path_to_download = path.join(__dirname, file_path_to_download); res.sendFile(file_path_to_download, function (err) { if (err) { logger.error(err); - } else if (is_playlist) { + } else if (zip_file_generated) { try { // delete generated zip file fs.unlinkSync(file_path_to_download); diff --git a/backend/db.js b/backend/db.js index 061280b..106c3f2 100644 --- a/backend/db.js +++ b/backend/db.js @@ -245,7 +245,6 @@ exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = fal if (uid) playlist['uids'].push(uid); else logger.warn(`Failed to convert file with name ${fileName} to its UID while converting playlist ${playlist['name']} to the new UID-based schema. The original file is likely missing/deleted and it will be skipped.`); } - delete playlist['fileNames']; exports.updatePlaylist(playlist, user_uid); } diff --git a/backend/test/tests.js b/backend/test/tests.js index c9726a0..9697a36 100644 --- a/backend/test/tests.js +++ b/backend/test/tests.js @@ -35,13 +35,19 @@ const logger = winston.createLogger({ var auth_api = require('../authentication/auth'); var db_api = require('../db'); +const utils = require('../utils'); +const subscriptions_api = require('../subscriptions'); +const fs = require('fs-extra'); db_api.initialize(db, users_db, logger); auth_api.initialize(db, users_db, logger); +subscriptions_api.initialize(db, users_db, logger, db_api); describe('Multi User', async function() { let user = null; const user_to_test = 'admin'; + const sub_to_test = 'dc834388-3454-41bf-a618-e11cb8c7de1c'; + const playlist_to_test = 'ysabVZz4x'; before(async function() { user = await auth_api.login('admin', 'pass'); console.log('hi') @@ -70,6 +76,36 @@ describe('Multi User', async function() { assert(video_obj); }); }); + describe('Zip generators', function() { + it('Playlist zip generator', async function() { + const playlist = await db_api.getPlaylist(playlist_to_test, user_to_test); + assert(playlist); + const playlist_files_to_download = []; + for (let i = 0; i < playlist['uids'].length; i++) { + const uid = playlist['uids'][i]; + const playlist_file = await db_api.getVideo(uid, user_to_test); + playlist_files_to_download.push(playlist_file); + } + const zip_path = await utils.createContainerZipFile(playlist, playlist_files_to_download); + const zip_exists = fs.pathExistsSync(zip_path); + assert(zip_exists); + if (zip_exists) fs.unlinkSync(zip_path); + }); + + it('Subscription zip generator', async function() { + const sub = subscriptions_api.getSubscription(sub_to_test, user_to_test); + assert(sub); + const sub_files_to_download = []; + for (let i = 0; i < sub['videos'].length; i++) { + const sub_file = sub['videos'][i]; + sub_files_to_download.push(sub_file); + } + const zip_path = await utils.createContainerZipFile(sub, sub_files_to_download); + const zip_exists = fs.pathExistsSync(zip_path); + assert(zip_exists); + if (zip_exists) fs.unlinkSync(zip_path); + }); + }); // describe('Video player - subscription', function() { // const sub_to_test = ''; // const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e'; diff --git a/backend/utils.js b/backend/utils.js index cd7c23d..2b825dd 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -1,6 +1,7 @@ -var fs = require('fs-extra') -var path = require('path') +const fs = require('fs-extra') +const path = require('path') const config_api = require('./config'); +const archiver = require('archiver'); const is_windows = process.platform === 'win32'; @@ -52,6 +53,43 @@ async function getDownloadedFilesByType(basePath, type, full_metadata = false) { return files; } +async function createContainerZipFile(container_obj, container_file_objs) { + const container_files_to_download = []; + for (let i = 0; i < container_file_objs.length; i++) { + const container_file_obj = container_file_objs[i]; + container_files_to_download.push(container_file_obj.path); + } + return await createZipFile(path.join('appdata', container_obj.name + '.zip'), container_files_to_download); +} + +async function createZipFile(zip_file_path, file_paths) { + let output = fs.createWriteStream(zip_file_path); + + var archive = archiver('zip', { + gzip: true, + zlib: { level: 9 } // Sets the compression level. + }); + + archive.on('error', function(err) { + logger.error(err); + throw err; + }); + + // pipe archive data to the output file + archive.pipe(output); + + for (let file_path of file_paths) { + const file_name = path.parse(file_path).base; + archive.file(file_path, {name: file_name}) + } + + await archive.finalize(); + + // wait a tiny bit for the zip to reload in fs + await wait(100); + return zip_file_path; +} + function getJSONMp4(name, customPath, openReadPerms = false) { var obj = null; // output if (!customPath) customPath = config_api.getConfigItem('ytdl_video_folder_path'); @@ -193,6 +231,16 @@ function removeFileExtension(filename) { return filename_parts.join('.'); } +/** + * setTimeout, but its a promise. + * @param {number} ms + */ + async function wait(ms) { + await new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + // objects function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) { @@ -221,7 +269,9 @@ module.exports = { fixVideoMetadataPerms: fixVideoMetadataPerms, deleteJSONFile: deleteJSONFile, getDownloadedFilesByType: getDownloadedFilesByType, + createContainerZipFile: createContainerZipFile, recFindByExt: recFindByExt, removeFileExtension: removeFileExtension, + wait: wait, File: File } diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index 8870a8e..69c7908 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -97,7 +97,7 @@ export class CustomPlaylistsComponent implements OnInit { const index = args.index; const dialogRef = this.dialog.open(ModifyPlaylistComponent, { data: { - playlist: playlist, + playlist_id: playlist.id, width: '65vw' } }); diff --git a/src/app/create-playlist/create-playlist.component.html b/src/app/create-playlist/create-playlist.component.html index d9f108a..ad21ec0 100644 --- a/src/app/create-playlist/create-playlist.component.html +++ b/src/app/create-playlist/create-playlist.component.html @@ -20,8 +20,8 @@ Videos {{file.id}} - {{file.id}} - {{file.id}} + {{file.id}} + {{file.id}} diff --git a/src/app/dialogs/modify-playlist/modify-playlist.component.html b/src/app/dialogs/modify-playlist/modify-playlist.component.html index 69f4cad..a8471bb 100644 --- a/src/app/dialogs/modify-playlist/modify-playlist.component.html +++ b/src/app/dialogs/modify-playlist/modify-playlist.component.html @@ -1,38 +1,40 @@

Modify playlist

- -
- - - -
- -
-
- Normal order  - Reverse order  - +
+ +
+ + +
-
- -
-
+
+
+ Normal order  + Reverse order  + +
- - - -
{{playlist_item}}
-
- - - - +
+ +
+
+ + + + +
{{playlist_item.title}}
+
+ + + + +
- + \ No newline at end of file diff --git a/src/app/dialogs/modify-playlist/modify-playlist.component.ts b/src/app/dialogs/modify-playlist/modify-playlist.component.ts index 414fc92..161cab8 100644 --- a/src/app/dialogs/modify-playlist/modify-playlist.component.ts +++ b/src/app/dialogs/modify-playlist/modify-playlist.component.ts @@ -10,8 +10,12 @@ import { PostsService } from 'app/posts.services'; }) export class ModifyPlaylistComponent implements OnInit { + playlist_id = null; + original_playlist = null; playlist = null; + playlist_file_objs = null; + available_files = []; all_files = []; playlist_updated = false; @@ -23,9 +27,8 @@ export class ModifyPlaylistComponent implements OnInit { ngOnInit(): void { if (this.data) { - this.playlist = JSON.parse(JSON.stringify(this.data.playlist)); - this.original_playlist = JSON.parse(JSON.stringify(this.data.playlist)); - this.getFiles(); + this.playlist_id = this.data.playlist_id; + this.getPlaylist(); } this.reverse_order = localStorage.getItem('default_playlist_order_reversed') === 'true'; @@ -44,11 +47,12 @@ export class ModifyPlaylistComponent implements OnInit { } processFiles(new_files = null) { - if (new_files) { this.all_files = new_files.map(file => file.id); } - this.available_files = this.all_files.filter(e => !this.playlist.fileNames.includes(e)) + if (new_files) { this.all_files = new_files; } + this.available_files = this.all_files.filter(e => !this.playlist_file_objs.includes(e)) } updatePlaylist() { + this.playlist['uids'] = this.playlist_file_objs.map(playlist_file_obj => playlist_file_obj['uid']) this.postsService.updatePlaylist(this.playlist).subscribe(res => { this.playlist_updated = true; this.postsService.openSnackBar('Playlist updated successfully.'); @@ -61,24 +65,26 @@ export class ModifyPlaylistComponent implements OnInit { } getPlaylist() { - this.postsService.getPlaylist(this.playlist.id, this.playlist.type, null).subscribe(res => { + this.postsService.getPlaylist(this.playlist_id, null, true).subscribe(res => { if (res['playlist']) { this.playlist = res['playlist']; + this.playlist_file_objs = res['file_objs']; this.original_playlist = JSON.parse(JSON.stringify(this.playlist)); + this.getFiles(); } }); } addContent(file) { - this.playlist.fileNames.push(file); + this.playlist_file_objs.push(file); this.processFiles(); } removeContent(index) { if (this.reverse_order) { - index = this.playlist.fileNames.length - 1 - index; + index = this.playlist_file_objs.length - 1 - index; } - this.playlist.fileNames.splice(index, 1); + this.playlist_file_objs.splice(index, 1); this.processFiles(); } @@ -89,10 +95,10 @@ export class ModifyPlaylistComponent implements OnInit { drop(event: CdkDragDrop) { if (this.reverse_order) { - event.previousIndex = this.playlist.fileNames.length - 1 - event.previousIndex; - event.currentIndex = this.playlist.fileNames.length - 1 - event.currentIndex; + event.previousIndex = this.playlist_file_objs.length - 1 - event.previousIndex; + event.currentIndex = this.playlist_file_objs.length - 1 - event.currentIndex; } - moveItemInArray(this.playlist.fileNames, event.previousIndex, event.currentIndex); + moveItemInArray(this.playlist_file_objs, event.previousIndex, event.currentIndex); } } diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 90b906c..5596eec 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -84,7 +84,7 @@ export class FileCardComponent implements OnInit { editPlaylistDialog() { const dialogRef = this.dialog.open(ModifyPlaylistComponent, { data: { - playlist: this.playlist, + playlist_id: this.playlist.id, width: '65vw' } }); diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index e37d041..cd1e3ab 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -657,7 +657,7 @@ export class MainComponent implements OnInit { } downloadPlaylist(playlist) { - this.postsService.downloadFileFromServer(playlist.id, null, true).subscribe(res => { + this.postsService.downloadPlaylistFromServer(playlist.id).subscribe(res => { if (playlist.id) { this.downloading_content[playlist.type][playlist.id] = false }; const blob: Blob = res; saveAs(blob, playlist.name + '.zip'); diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 601271d..d0dd480 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -187,6 +187,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.postsService.getPlaylist(this.playlist_id, this.uuid, true).subscribe(res => { if (res['playlist']) { this.db_playlist = res['playlist']; + this.db_playlist['file_objs'] = res['file_objs']; this.uids = this.db_playlist.uids; this.type = res['type']; this.show_player = true; @@ -316,7 +317,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const zipName = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0]; this.downloading = true; - this.postsService.downloadFileFromServer(this.playlist_id, this.uuid, true).subscribe(res => { + this.postsService.downloadPlaylistFromServer(this.playlist_id, this.uuid).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, zipName + '.zip'); @@ -330,7 +331,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const ext = (this.type === 'audio') ? '.mp3' : '.mp4'; const filename = this.playlist[0].title; this.downloading = true; - this.postsService.downloadFileFromServer(this.uid, this.uuid, false).subscribe(res => { + this.postsService.downloadFileFromServer(this.uid, this.uuid, this.sub_id).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, filename + ext); diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index cf04447..f0fc20e 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -247,17 +247,29 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid, sub: sub}, this.httpOptions); } - downloadFileFromServer(uid, uuid = null, is_playlist = false) { + downloadFileFromServer(uid, uuid = null, sub_id = null) { return this.http.post(this.path + 'downloadFile', { uid: uid, uuid: uuid, - is_playlist: is_playlist + sub_id: sub_id }, {responseType: 'blob', params: this.httpOptions.params}); } downloadPlaylistFromServer(playlist_id, uuid = null) { - return this.http.post(this.path + 'downloadPlaylist', {playlist_id: playlist_id, uuid: uuid}); + return this.http.post(this.path + 'downloadFile', { + uuid: uuid, + playlist_id: playlist_id + }, + {responseType: 'blob', params: this.httpOptions.params}); + } + + downloadSubFromServer(sub_id, uuid = null) { + return this.http.post(this.path + 'downloadFile', { + uuid: uuid, + sub_id: sub_id + }, + {responseType: 'blob', params: this.httpOptions.params}); } checkConcurrentStream(uid) { diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index af08088..cb40b1c 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -153,15 +153,14 @@ export class SubscriptionComponent implements OnInit, OnDestroy { } this.downloading = true; - // TODO: add download subscription route - /*this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => { + this.postsService.downloadSubFromServer(this.subscription.id).subscribe(res => { this.downloading = false; const blob: Blob = res; saveAs(blob, this.subscription.name + '.zip'); }, err => { console.log(err); this.downloading = false; - });*/ + }); } editSubscription() { From 4ea239170ee2e4641833ac9f5370f4aacd37de26 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 30 May 2021 00:39:00 -0600 Subject: [PATCH 035/209] If multiple videos exist in one URL, a playlist will be auto generated Removed tomp3 and tomp4 routes, replaced with /downloadFile Simplified category->playlist conversion Simplified playlist creation Simplified file deletion Playlist duration calculation is now done on the backend (categories uses this now too) removeIDFromArchive moved from subscriptions->utils Added plumbing to support type agnostic playlists --- backend/app.js | 223 +++----------- backend/authentication/auth.js | 109 +------ backend/categories.js | 21 ++ backend/db.js | 155 +++++++++- backend/subscriptions.js | 30 +- backend/utils.js | 50 ++++ .../custom-playlists.component.ts | 2 - .../recent-videos/recent-videos.component.ts | 2 +- .../create-playlist.component.ts | 35 +-- .../share-media-dialog.component.html | 3 +- .../share-media-dialog.component.ts | 6 +- src/app/main/main.component.ts | 278 ++++++------------ src/app/player/player.component.html | 8 +- src/app/player/player.component.ts | 19 +- src/app/posts.services.ts | 40 +-- 15 files changed, 381 insertions(+), 600 deletions(-) diff --git a/backend/app.js b/backend/app.js index 065b339..10db1ac 100644 --- a/backend/app.js +++ b/backend/app.js @@ -231,7 +231,7 @@ async function runFilesToDBMigration() { const file_already_in_db = db.get('files.audio').find({id: file_obj.id}).value(); if (!file_already_in_db) { logger.verbose(`Migrating file ${file_obj.id}`); - await db_api.registerFileDB(file_obj.id + '.mp3', 'audio'); + db_api.registerFileDB(file_obj.id + '.mp3', 'audio'); } } @@ -240,7 +240,7 @@ async function runFilesToDBMigration() { const file_already_in_db = db.get('files.video').find({id: file_obj.id}).value(); if (!file_already_in_db) { logger.verbose(`Migrating file ${file_obj.id}`); - await db_api.registerFileDB(file_obj.id + '.mp4', 'video'); + db_api.registerFileDB(file_obj.id + '.mp4', 'video'); } } @@ -837,87 +837,6 @@ function getVideoFormatID(name) } } -// TODO: add to db_api and support multi-user mode -async function deleteFile(uid, uuid = null, blacklistMode = false) { - const file_obj = await db_api.getVideo(uid, uuid); - const type = file_obj.isAudio ? 'audio' : 'video'; - const folderPath = path.dirname(file_obj.path); - const ext = type === 'audio' ? 'mp3' : 'mp4'; - const name = file_obj.id; - const filePathNoExtension = utils.removeFileExtension(file_obj.path); - - var jsonPath = `${file_obj.path}.info.json`; - var altJSONPath = `${filePathNoExtension}.info.json`; - var thumbnailPath = `${filePathNoExtension}.webp`; - var altThumbnailPath = `${filePathNoExtension}.jpg`; - - jsonPath = path.join(__dirname, jsonPath); - altJSONPath = path.join(__dirname, altJSONPath); - - let jsonExists = await fs.pathExists(jsonPath); - let thumbnailExists = await fs.pathExists(thumbnailPath); - - if (!jsonExists) { - if (await fs.pathExists(altJSONPath)) { - jsonExists = true; - jsonPath = altJSONPath; - } - } - - if (!thumbnailExists) { - if (await fs.pathExists(altThumbnailPath)) { - thumbnailExists = true; - thumbnailPath = altThumbnailPath; - } - } - - let fileExists = await fs.pathExists(file_obj.path); - - if (config_api.descriptors[name]) { - try { - for (let i = 0; i < config_api.descriptors[name].length; i++) { - config_api.descriptors[name][i].destroy(); - } - } catch(e) { - - } - } - - let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); - if (useYoutubeDLArchive) { - const archive_path = path.join(archivePath, `archive_${type}.txt`); - - // get ID from JSON - - var jsonobj = await (type === 'audio' ? utils.getJSONMp3(name, folderPath) : utils.getJSONMp4(name, folderPath)); - let id = null; - if (jsonobj) id = jsonobj.id; - - // use subscriptions API to remove video from the archive file, and write it to the blacklist - if (await fs.pathExists(archive_path)) { - const line = id ? await subscriptions_api.removeIDFromArchive(archive_path, id) : null; - if (blacklistMode && line) await writeToBlacklist(type, line); - } else { - logger.info('Could not find archive file for audio files. Creating...'); - await fs.close(await fs.open(archive_path, 'w')); - } - } - - if (jsonExists) await fs.unlink(jsonPath); - if (thumbnailExists) await fs.unlink(thumbnailPath); - if (fileExists) { - await fs.unlink(file_obj.path); - if (await fs.pathExists(jsonPath) || await fs.pathExists(file_obj.path)) { - return false; - } else { - return true; - } - } else { - // TODO: tell user that the file didn't exist - return true; - } -} - /** * @param {'audio' | 'video'} type * @param {string[]} fileNames @@ -1036,7 +955,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { download['downloading'] = false; download['timestamp_end'] = Date.now(); - var file_uid = null; + var file_objs = []; let new_date = Date.now(); let difference = (new_date - date)/1000; logger.debug(`${is_audio ? 'Audio' : 'Video'} download delay: ${difference} seconds.`); @@ -1108,9 +1027,12 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } // registers file in DB - file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category, options.cropFileSettings); + const file_obj = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category, options.cropFileSettings); + // TODO: remove the following line if (file_name) file_names.push(file_name); + + file_objs.push(file_obj); } let is_playlist = file_names.length > 1; @@ -1126,12 +1048,22 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { download['fileNames'] = is_playlist ? file_names : [full_file_path] updateDownloads(); - var videopathEncoded = encodeURIComponent(file_names[0]); + let container = null; + + if (file_objs.length > 1) { + // create playlist + const playlist_name = file_objs.map(file_obj => file_obj.title).join(', '); + const duration = file_objs.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0); + container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, file_objs[0]['thumbnailURL'], options.user); + } else if (file_objs.length === 1) { + container = file_objs[0]; + } else { + logger.error('Downloaded file failed to result in metadata object.'); + } resolve({ - [(type === 'audio') ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded, - file_names: is_playlist ? file_names : null, - uid: file_uid + file_uids: file_objs.map(file_obj => file_obj.uid), + container: container }); } }); @@ -1260,7 +1192,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) { videopathEncoded = encodeURIComponent(utils.removeFileExtension(base_file_name)); resolve({ - [is_audio ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded, + encodedPath: videopathEncoded, file_names: /*is_playlist ? file_names :*/ null, // playlist support is not ready uid: file_uid }); @@ -1727,18 +1659,18 @@ app.use(function(req, res, next) { app.use(compression()); -const optionalJwt = function (req, res, next) { +const optionalJwt = async function (req, res, next) { const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); if (multiUserMode && ((req.body && req.body.uuid) || (req.query && req.query.uuid)) && (req.path.includes('/api/getFile') || req.path.includes('/api/stream') || req.path.includes('/api/getPlaylist') || - req.path.includes('/api/downloadFile'))) { + req.path.includes('/api/downloadFileFromServer'))) { // check if shared video const using_body = req.body && req.body.uuid; const uuid = using_body ? req.body.uuid : req.query.uuid; const uid = using_body ? req.body.uid : req.query.uid; const playlist_id = using_body ? req.body.playlist_id : req.query.playlist_id; - const file = !playlist_id ? auth_api.getUserVideo(uuid, uid, true) : db_api.getPlaylist(playlist_id, uuid, true); + const file = !playlist_id ? auth_api.getUserVideo(uuid, uid, true) : await db_api.getPlaylist(playlist_id, uuid, true); if (file) { req.can_watch = true; return next(); @@ -1783,38 +1715,10 @@ app.post('/api/restartServer', optionalJwt, (req, res) => { res.send({success: true}); }); -app.post('/api/tomp3', optionalJwt, async function(req, res) { - var url = req.body.url; - var options = { - customArgs: req.body.customArgs, - customOutput: req.body.customOutput, - maxBitrate: req.body.maxBitrate, - customQualityConfiguration: req.body.customQualityConfiguration, - youtubeUsername: req.body.youtubeUsername, - youtubePassword: req.body.youtubePassword, - ui_uid: req.body.ui_uid, - user: req.isAuthenticated() ? req.user.uid : null - } - - const safeDownloadOverride = config_api.getConfigItem('ytdl_safe_download_override') || config_api.globalArgsRequiresSafeDownload(); - if (safeDownloadOverride) logger.verbose('Download is running with the safe download override.'); - const is_playlist = url.includes('playlist'); - - let result_obj = null; - if (true || safeDownloadOverride || is_playlist || options.customQualityConfiguration || options.customArgs || options.maxBitrate) - result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID); - else - result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID); - if (result_obj) { - res.send(result_obj); - } else { - res.sendStatus(500); - } -}); - -app.post('/api/tomp4', optionalJwt, async function(req, res) { +app.post('/api/downloadFile', optionalJwt, async function(req, res) { req.setTimeout(0); // remove timeout in case of long videos - var url = req.body.url; + const url = req.body.url; + const type = req.body.type; var options = { customArgs: req.body.customArgs, customOutput: req.body.customOutput, @@ -1833,7 +1737,7 @@ app.post('/api/tomp4', optionalJwt, async function(req, res) { let result_obj = null; if (true || safeDownloadOverride || is_playlist || options.customQualityConfiguration || options.customArgs || options.selectedHeight || !url.includes('youtu')) - result_obj = await downloadFileByURL_exec(url, 'video', options, req.query.sessionID); + result_obj = await downloadFileByURL_exec(url, type, options, req.query.sessionID); else result_obj = await downloadFileByURL_normal(url, 'video', options, req.query.sessionID); if (result_obj) { @@ -1936,43 +1840,22 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { // these are returned let files = null; let playlists = null; + const uuid = req.isAuthenticated() ? req.user.uid : null; let subscriptions = config_api.getConfigItem('ytdl_allow_subscriptions') ? (subscriptions_api.getSubscriptions(req.isAuthenticated() ? req.user.uid : null)) : []; // get basic info depending on multi-user mode being enabled - if (req.isAuthenticated()) { + if (uuid) { files = auth_api.getUserVideos(req.user.uid); playlists = auth_api.getUserPlaylists(req.user.uid, files); } else { files = db.get('files').value(); playlists = JSON.parse(JSON.stringify(db.get('playlists').value())); - const categories = db.get('categories').value(); - if (categories) { - categories.forEach(category => { - const audio_files = files && files.filter(file => file.category && file.category.uid === category.uid && file.isAudio); - const video_files = files && files.filter(file => file.category && file.category.uid === category.uid && !file.isAudio); - if (audio_files && audio_files.length > 0) { - playlists.push({ - name: category['name'], - thumbnailURL: audio_files[0].thumbnailURL, - thumbnailPath: audio_files[0].thumbnailPath, - fileNames: audio_files.map(file => file.id), - type: 'audio', - auto: true - }); - } - if (video_files && video_files.length > 0) { - playlists.push({ - name: category['name'], - thumbnailURL: video_files[0].thumbnailURL, - thumbnailPath: video_files[0].thumbnailPath, - fileNames: video_files.map(file => file.id), - type: 'video', - auto: true - }); - } - }); - } + } + + const categories = categories_api.getCategoriesAsPlaylists(files); + if (categories) { + playlists = playlists.concat(categories); } // loop through subscriptions and add videos @@ -2439,26 +2322,8 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => { let uids = req.body.uids; let type = req.body.type; let thumbnailURL = req.body.thumbnailURL; - let duration = req.body.duration; - - let new_playlist = { - name: playlistName, - uids: uids, - id: shortid.generate(), - thumbnailURL: thumbnailURL, - type: type, - registered: Date.now(), - duration: duration - }; - - if (req.isAuthenticated()) { - auth_api.addPlaylist(req.user.uid, new_playlist, type); - } else { - db.get(`playlists`) - .push(new_playlist) - .write(); - } + const new_playlist = await db_api.createPlaylist(playlistName, uids, type, thumbnailURL, req.isAuthenticated() ? req.user.uid : null); res.send({ new_playlist: new_playlist, @@ -2517,7 +2382,7 @@ app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => { app.post('/api/updatePlaylist', optionalJwt, async (req, res) => { let playlist = req.body.playlist; - let success = db_api.updatePlaylist(playlist, req.user && req.user.uid); + let success = await db_api.updatePlaylist(playlist, req.user && req.user.uid); res.send({ success: success }); @@ -2551,20 +2416,14 @@ app.post('/api/deletePlaylist', optionalJwt, async (req, res) => { app.post('/api/deleteFile', optionalJwt, async (req, res) => { const uid = req.body.uid; const blacklistMode = req.body.blacklistMode; - - if (req.isAuthenticated()) { - let success = await auth_api.deleteUserFile(req.user.uid, uid, blacklistMode); - res.send(success); - return; - } + const uuid = req.isAuthenticated() ? req.user.uid : null; let wasDeleted = false; - wasDeleted = await deleteFile(uid, null, blacklistMode); - db.get('files').remove({uid: uid}).write(); + wasDeleted = await db_api.deleteFile(uid, uuid, blacklistMode); res.send(wasDeleted); }); -app.post('/api/downloadFile', optionalJwt, async (req, res) => { +app.post('/api/downloadFileFromServer', optionalJwt, async (req, res) => { let uid = req.body.uid; let uuid = req.body.uuid; let playlist_id = req.body.playlist_id; diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index 3af31e7..6e83ec3 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -1,12 +1,10 @@ const path = require('path'); const config_api = require('../config'); const consts = require('../consts'); -var subscriptions_api = require('../subscriptions') const fs = require('fs-extra'); -var jwt = require('jsonwebtoken'); +const jwt = require('jsonwebtoken'); const { uuid } = require('uuidv4'); -var bcrypt = require('bcryptjs'); - +const bcrypt = require('bcryptjs'); var LocalStrategy = require('passport-local').Strategy; var LdapStrategy = require('passport-ldapauth'); @@ -299,11 +297,6 @@ exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) { return file; } -exports.addPlaylist = function(user_uid, new_playlist) { - users_db.get('users').find({uid: user_uid}).get(`playlists`).push(new_playlist).write(); - return true; -} - exports.updatePlaylistFiles = function(user_uid, playlistID, new_filenames) { users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}).assign({fileNames: new_filenames}); return true; @@ -317,35 +310,6 @@ exports.removePlaylist = function(user_uid, playlistID) { exports.getUserPlaylists = function(user_uid, user_files = null) { const user = users_db.get('users').find({uid: user_uid}).value(); const playlists = JSON.parse(JSON.stringify(user['playlists'])); - const categories = db.get('categories').value(); - if (categories && user_files) { - categories.forEach(category => { - const audio_files = user_files && user_files.filter(file => file.category && file.category.uid === category.uid && file.isAudio); - const video_files = user_files && user_files.filter(file => file.category && file.category.uid === category.uid && !file.isAudio); - if (audio_files && audio_files.length > 0) { - playlists.push({ - name: category['name'], - thumbnailURL: audio_files[0].thumbnailURL, - thumbnailPath: audio_files[0].thumbnailPath, - fileNames: audio_files.map(file => file.id), - type: 'audio', - uid: user_uid, - auto: true - }); - } - if (video_files && video_files.length > 0) { - playlists.push({ - name: category['name'], - thumbnailURL: video_files[0].thumbnailURL, - thumbnailPath: video_files[0].thumbnailPath, - fileNames: video_files.map(file => file.id), - type: 'video', - uid: user_uid, - auto: true - }); - } - }); - } return playlists; } @@ -369,75 +333,6 @@ exports.registerUserFile = function(user_uid, file_object) { .write(); } -exports.deleteUserFile = async function(user_uid, file_uid, blacklistMode = false) { - let success = false; - const file_obj = users_db.get('users').find({uid: user_uid}).get(`files`).find({uid: file_uid}).value(); - if (file_obj) { - const type = file_obj.isAudio ? 'audio' : 'video'; - const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); - const ext = type === 'audio' ? '.mp3' : '.mp4'; - - // close descriptors - if (config_api.descriptors[file_obj.id]) { - try { - for (let i = 0; i < config_api.descriptors[file_obj.id].length; i++) { - config_api.descriptors[file_obj.id][i].destroy(); - } - } catch(e) { - - } - } - - const full_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext); - users_db.get('users').find({uid: user_uid}).get(`files`) - .remove({ - uid: file_uid - }).write(); - if (await fs.pathExists(full_path)) { - // remove json and file - const json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + '.info.json'); - const alternate_json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext + '.info.json'); - let youtube_id = null; - if (await fs.pathExists(json_path)) { - youtube_id = await fs.readJSON(json_path).id; - await fs.unlink(json_path); - } else if (await fs.pathExists(alternate_json_path)) { - youtube_id = await fs.readJSON(alternate_json_path).id; - await fs.unlink(alternate_json_path); - } - - await fs.unlink(full_path); - - // do archive stuff - - let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); - if (useYoutubeDLArchive) { - const archive_path = path.join(usersFileFolder, user_uid, 'archives', `archive_${type}.txt`); - - // use subscriptions API to remove video from the archive file, and write it to the blacklist - if (await fs.pathExists(archive_path)) { - const line = youtube_id ? await subscriptions_api.removeIDFromArchive(archive_path, youtube_id) : null; - if (blacklistMode && line) { - let blacklistPath = path.join(usersFileFolder, user_uid, 'archives', `blacklist_${type}.txt`); - // adds newline to the beginning of the line - line = '\n' + line; - await fs.appendFile(blacklistPath, line); - } - } else { - logger.info(`Could not find archive file for ${type} files. Creating...`); - await fs.ensureFile(archive_path); - } - } - } - success = true; - } else { - success = false; - logger.warn(`User file ${file_uid} does not exist!`); - } - - return success; -} - exports.changeSharingMode = function(user_uid, file_uid, is_playlist, enabled) { let success = false; const user_db_obj = users_db.get('users').find({uid: user_uid}); diff --git a/backend/categories.js b/backend/categories.js index 2134373..ce56d5c 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -1,4 +1,5 @@ const config_api = require('./config'); +const utils = require('./utils'); var logger = null; var db = null; @@ -68,6 +69,24 @@ function getCategories() { return categories ? categories : null; } +function getCategoriesAsPlaylists(files = null) { + const categories_as_playlists = []; + const available_categories = getCategories(); + if (available_categories && files) { + for (category of available_categories) { + const files_that_match = utils.addUIDsToCategory(category, files); + if (files_that_match && files_that_match.length > 0) { + category['thumbnailURL'] = files_that_match[0].thumbnailURL; + category['thumbnailPath'] = files_that_match[0].thumbnailPath; + category['duration'] = files_that_match.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0); + category['id'] = category['uid']; + categories_as_playlists.push(category); + } + } + } + return categories_as_playlists; +} + function applyCategoryRules(file_json, rules, category_name) { let rules_apply = false; for (let i = 0; i < rules.length; i++) { @@ -126,4 +145,6 @@ async function addTagToExistingTags(tag) { module.exports = { initialize: initialize, categorize: categorize, + getCategories: getCategories, + getCategoriesAsPlaylists: getCategoriesAsPlaylists } \ No newline at end of file diff --git a/backend/db.js b/backend/db.js index 106c3f2..719161c 100644 --- a/backend/db.js +++ b/backend/db.js @@ -53,14 +53,14 @@ exports.registerFileDB = (file_path, type, multiUserMode = null, sub = null, cus } } - const file_uid = registerFileDBManual(db_path, file_object); + const file_obj = registerFileDBManual(db_path, file_object); // remove metadata JSON if needed if (!config_api.getConfigItem('ytdl_include_metadata')) { utils.deleteJSONFile(file_id, type, multiUserMode && multiUserMode.file_path) } - return file_uid; + return file_obj; } function registerFileDBManual(db_path, file_object) { @@ -75,7 +75,7 @@ function registerFileDBManual(db_path, file_object) { // add new file to db db_path.push(file_object).write(); - return file_object['uid']; + return file_object; } function generateFileObject(id, type, customPath = null, sub = null) { @@ -224,17 +224,47 @@ exports.preimportUnregisteredSubscriptionFile = async (sub, appendedBasePath) => return preimported_file_paths; } +exports.createPlaylist = async (playlist_name, uids, type, thumbnail_url, user_uid = null) => { + let new_playlist = { + name: playlist_name, + uids: uids, + id: uuid(), + thumbnailURL: thumbnail_url, + type: type, + registered: Date.now(), + }; + + const duration = await exports.calculatePlaylistDuration(new_playlist, user_uid); + new_playlist.duration = duration; + + if (user_uid) { + users_db.get('users').find({uid: user_uid}).get(`playlists`).push(new_playlist).write(); + } else { + db.get(`playlists`) + .push(new_playlist) + .write(); + } + + return new_playlist; +} + exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = false) => { let playlist = null if (user_uid) { playlist = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlist_id}).value(); - - // prevent unauthorized users from accessing the file info - if (require_sharing && !playlist['sharingEnabled']) return null; } else { playlist = db.get(`playlists`).find({id: playlist_id}).value(); } + if (!playlist) { + playlist = db.get('categories').find({uid: playlist_id}).value(); + if (playlist) { + // category found + const files = await exports.getFiles(user_uid); + utils.addUIDsToCategory(playlist, files); + } + } + // converts playlists to new UID-based schema if (playlist && playlist['fileNames'] && !playlist['uids']) { playlist['uids'] = []; @@ -248,11 +278,18 @@ exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = fal exports.updatePlaylist(playlist, user_uid); } + // prevent unauthorized users from accessing the file info + if (require_sharing && !playlist['sharingEnabled']) return null; + return playlist; } -exports.updatePlaylist = (playlist, user_uid = null) => { +exports.updatePlaylist = async (playlist, user_uid = null) => { let playlistID = playlist.id; + + const duration = await exports.calculatePlaylistDuration(playlist, user_uid); + playlist.duration = duration; + let db_loc = null; if (user_uid) { db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}); @@ -263,6 +300,103 @@ exports.updatePlaylist = (playlist, user_uid = null) => { return true; } +exports.calculatePlaylistDuration = async (playlist, uuid, playlist_file_objs = null) => { + if (!playlist_file_objs) { + playlist_file_objs = []; + for (let i = 0; i < playlist['uids'].length; i++) { + const uid = playlist['uids'][i]; + const file_obj = await exports.getVideo(uid, uuid); + if (file_obj) playlist_file_objs.push(file_obj); + } + } + + return playlist_file_objs.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0); +} + +exports.deleteFile = async (uid, uuid = null, blacklistMode = false) => { + const file_obj = await exports.getVideo(uid, uuid); + const type = file_obj.isAudio ? 'audio' : 'video'; + const folderPath = path.dirname(file_obj.path); + const ext = type === 'audio' ? 'mp3' : 'mp4'; + const name = file_obj.id; + const filePathNoExtension = utils.removeFileExtension(file_obj.path); + + var jsonPath = `${file_obj.path}.info.json`; + var altJSONPath = `${filePathNoExtension}.info.json`; + var thumbnailPath = `${filePathNoExtension}.webp`; + var altThumbnailPath = `${filePathNoExtension}.jpg`; + + jsonPath = path.join(__dirname, jsonPath); + altJSONPath = path.join(__dirname, altJSONPath); + + let jsonExists = await fs.pathExists(jsonPath); + let thumbnailExists = await fs.pathExists(thumbnailPath); + + if (!jsonExists) { + if (await fs.pathExists(altJSONPath)) { + jsonExists = true; + jsonPath = altJSONPath; + } + } + + if (!thumbnailExists) { + if (await fs.pathExists(altThumbnailPath)) { + thumbnailExists = true; + thumbnailPath = altThumbnailPath; + } + } + + let fileExists = await fs.pathExists(file_obj.path); + + if (config_api.descriptors[uid]) { + try { + for (let i = 0; i < config_api.descriptors[uid].length; i++) { + config_api.descriptors[uid][i].destroy(); + } + } catch(e) { + + } + } + + let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); + if (useYoutubeDLArchive) { + const archive_path = uuid ? path.join(usersFileFolder, uuid, 'archives', `archive_${type}.txt`) : path.join('appdata', 'archives', `archive_${type}.txt`); + + // get ID from JSON + + var jsonobj = await (type === 'audio' ? utils.getJSONMp3(name, folderPath) : utils.getJSONMp4(name, folderPath)); + let id = null; + if (jsonobj) id = jsonobj.id; + + // use subscriptions API to remove video from the archive file, and write it to the blacklist + if (await fs.pathExists(archive_path)) { + const line = id ? await utils.removeIDFromArchive(archive_path, id) : null; + if (blacklistMode && line) await writeToBlacklist(type, line); + } else { + logger.info('Could not find archive file for audio files. Creating...'); + await fs.close(await fs.open(archive_path, 'w')); + } + } + + if (jsonExists) await fs.unlink(jsonPath); + if (thumbnailExists) await fs.unlink(thumbnailPath); + + const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; + base_db_path.get('files').remove({uid: uid}).write(); + + if (fileExists) { + await fs.unlink(file_obj.path); + if (await fs.pathExists(jsonPath) || await fs.pathExists(file_obj.path)) { + return false; + } else { + return true; + } + } else { + // TODO: tell user that the file didn't exist + return true; + } +} + // Video ID is basically just the file name without the base path and file extension - this method helps us get away from that exports.getVideoUIDByID = (file_id, uuid = null) => { const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; @@ -270,12 +404,17 @@ exports.getVideoUIDByID = (file_id, uuid = null) => { return file_obj ? file_obj['uid'] : null; } -exports.getVideo = async (file_uid, uuid, sub_id) => { +exports.getVideo = async (file_uid, uuid = null, sub_id = null) => { const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; const sub_db_path = sub_id ? base_db_path.get('subscriptions').find({id: sub_id}).get('videos') : base_db_path.get('files'); return sub_db_path.find({uid: file_uid}).value(); } +exports.getFiles = async (uuid = null) => { + const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; + return base_db_path.get('files').value(); +} + exports.setVideoProperty = async (file_uid, assignment_obj, uuid, sub_id) => { const base_db_path = uuid ? users_db.get('users').find({uid: uuid}) : db; const sub_db_path = sub_id ? base_db_path.get('subscriptions').find({id: sub_id}).get('videos') : base_db_path.get('files'); diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 7ff1d06..8f29cae 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -243,7 +243,7 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, const archive_path = path.join(sub.archive, 'archive.txt') // if archive exists, remove line with video ID if (await fs.pathExists(archive_path)) { - await removeIDFromArchive(archive_path, retrievedID); + utils.removeIDFromArchive(archive_path, retrievedID); } } return true; @@ -597,33 +597,6 @@ function getAppendedBasePath(sub, base_path) { return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name); } -async function removeIDFromArchive(archive_path, id) { - let data = await fs.readFile(archive_path, {encoding: 'utf-8'}); - if (!data) { - logger.error('Archive could not be found.'); - return; - } - - let dataArray = data.split('\n'); // convert file data in an array - const searchKeyword = id; // we are looking for a line, contains, key word id in the file - let lastIndex = -1; // let say, we have not found the keyword - - for (let index=0; index= 0; i--) { + num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i)); + } + return num_sum; +} + +function getMatchingCategoryFiles(category, files) { + return files && files.filter(file => file.category && file.category.uid === category.uid); +} + +function addUIDsToCategory(category, files) { + const files_that_match = getMatchingCategoryFiles(category, files); + category['uids'] = files_that_match.map(file => file.uid); + return files_that_match; +} async function recFindByExt(base,ext,files,result) { @@ -268,8 +314,12 @@ module.exports = { getExpectedFileSize: getExpectedFileSize, fixVideoMetadataPerms: fixVideoMetadataPerms, deleteJSONFile: deleteJSONFile, + removeIDFromArchive, removeIDFromArchive, getDownloadedFilesByType: getDownloadedFilesByType, createContainerZipFile: createContainerZipFile, + durationStringToNumber: durationStringToNumber, + getMatchingCategoryFiles: getMatchingCategoryFiles, + addUIDsToCategory: addUIDsToCategory, recFindByExt: recFindByExt, removeFileExtension: removeFileExtension, wait: wait, diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index 69c7908..3914586 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -53,14 +53,12 @@ export class CustomPlaylistsComponent implements OnInit { goToPlaylist(info_obj) { const playlist = info_obj.file; const playlistID = playlist.id; - const type = playlist.type; if (playlist) { if (this.postsService.config['Extra']['download_only_mode']) { this.downloadPlaylist(playlist.id, playlist.name); } else { localStorage.setItem('player_navigator', this.router.url); - const fileNames = playlist.fileNames; this.router.navigate(['/player', {playlist_id: playlistID, auto: playlist.auto}]); } } else { diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 31ed771..05deb2b 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -221,7 +221,7 @@ export class RecentVideosComponent implements OnInit { if (!this.postsService.config.Extra.file_manager_enabled) { // tell server to delete the file once downloaded - this.postsService.deleteFile(name, type).subscribe(delRes => { + this.postsService.deleteFile(file.uid).subscribe(delRes => { // reload mp4s this.getAllFiles(); }); diff --git a/src/app/create-playlist/create-playlist.component.ts b/src/app/create-playlist/create-playlist.component.ts index b9cf976..c22d32d 100644 --- a/src/app/create-playlist/create-playlist.component.ts +++ b/src/app/create-playlist/create-playlist.component.ts @@ -51,9 +51,8 @@ export class CreatePlaylistComponent implements OnInit { createPlaylist() { const thumbnailURL = this.getThumbnailURL(); - const duration = this.calculateDuration(); this.create_in_progress = true; - this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL, duration).subscribe(res => { + this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL).subscribe(res => { this.create_in_progress = false; if (res['success']) { this.dialogRef.close(true); @@ -78,36 +77,4 @@ export class CreatePlaylistComponent implements OnInit { } return null; } - - getDuration(file_id) { - let properFilesToSelectFrom = this.filesToSelectFrom; - if (!this.filesToSelectFrom) { - properFilesToSelectFrom = this.type === 'audio' ? this.audiosToSelectFrom : this.videosToSelectFrom; - } - for (let i = 0; i < properFilesToSelectFrom.length; i++) { - const file = properFilesToSelectFrom[i]; - if (file.id === file_id) { - return file.duration; - } - } - return null; - } - - calculateDuration() { - let sum = 0; - for (let i = 0; i < this.filesSelect.value.length; i++) { - const duration_val = this.getDuration(this.filesSelect.value[i]); - sum += typeof duration_val === 'string' ? this.durationStringToNumber(duration_val) : duration_val; - } - return sum; - } - - durationStringToNumber(dur_str) { - let num_sum = 0; - const dur_str_parts = dur_str.split(':'); - for (let i = dur_str_parts.length-1; i >= 0; i--) { - num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i)); - } - return num_sum; - } } diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.html b/src/app/dialogs/share-media-dialog/share-media-dialog.component.html index fcd8f3c..7175b52 100644 --- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.html +++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.html @@ -1,7 +1,6 @@

Share playlist - Share video - Share audio + Share file

diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts index 9b687ff..332a461 100644 --- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts +++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts @@ -11,7 +11,6 @@ import { PostsService } from 'app/posts.services'; }) export class ShareMediaDialogComponent implements OnInit { - type = null; uid = null; uuid = null; share_url = null; @@ -26,7 +25,6 @@ export class ShareMediaDialogComponent implements OnInit { ngOnInit(): void { if (this.data) { - this.type = this.data.type; this.uid = this.data.uid; this.uuid = this.data.uuid; this.sharing_enabled = this.data.sharing_enabled; @@ -65,7 +63,7 @@ export class ShareMediaDialogComponent implements OnInit { sharingChanged(event) { if (event.checked) { - this.postsService.enableSharing(this.uid, this.type, this.is_playlist).subscribe(res => { + this.postsService.enableSharing(this.uid, this.is_playlist).subscribe(res => { if (res['success']) { this.openSnackBar('Sharing enabled.'); this.sharing_enabled = true; @@ -76,7 +74,7 @@ export class ShareMediaDialogComponent implements OnInit { this.openSnackBar('Failed to enable sharing - server error.'); }); } else { - this.postsService.disableSharing(this.uid, this.type, this.is_playlist).subscribe(res => { + this.postsService.disableSharing(this.uid, this.is_playlist).subscribe(res => { if (res['success']) { this.openSnackBar('Sharing disabled.'); this.sharing_enabled = false; diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index cd1e3ab..bf99b73 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -342,12 +342,8 @@ export class MainComponent implements OnInit { } } - public goToFile(name, isAudio, uid) { - if (isAudio) { - this.downloadHelperMp3(name, uid, false, false, null, true); - } else { - this.downloadHelperMp4(name, uid, false, false, null, true); - } + public goToFile(container, isAudio, uid) { + this.downloadHelper(container, isAudio ? 'audio' : 'video', false, false, null, true); } public goToPlaylist(playlistID, type) { @@ -379,56 +375,26 @@ export class MainComponent implements OnInit { // download helpers - downloadHelperMp3(name, uid, is_playlist = false, forceView = false, new_download = null, navigate_mode = false) { + downloadHelper(container, type, is_playlist = false, force_view = false, new_download = null, navigate_mode = false) { this.downloadingfile = false; if (this.multiDownloadMode && !this.downloadOnlyMode && !navigate_mode) { // do nothing this.reloadRecentVideos(); } else { // if download only mode, just download the file. no redirect - if (forceView === false && this.downloadOnlyMode && !this.iOS) { + if (force_view === false && this.downloadOnlyMode && !this.iOS) { if (is_playlist) { - const zipName = name[0].split(' ')[0] + name[1].split(' ')[0]; - this.downloadPlaylist(name, 'audio', zipName); + this.downloadPlaylist(container['uid']); } else { - this.downloadAudioFile(decodeURI(name)); + this.downloadFileFromServer(container, type); } this.reloadRecentVideos(); } else { localStorage.setItem('player_navigator', this.router.url.split(';')[0]); if (is_playlist) { - this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'audio'}]); + this.router.navigate(['/player', {playlist_id: container['id'], type: type}]); } else { - this.router.navigate(['/player', {type: 'audio', uid: uid}]); - } - } - } - - // remove download from current downloads - this.removeDownloadFromCurrentDownloads(new_download); - } - - downloadHelperMp4(name, uid, is_playlist = false, forceView = false, new_download = null, navigate_mode = false) { - this.downloadingfile = false; - if (this.multiDownloadMode && !this.downloadOnlyMode && !navigate_mode) { - // do nothing - this.reloadRecentVideos(); - } else { - // if download only mode, just download the file. no redirect - if (forceView === false && this.downloadOnlyMode) { - if (is_playlist) { - const zipName = name[0].split(' ')[0] + name[1].split(' ')[0]; - this.downloadPlaylist(name, 'video', zipName); - } else { - this.downloadVideoFile(decodeURI(name)); - } - this.reloadRecentVideos(); - } else { - localStorage.setItem('player_navigator', this.router.url.split(';')[0]); - if (is_playlist) { - this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'video'}]); - } else { - this.router.navigate(['/player', {type: 'video', uid: uid}]); + this.router.navigate(['/player', {type: type, uid: container['uid']}]); } } } @@ -439,133 +405,85 @@ export class MainComponent implements OnInit { // download click handler downloadClicked() { - if (this.ValidURL(this.url)) { - this.urlError = false; - this.path = ''; - - // get common args - const customArgs = (this.customArgsEnabled ? this.customArgs : null); - const customOutput = (this.customOutputEnabled ? this.customOutput : null); - const youtubeUsername = (this.youtubeAuthEnabled && this.youtubeUsername ? this.youtubeUsername : null); - const youtubePassword = (this.youtubeAuthEnabled && this.youtubePassword ? this.youtubePassword : null); - - // set advanced inputs - if (this.allowAdvancedDownload) { - if (customArgs) { - localStorage.setItem('customArgs', customArgs); - } - if (customOutput) { - localStorage.setItem('customOutput', customOutput); - } - if (youtubeUsername) { - localStorage.setItem('youtubeUsername', youtubeUsername); - } - } - - if (this.audioOnly) { - // create download object - const new_download: Download = { - uid: uuid(), - type: 'audio', - percent_complete: 0, - url: this.url, - downloading: true, - is_playlist: this.url.includes('playlist'), - error: false - }; - this.downloads.push(new_download); - if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download }; - this.downloadingfile = true; - - let customQualityConfiguration = null; - if (this.selectedQuality !== '') { - customQualityConfiguration = this.getSelectedAudioFormat(); - } - - this.postsService.makeMP3(this.url, (this.selectedQuality === '' ? null : this.selectedQuality), - customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid).subscribe(posts => { - // update download object - new_download.downloading = false; - new_download.percent_complete = 100; - - const is_playlist = !!(posts['file_names']); - this.path = is_playlist ? posts['file_names'] : posts['audiopathEncoded']; - - this.current_download = null; - - if (this.path !== '-1') { - this.downloadHelperMp3(this.path, posts['uid'], is_playlist, false, new_download); - } - }, error => { // can't access server or failed to download for other reasons - this.downloadingfile = false; - this.current_download = null; - new_download['downloading'] = false; - // removes download from list of downloads - const downloads_index = this.downloads.indexOf(new_download); - if (downloads_index !== -1) { - this.downloads.splice(downloads_index) - } - this.openSnackBar('Download failed!', 'OK.'); - }); - } else { - // create download object - const new_download: Download = { - uid: uuid(), - type: 'video', - percent_complete: 0, - url: this.url, - downloading: true, - is_playlist: this.url.includes('playlist'), - error: false - }; - this.downloads.push(new_download); - if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download }; - this.downloadingfile = true; - - const customQualityConfiguration = this.getSelectedVideoFormat(); - - let cropFileSettings = null; - - if (this.cropFile) { - cropFileSettings = { - cropFileStart: this.cropFileStart, - cropFileEnd: this.cropFileEnd - } - } - - this.postsService.makeMP4(this.url, (this.selectedQuality === '' ? null : this.selectedQuality), - customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid, cropFileSettings).subscribe(posts => { - // update download object - new_download.downloading = false; - new_download.percent_complete = 100; - - const is_playlist = !!(posts['file_names']); - this.path = is_playlist ? posts['file_names'] : posts['videopathEncoded']; - - this.current_download = null; - - if (this.path !== '-1') { - this.downloadHelperMp4(this.path, posts['uid'], is_playlist, false, new_download); - } - }, error => { // can't access server - this.downloadingfile = false; - this.current_download = null; - new_download['downloading'] = false; - // removes download from list of downloads - const downloads_index = this.downloads.indexOf(new_download); - if (downloads_index !== -1) { - this.downloads.splice(downloads_index) - } - this.openSnackBar('Download failed!', 'OK.'); - }); - } - - if (this.multiDownloadMode) { - this.url = ''; - this.downloadingfile = false; - } - } else { + if (!this.ValidURL(this.url)) { this.urlError = true; + return; + } + + this.urlError = false; + + // get common args + const customArgs = (this.customArgsEnabled ? this.customArgs : null); + const customOutput = (this.customOutputEnabled ? this.customOutput : null); + const youtubeUsername = (this.youtubeAuthEnabled && this.youtubeUsername ? this.youtubeUsername : null); + const youtubePassword = (this.youtubeAuthEnabled && this.youtubePassword ? this.youtubePassword : null); + + // set advanced inputs + if (this.allowAdvancedDownload) { + if (customArgs) { + localStorage.setItem('customArgs', customArgs); + } + if (customOutput) { + localStorage.setItem('customOutput', customOutput); + } + if (youtubeUsername) { + localStorage.setItem('youtubeUsername', youtubeUsername); + } + } + + const type = this.audioOnly ? 'audio' : 'video'; + // create download object + const new_download: Download = { + uid: uuid(), + type: type, + percent_complete: 0, + url: this.url, + downloading: true, + is_playlist: this.url.includes('playlist'), + error: false + }; + this.downloads.push(new_download); + if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download }; + this.downloadingfile = true; + + let customQualityConfiguration = type === 'audio' ? this.getSelectedAudioFormat() : this.getSelectedVideoFormat(); + + let cropFileSettings = null; + + if (this.cropFile) { + cropFileSettings = { + cropFileStart: this.cropFileStart, + cropFileEnd: this.cropFileEnd + } + } + + this.postsService.downloadFile(this.url, type, (this.selectedQuality === '' ? null : this.selectedQuality), + customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid, cropFileSettings).subscribe(res => { + // update download object + new_download.downloading = false; + new_download.percent_complete = 100; + + const container = res['container']; + const is_playlist = res['file_uids'].length > 1; + + this.current_download = null; + + this.downloadHelper(container, type, is_playlist, false, new_download); + }, error => { // can't access server + this.downloadingfile = false; + this.current_download = null; + new_download['downloading'] = false; + // removes download from list of downloads + const downloads_index = this.downloads.indexOf(new_download); + if (downloads_index !== -1) { + this.downloads.splice(downloads_index) + } + this.openSnackBar('Download failed!', 'OK.'); + }); + + if (this.multiDownloadMode) { + this.url = ''; + this.downloadingfile = false; } } @@ -626,27 +544,13 @@ export class MainComponent implements OnInit { } } - downloadAudioFile(file) { - this.downloading_content['audio'][file.id] = true; + downloadFileFromServer(file, type) { + const ext = type === 'audio' ? 'mp3' : 'mp4' + this.downloading_content[type][file.id] = true; this.postsService.downloadFileFromServer(file.uid).subscribe(res => { - this.downloading_content['audio'][file.id] = false; + this.downloading_content[type][file.id] = false; const blob: Blob = res; - saveAs(blob, decodeURIComponent(file.id) + '.mp3'); - - if (!this.fileManagerEnabled) { - // tell server to delete the file once downloaded - this.postsService.deleteFile(file.uid).subscribe(delRes => { - }); - } - }); - } - - downloadVideoFile(file) { - this.downloading_content['video'][file.id] = true; - this.postsService.downloadFileFromServer(file.uid).subscribe(res => { - this.downloading_content['video'][file.id] = false; - const blob: Blob = res; - saveAs(blob, decodeURIComponent(file.id) + '.mp4'); + saveAs(blob, decodeURIComponent(file.id) + `.${ext}`); if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 9de791a..fc8dbac 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -1,10 +1,10 @@
-
+
-
- -