From 8595864118634e463b4eedfa847b66260f858086 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 17 Sep 2020 03:14:24 -0400 Subject: [PATCH 001/250] Added basic categorization functionality in the server & UI --- backend/app.js | 68 ++++++++++- backend/categories.js | 112 ++++++++++++++++++ src/app/app.component.ts | 2 + src/app/app.module.ts | 4 +- .../edit-category-dialog.component.html | 47 ++++++++ .../edit-category-dialog.component.scss | 16 +++ .../edit-category-dialog.component.spec.ts | 25 ++++ .../edit-category-dialog.component.ts | 110 +++++++++++++++++ src/app/posts.services.ts | 26 ++++ src/app/settings/settings.component.html | 29 ++++- src/app/settings/settings.component.scss | 51 ++++++++ src/app/settings/settings.component.ts | 44 +++++++ 12 files changed, 526 insertions(+), 8 deletions(-) create mode 100644 backend/categories.js create mode 100644 src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html create mode 100644 src/app/dialogs/edit-category-dialog/edit-category-dialog.component.scss create mode 100644 src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts create mode 100644 src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts diff --git a/backend/app.js b/backend/app.js index 38ab93d..2d8bd7a 100644 --- a/backend/app.js +++ b/backend/app.js @@ -27,6 +27,7 @@ const shortid = require('shortid') const url_api = require('url'); var config_api = require('./config.js'); var subscriptions_api = require('./subscriptions') +var categories_api = require('./categories'); const CONSTS = require('./consts') const { spawn } = require('child_process') const read_last_lines = require('read-last-lines'); @@ -37,7 +38,7 @@ const is_windows = process.platform === 'win32'; var app = express(); // database setup -const FileSync = require('lowdb/adapters/FileSync') +const FileSync = require('lowdb/adapters/FileSync'); const adapter = new FileSync('./appdata/db.json'); const db = low(adapter) @@ -80,6 +81,15 @@ config_api.initialize(logger); auth_api.initialize(users_db, logger); db_api.initialize(db, users_db, logger); subscriptions_api.initialize(db, users_db, logger, db_api); +categories_api.initialize(db, users_db, logger, db_api); + + +async function test() { + const test_cat = await categories_api.categorize(fs.readJSONSync('video/Claire Lost Her First Tooth!.info.json')); + console.log(test_cat); +} + +test(); // var GithubContent = require('github-content'); @@ -1115,6 +1125,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { var is_audio = type === 'audio'; var ext = is_audio ? '.mp3' : '.mp4'; var fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath; + let category = null; // prepend with user if needed let multiUserMode = null; @@ -1131,7 +1142,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } options.downloading_method = 'exec'; - const downloadConfig = await generateArgs(url, type, options); + let downloadConfig = await generateArgs(url, type, options); // adds download to download helper const download_uid = uuid(); @@ -1153,11 +1164,22 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { updateDownloads(); // get video info prior to download - const info = await getVideoInfoByURL(url, downloadConfig, download); + let info = await getVideoInfoByURL(url, downloadConfig, download); if (!info) { resolve(false); return; } else { + // check if it fits into a category. If so, then get info again using new downloadConfig + category = await categories_api.categorize(info); + + // set custom output if the category has one and re-retrieve info so the download manager has the right file name + if (category && category['custom_output']) { + options.customOutput = category['custom_output']; + options.noRelativePath = true; + downloadConfig = await generateArgs(url, type, options); + info = await getVideoInfoByURL(url, downloadConfig, download); + } + // store info in download for future use download['_filename'] = info['_filename']; download['filesize'] = utils.getExpectedFileSize(info); @@ -1445,7 +1467,8 @@ async function generateArgs(url, type, options) { } if (customOutput) { - downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", '--write-info-json', '--print-json']; + customOutput = options.noRelativePath ? customOutput : path.join(fileFolderPath, customOutput); + downloadConfig = ['-o', `${customOutput}.%(ext)s`, '--write-info-json', '--print-json']; } else { downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json']; } @@ -2131,6 +2154,43 @@ app.post('/api/disableSharing', optionalJwt, function(req, res) { }); }); +// categories + +app.post('/api/getAllCategories', optionalJwt, async (req, res) => { + const categories = db.get('categories').value(); + res.send({categories: categories}); +}); + +app.post('/api/createCategory', optionalJwt, async (req, res) => { + const name = req.body.name; + const new_category = { + name: name, + uid: uuid(), + rules: [] + }; + + db.get('categories').push(new_category).write(); + + res.send({ + new_category: new_category, + success: !!new_category + }); +}); + +app.post('/api/updateCategory', optionalJwt, async (req, res) => { + const category = req.body.category; + db.get('categories').find({uid: category.uid}).assign(category).write(); + res.send({success: true}); +}); + +app.post('/api/updateCategories', optionalJwt, async (req, res) => { + const categories = req.body.categories; + db.get('categories').assign(categories).write(); + res.send({success: true}); +}); + +// subscriptions + app.post('/api/subscribe', optionalJwt, async (req, res) => { let name = req.body.name; let url = req.body.url; diff --git a/backend/categories.js b/backend/categories.js new file mode 100644 index 0000000..ac3a3c7 --- /dev/null +++ b/backend/categories.js @@ -0,0 +1,112 @@ +const config_api = require('./config'); + +var logger = null; +var db = null; +var users_db = null; +var db_api = null; + +function setDB(input_db, input_users_db, input_db_api) { db = input_db; users_db = input_users_db; db_api = input_db_api } +function setLogger(input_logger) { logger = input_logger; } + +function initialize(input_db, input_users_db, input_logger, input_db_api) { + setDB(input_db, input_users_db, input_db_api); + setLogger(input_logger); +} + +/* + +Categories: + + Categories are a way to organize videos based on dynamic rules set by the user. Categories are universal (so not per-user). + + Categories, besides rules, have an optional custom output. This custom output can help users create their + desired directory structure. + +Rules: + A category rule consists of a property, a comparison, and a value. For example, "uploader includes 'VEVO'" + + Rules are stored as an object with the above fields. In addition to those fields, it also has a preceding_operator, which + is either OR or AND, and signifies whether the rule should be ANDed with the previous rules, or just ORed. For the first + rule, this field is null. + + Ex. (title includes 'Rihanna' OR title includes 'Beyonce' AND uploader includes 'VEVO') + +*/ + +async function categorize(file_json) { + return new Promise(resolve => { + let selected_category = null; + const categories = getCategories(); + if (!categories) { + logger.warn('Categories could not be found. Initializing categories...'); + db.assign({categories: []}).write(); + resolve(null); + return; + } + + for (let i = 0; i < categories.length; i++) { + const category = categories[i]; + const rules = category['rules']; + + // if rules for current category apply, then that is the selected category + if (applyCategoryRules(file_json, rules, category['name'])) { + selected_category = category; + logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`); + } + } + + resolve(selected_category); + + }); +} + +function getCategories() { + const categories = db.get('categories').value(); + return categories ? categories : null; +} + +function applyCategoryRules(file_json, rules, category_name) { + let rules_apply = false; + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + let rule_applies = null; + + let preceding_operator = rule['preceding_operator']; + + switch (rule['comparator']) { + case 'includes': + rule_applies = file_json[rule['property']].includes(rule['value']); + break; + case 'not_includes': + rule_applies = !(file_json[rule['property']].includes(rule['value'])); + break; + case 'equals': + rule_applies = file_json[rule['property']] === rule['value']; + break; + case 'not_equals': + rule_applies = file_json[rule['property']] !== rule['value']; + break; + default: + logger.warn(`Invalid comparison used for category ${category_name}`) + break; + } + + // OR the first rule with rules_apply, which will be initially false + if (i === 0) preceding_operator = 'or'; + + // update rules_apply based on current rule + if (preceding_operator === 'or') + rules_apply = rules_apply || rule_applies; + else + rules_apply = rules_apply && rule_applies; + } + + return rules_apply; +} + + + +module.exports = { + initialize: initialize, + categorize: categorize, +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d8540bb..a6c7630 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -116,6 +116,8 @@ export class AppComponent implements OnInit, AfterViewInit { if (this.allowSubscriptions) { this.postsService.reloadSubscriptions(); } + + this.postsService.reloadCategories(); } // theme stuff diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 69cba76..423f714 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -79,6 +79,7 @@ import { UnifiedFileCardComponent } from './components/unified-file-card/unified import { RecentVideosComponent } from './components/recent-videos/recent-videos.component'; import { EditSubscriptionDialogComponent } from './dialogs/edit-subscription-dialog/edit-subscription-dialog.component'; import { CustomPlaylistsComponent } from './components/custom-playlists/custom-playlists.component'; +import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit-category-dialog.component'; registerLocaleData(es, 'es'); @@ -123,7 +124,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible UnifiedFileCardComponent, RecentVideosComponent, EditSubscriptionDialogComponent, - CustomPlaylistsComponent + CustomPlaylistsComponent, + EditCategoryDialogComponent ], imports: [ CommonModule, diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html new file mode 100644 index 0000000..fe6cec7 --- /dev/null +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html @@ -0,0 +1,47 @@ +

Editing category {{category['name']}}

+ + + + + + +
Rules
+ + + + + + OR + AND + + + + + {{propertyOption.label}} + + + + + {{comparatorOption.label}} + + + + + + + + + + + + +
+ + + + + +
+ +
+
\ No newline at end of file diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.scss b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.scss new file mode 100644 index 0000000..53fcc70 --- /dev/null +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.scss @@ -0,0 +1,16 @@ +.operator-select { + width: 55px; +} + +.property-select { + margin-left: 10px; + width: 110px; +} + +.comparator-select { + margin-left: 10px; +} + +.value-input { + margin-left: 10px; +} \ No newline at end of file diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts new file mode 100644 index 0000000..71d64a9 --- /dev/null +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditCategoryDialogComponent } from './edit-category-dialog.component'; + +describe('EditCategoryDialogComponent', () => { + let component: EditCategoryDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditCategoryDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditCategoryDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts new file mode 100644 index 0000000..928ff1c --- /dev/null +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts @@ -0,0 +1,110 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { PostsService } from 'app/posts.services'; + +@Component({ + selector: 'app-edit-category-dialog', + templateUrl: './edit-category-dialog.component.html', + styleUrls: ['./edit-category-dialog.component.scss'] +}) +export class EditCategoryDialogComponent implements OnInit { + + updating = false; + original_category = null; + category = null; + + propertyOptions = [ + { + value: 'fulltitle', + label: 'Title' + }, + { + value: 'id', + label: 'ID' + }, + { + value: 'webpage_url', + label: 'URL' + }, + { + value: 'view_count', + label: 'Views' + }, + { + value: 'uploader', + label: 'Uploader' + }, + { + value: '_filename', + label: 'File Name' + }, + { + value: 'tags', + label: 'Tags' + } + ]; + + comparatorOptions = [ + { + value: 'includes', + label: 'includes' + }, + { + value: 'not_includes', + label: 'not includes' + }, + { + value: 'equals', + label: 'equals' + }, + { + value: 'not_equals', + label: 'not equals' + }, + + ]; + + constructor(@Inject(MAT_DIALOG_DATA) public data: any, private postsService: PostsService) { + if (this.data) { + this.original_category = this.data.category; + this.category = JSON.parse(JSON.stringify(this.original_category)); + } + } + + ngOnInit(): void { + } + + addNewRule() { + this.category['rules'].push({ + preceding_operator: 'or', + property: 'fulltitle', + comparator: 'includes', + value: '' + }); + } + + saveClicked() { + this.updating = true; + this.postsService.updateCategory(this.category).subscribe(res => { + this.updating = false; + this.original_category = JSON.parse(JSON.stringify(this.category)); + }, err => { + this.updating = false; + console.error(err); + }); + } + + categoryChanged() { + return JSON.stringify(this.category) === JSON.stringify(this.original_category); + } + + swapRules(original_index, new_index) { + [this.category.rules[original_index], this.category.rules[new_index]] = [this.category.rules[new_index], + this.category.rules[original_index]]; + } + + removeRule(index) { + this.category['rules'].splice(index, 1); + } + +} diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 67e3bd1..3e45e81 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -52,6 +52,7 @@ export class PostsService implements CanActivate { // global vars config = null; subscriptions = null; + categories = null; sidenav = null; constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document, @@ -296,6 +297,31 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions); } + // categories + + getAllCategories() { + return this.http.post(this.path + 'getAllCategories', {}, this.httpOptions); + } + + createCategory(name) { + console.log(name); + return this.http.post(this.path + 'createCategory', {name: name}, this.httpOptions); + } + + updateCategory(category) { + return this.http.post(this.path + 'updateCategory', {category: category}, this.httpOptions); + } + + updateCategories(categories) { + return this.http.post(this.path + 'updateCategories', {categories: categories}, this.httpOptions); + } + + reloadCategories() { + this.getAllCategories().subscribe(res => { + this.categories = res['categories']; + }); + } + 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); diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 719683b..8e6fc0d 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -115,15 +115,38 @@ -
+
Global custom args for downloads on the home page. Args are delimited using two commas like so: ,,
- -
+
+
+ +
+
+
+
Categories
+
+
+
+ {{category['name']}} + + + + +
+
+ +
+
+
+ +
+
+
Use youtube-dl archive
diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss index 9ff0b70..f85952f 100644 --- a/src/app/settings/settings.component.scss +++ b/src/app/settings/settings.component.scss @@ -30,4 +30,55 @@ margin-left: 15px; margin-bottom: 12px; bottom: 4px; +} + +.category-list { + width: 500px; + max-width: 100%; + border: solid 1px #ccc; + min-height: 60px; + display: block; + // background: white; + border-radius: 4px; + overflow: hidden; +} + +.category-box { + padding: 20px 10px; + border-bottom: solid 1px #ccc; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + cursor: move; + // background: white; + font-size: 14px; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.category-box:last-child { + border: none; +} + +.category-list.cdk-drop-list-dragging .category-box:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.category-custom-placeholder { +background: #ccc; +border: dotted 3px #999; +min-height: 60px; +transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } \ No newline at end of file diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 4a7684f..ec0c6d4 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -9,6 +9,9 @@ import { CURRENT_VERSION } from 'app/consts'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { CookiesUploaderDialogComponent } from 'app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component'; import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component'; +import { moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop'; +import { InputDialogComponent } from 'app/input-dialog/input-dialog.component'; +import { EditCategoryDialogComponent } from 'app/dialogs/edit-category-dialog/edit-category-dialog.component'; @Component({ selector: 'app-settings', @@ -77,6 +80,47 @@ export class SettingsComponent implements OnInit { }) } + dropCategory(event: CdkDragDrop) { + moveItemInArray(this.postsService.categories, event.previousIndex, event.currentIndex); + this.postsService.updateCategories(this.postsService.categories); + } + + openAddCategoryDialog() { + const done = new EventEmitter(); + const dialogRef = this.dialog.open(InputDialogComponent, { + width: '300px', + data: { + inputTitle: 'Name the category', + inputPlaceholder: 'Name', + submitText: 'Add', + doneEmitter: done + } + }); + + done.subscribe(name => { + + // Eventually do additional checks on name + if (name) { + this.postsService.createCategory(name).subscribe(res => { + if (res['success']) { + this.postsService.reloadCategories(); + dialogRef.close(); + const new_category = res['new_category']; + this.openEditCategoryDialog(new_category); + } + }); + } + }); + } + + openEditCategoryDialog(category) { + this.dialog.open(EditCategoryDialogComponent, { + data: { + category: category + } + }); + } + generateAPIKey() { this.postsService.generateNewAPIKey().subscribe(res => { if (res['new_api_key']) { From fed0a54145da432490144ba5a052612fea10737e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 12 Oct 2020 22:46:23 -0400 Subject: [PATCH 002/250] Updated styling on edit category dialog --- .../edit-category-dialog.component.html | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html index fe6cec7..64aa2be 100644 --- a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html @@ -1,11 +1,13 @@ -

Editing category {{category['name']}}

+

Editing category {{category['name']}}

- - - + + + -
Rules
+ + +
Rules
@@ -34,7 +36,18 @@ - + + + + + + + + + Documentation. + Path is relative to the config download path. Don't include extension. + +
From dff4b141b0506b2664a3a838e5af24627002bdde Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 12 Oct 2020 22:47:11 -0400 Subject: [PATCH 003/250] Blobs are now only included in getAllFiles() if the config option for including thumbnail is set to true --- backend/app.js | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/backend/app.js b/backend/app.js index 2d8bd7a..f316c11 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1936,11 +1936,13 @@ app.get('/api/getMp3s', optionalJwt, function(req, res) { mp3s = JSON.parse(JSON.stringify(mp3s)); - // add thumbnails if present - mp3s.forEach(mp3 => { - if (mp3['thumbnailPath'] && fs.existsSync(mp3['thumbnailPath'])) - mp3['thumbnailBlob'] = fs.readFileSync(mp3['thumbnailPath']); - }); + if (config_api.getConfigItem('ytdl_include_thumbnail')) { + // add thumbnails if present + mp3s.forEach(mp3 => { + if (mp3['thumbnailPath'] && fs.existsSync(mp3['thumbnailPath'])) + mp3['thumbnailBlob'] = fs.readFileSync(mp3['thumbnailPath']); + }); + } res.send({ mp3s: mp3s, @@ -1963,11 +1965,13 @@ app.get('/api/getMp4s', optionalJwt, function(req, res) { mp4s = JSON.parse(JSON.stringify(mp4s)); - // add thumbnails if present - mp4s.forEach(mp4 => { - if (mp4['thumbnailPath'] && fs.existsSync(mp4['thumbnailPath'])) - mp4['thumbnailBlob'] = fs.readFileSync(mp4['thumbnailPath']); - }); + if (config_api.getConfigItem('ytdl_include_thumbnail')) { + // add thumbnails if present + mp4s.forEach(mp4 => { + if (mp4['thumbnailPath'] && fs.existsSync(mp4['thumbnailPath'])) + mp4['thumbnailBlob'] = fs.readFileSync(mp4['thumbnailPath']); + }); + } res.send({ mp4s: mp4s, @@ -2055,12 +2059,14 @@ app.post('/api/getAllFiles', optionalJwt, function (req, res) { files = JSON.parse(JSON.stringify(files)); - // add thumbnails if present - files.forEach(file => { - if (file['thumbnailPath'] && fs.existsSync(file['thumbnailPath'])) - file['thumbnailBlob'] = fs.readFileSync(file['thumbnailPath']); - }); - + if (config_api.getConfigItem('ytdl_include_thumbnail')) { + // add thumbnails if present + files.forEach(file => { + if (file['thumbnailPath'] && fs.existsSync(file['thumbnailPath'])) + file['thumbnailBlob'] = fs.readFileSync(file['thumbnailPath']); + }); + } + res.send({ files: files, playlists: playlists @@ -2166,7 +2172,8 @@ app.post('/api/createCategory', optionalJwt, async (req, res) => { const new_category = { name: name, uid: uuid(), - rules: [] + rules: [], + custom_putput: '' }; db.get('categories').push(new_category).write(); From fe7303a1912563a072356cd72bbb994416a77924 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 15 Oct 2020 16:57:45 -0400 Subject: [PATCH 004/250] Replaced /audio and /video APIs with /stream that now requires a type parameter to simplify future code changes getSubscription can now accept a subscription name instead of just an ID Added API call to delete a category Categories can now have a custom path Minor code cleanup --- backend/app.js | 127 +++++++++++++-------------------------- backend/categories.js | 43 ++++++------- backend/db.js | 6 +- backend/subscriptions.js | 8 +++ 4 files changed, 74 insertions(+), 110 deletions(-) diff --git a/backend/app.js b/backend/app.js index f316c11..acbb45e 100644 --- a/backend/app.js +++ b/backend/app.js @@ -83,16 +83,6 @@ db_api.initialize(db, users_db, logger); subscriptions_api.initialize(db, users_db, logger, db_api); categories_api.initialize(db, users_db, logger, db_api); - -async function test() { - const test_cat = await categories_api.categorize(fs.readJSONSync('video/Claire Lost Her First Tooth!.info.json')); - console.log(test_cat); -} - -test(); - -// var GithubContent = require('github-content'); - // Set some defaults db.defaults( { @@ -184,7 +174,6 @@ const subscription_timeouts = {}; // don't overwrite config if it already happened.. NOT // let alreadyWritten = db.get('configWriteFlag').value(); let writeConfigMode = process.env.write_ytdl_config; -var config = null; // checks if config exists, if not, a config is auto generated config_api.configExistsCheck(); @@ -1221,7 +1210,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } catch(e) { output_json = null; } - var modified_file_name = output_json ? output_json['title'] : null; + if (!output_json) { continue; } @@ -1250,8 +1239,11 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']); } + const file_path = options.noRelativePath ? path.basename(full_file_path) : full_file_path.substring(fileFolderPath.length, full_file_path.length); + const customPath = options.noRelativePath ? path.dirname(full_file_path).split(path.sep).pop() : null; + // registers file in DB - file_uid = db_api.registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type, multiUserMode); + file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath); if (file_name) file_names.push(file_name); } @@ -1795,7 +1787,7 @@ app.use(function(req, res, next) { next(); } else if (req.query.apiKey && config_api.getConfigItem('ytdl_use_api_key') && req.query.apiKey === config_api.getConfigItem('ytdl_api_key')) { next(); - } else if (req.path.includes('/api/video/') || req.path.includes('/api/audio/')) { + } else if (req.path.includes('/api/stream/')) { next(); } else { logger.verbose(`Rejecting request - invalid API use for endpoint: ${req.path}. API key received: ${req.query.apiKey}`); @@ -1808,15 +1800,14 @@ app.use(compression()); 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/audio') || - req.path.includes('/api/video') || + req.path.includes('/api/stream') || 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 file = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true, req.body) : auth_api.getUserPlaylist(uuid, req.query.id, null, true); + const file = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true, !!req.body) : auth_api.getUserPlaylist(uuid, req.query.id, null, true); const is_shared = file ? file['sharingEnabled'] : false; if (is_shared) { req.can_watch = true; @@ -2184,6 +2175,16 @@ app.post('/api/createCategory', optionalJwt, async (req, res) => { }); }); +app.post('/api/deleteCategory', optionalJwt, async (req, res) => { + const category_uid = req.body.category_uid; + + db.get('categories').remove({uid: category_uid}).write(); + + res.send({ + success: true + }); +}); + app.post('/api/updateCategory', optionalJwt, async (req, res) => { const category = req.body.category; db.get('categories').find({uid: category.uid}).assign(category).write(); @@ -2282,10 +2283,17 @@ app.post('/api/deleteSubscriptionFile', optionalJwt, async (req, res) => { app.post('/api/getSubscription', optionalJwt, async (req, res) => { let subID = req.body.id; + let subName = req.body.name; // if included, subID is optional + let user_uid = req.isAuthenticated() ? req.user.uid : null; // get sub from db - let subscription = subscriptions_api.getSubscription(subID, user_uid); + let subscription = null; + if (subID) { + subscription = subscriptions_api.getSubscription(subID, user_uid) + } else if (subName) { + subscription = subscriptions_api.getSubscriptionByName(subName, user_uid) + } if (!subscription) { // failed to get subscription from db, send 400 error @@ -2708,25 +2716,33 @@ app.post('/api/generateNewAPIKey', function (req, res) { // Streaming API calls -app.get('/api/video/:id', optionalJwt, function(req , res){ +app.get('/api/stream/:id', optionalJwt, (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 = videoFolderPath + id + '.mp4'; - if (req.isAuthenticated() || req.can_watch) { + let file_path = req.query.file_path ? decodeURIComponent(req.query.file_path) : 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 + '.mp4') + 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, 'video', id + '.mp4'); + file_path = path.join(usersFileFolder, req.query.uuid ? req.query.uuid : req.user.uid, type, id + ext); } - } else if (optionalParams['subName']) { + } 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 + '.mp4'; + file_path = basePath + optionalParams['subName'] + '/' + id + ext; } + + if (!file_path) { + file_path = path.join(videoFolderPath, id + ext); + } + const stat = fs.statSync(file_path) const fileSize = stat.size const range = req.headers.range @@ -2749,77 +2765,20 @@ app.get('/api/video/:id', optionalJwt, function(req , res){ 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, - 'Content-Type': 'video/mp4', + 'Content-Type': mimetype, } res.writeHead(206, head); file.pipe(res); } else { head = { 'Content-Length': fileSize, - 'Content-Type': 'video/mp4', + 'Content-Type': mimetype, } res.writeHead(200, head) fs.createReadStream(file_path).pipe(res) } }); -app.get('/api/audio/:id', optionalJwt, function(req , res){ - var head; - let id = decodeURIComponent(req.params.id); - let file_path = "audio/" + id + '.mp3'; - let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); - let optionalParams = url_api.parse(req.url,true).query; - if (req.isAuthenticated()) { - if (optionalParams['subName']) { - const isPlaylist = optionalParams['subPlaylist']; - file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + '.mp3') - } else { - let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); - file_path = path.join(usersFileFolder, req.user.uid, 'audio', id + '.mp3'); - } - } else if (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 + '.mp3'; - } - file_path = file_path.replace(/\"/g, '\''); - const stat = fs.statSync(file_path) - const fileSize = stat.size - const range = req.headers.range - if (range) { - const parts = range.replace(/bytes=/, "").split("-") - const start = parseInt(parts[0], 10) - const end = parts[1] - ? parseInt(parts[1], 10) - : 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]; - file.on('close', function() { - let index = config_api.descriptors[id].indexOf(file); - config_api.descriptors[id].splice(index, 1); - logger.debug('Successfully closed stream and removed file reference.'); - }); - head = { - 'Content-Range': `bytes ${start}-${end}/${fileSize}`, - 'Accept-Ranges': 'bytes', - 'Content-Length': chunksize, - 'Content-Type': 'audio/mp3', - } - res.writeHead(206, head); - file.pipe(res); - } else { - head = { - 'Content-Length': fileSize, - 'Content-Type': 'audio/mp3', - } - res.writeHead(200, head) - fs.createReadStream(file_path).pipe(res) - } - }); - // Downloads management app.get('/api/downloads', async (req, res) => { diff --git a/backend/categories.js b/backend/categories.js index ac3a3c7..545a6a1 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -34,30 +34,27 @@ Rules: */ async function categorize(file_json) { - return new Promise(resolve => { - let selected_category = null; - const categories = getCategories(); - if (!categories) { - logger.warn('Categories could not be found. Initializing categories...'); - db.assign({categories: []}).write(); - resolve(null); - return; + let selected_category = null; + const categories = getCategories(); + if (!categories) { + logger.warn('Categories could not be found. Initializing categories...'); + db.assign({categories: []}).write(); + return null; + return; + } + + for (let i = 0; i < categories.length; i++) { + const category = categories[i]; + const rules = category['rules']; + + // if rules for current category apply, then that is the selected category + if (applyCategoryRules(file_json, rules, category['name'])) { + selected_category = category; + logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`); + return selected_category; } - - for (let i = 0; i < categories.length; i++) { - const category = categories[i]; - const rules = category['rules']; - - // if rules for current category apply, then that is the selected category - if (applyCategoryRules(file_json, rules, category['name'])) { - selected_category = category; - logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`); - } - } - - resolve(selected_category); - - }); + } + return selected_category; } function getCategories() { diff --git a/backend/db.js b/backend/db.js index c46cfd8..f625807 100644 --- a/backend/db.js +++ b/backend/db.js @@ -15,10 +15,10 @@ function initialize(input_db, input_users_db, input_logger) { setLogger(input_logger); } -function registerFileDB(file_path, type, multiUserMode = null, sub = null) { +function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null) { let db_path = null; const file_id = file_path.substring(0, file_path.length-4); - const file_object = generateFileObject(file_id, type, multiUserMode && multiUserMode.file_path, sub); + const file_object = generateFileObject(file_id, type, customPath || multiUserMode && multiUserMode.file_path, sub); if (!file_object) { logger.error(`Could not find associated JSON file for ${type} file ${file_id}`); return false; @@ -27,7 +27,7 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null) { utils.fixVideoMetadataPerms(file_id, type, multiUserMode && multiUserMode.file_path); // add thumbnail path - file_object['thumbnailPath'] = utils.getDownloadedThumbnail(file_id, type, multiUserMode && multiUserMode.file_path); + file_object['thumbnailPath'] = utils.getDownloadedThumbnail(file_id, type, customPath || multiUserMode && multiUserMode.file_path); if (!sub) { if (multiUserMode) { diff --git a/backend/subscriptions.js b/backend/subscriptions.js index e65043e..f1c20a7 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -433,6 +433,13 @@ function getSubscription(subID, user_uid = null) { return db.get('subscriptions').find({id: subID}).value(); } +function getSubscriptionByName(subName, user_uid = null) { + if (user_uid) + return users_db.get('users').find({uid: user_uid}).get('subscriptions').find({name: subName}).value(); + else + return db.get('subscriptions').find({name: subName}).value(); +} + function updateSubscription(sub, user_uid = null) { if (user_uid) { users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(sub).write(); @@ -500,6 +507,7 @@ function removeIDFromArchive(archive_path, id) { module.exports = { getSubscription : getSubscription, + getSubscriptionByName : getSubscriptionByName, getAllSubscriptions : getAllSubscriptions, updateSubscription : updateSubscription, subscribe : subscribe, From 0a38b0197120ff4016dd83edeeef4bec0a2a5483 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 15 Oct 2020 16:59:14 -0400 Subject: [PATCH 005/250] Updated posts service to allow for category deletion and subscription retrieval based on name --- src/app/posts.services.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 3e45e81..f65af2e 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -304,10 +304,13 @@ export class PostsService implements CanActivate { } createCategory(name) { - console.log(name); return this.http.post(this.path + 'createCategory', {name: name}, this.httpOptions); } + deleteCategory(category_uid) { + return this.http.post(this.path + 'deleteCategory', {category_uid: category_uid}, this.httpOptions); + } + updateCategory(category) { return this.http.post(this.path + 'updateCategory', {category: category}, this.httpOptions); } @@ -340,8 +343,8 @@ export class PostsService implements CanActivate { file_uid: file_uid}, this.httpOptions) } - getSubscription(id) { - return this.http.post(this.path + 'getSubscription', {id: id}, this.httpOptions); + getSubscription(id, name = null) { + return this.http.post(this.path + 'getSubscription', {id: id, name: name}, this.httpOptions); } getAllSubscriptions() { From 6f089491a5be49ff6c995c627f43ec6d07a65dc2 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 15 Oct 2020 16:59:33 -0400 Subject: [PATCH 006/250] Updated player component to support categories --- src/app/player/player.component.ts | 48 ++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 6582cd7..28ce1cc 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -124,6 +124,8 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.getFile(); } else if (this.id) { this.getPlaylistFiles(); + } else if (this.subscriptionName) { + this.getSubscription(); } if (this.url) { @@ -139,7 +141,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.currentItem = this.playlist[0]; this.currentIndex = 0; this.show_player = true; - } else if (this.subscriptionName || this.fileNames) { + } else if (this.fileNames && !this.subscriptionName) { this.show_player = true; this.parseFileNames(); } @@ -171,6 +173,25 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { }); } + getSubscription() { + this.postsService.getSubscription(null, this.subscriptionName).subscribe(res => { + const subscription = res['subscription']; + if (this.fileNames) { + subscription.videos.forEach(video => { + if (video['id'] === this.fileNames[0]) { + this.db_file = video; + this.show_player = true; + this.parseFileNames(); + } + }); + } else { + console.log('no file name specified'); + } + }, err => { + this.openSnackBar(`Failed to find subscription ${this.subscriptionName}`, 'Dismiss'); + }); + } + getPlaylistFiles() { this.postsService.getPlaylist(this.id, null, this.uuid).subscribe(res => { if (res['playlist']) { @@ -202,23 +223,26 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { const fileName = this.fileNames[i]; let baseLocation = null; let fullLocation = null; - if (!this.subscriptionName) { - baseLocation = this.type + '/'; - fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName); - } else { - // default to video but include subscription name param - baseLocation = this.type === 'audio' ? 'audio/' : 'video/'; - fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName + - '&subPlaylist=' + this.subPlaylist; - } // 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.id || !this.db_file) ? '' : `&type=${this.db_file.type}` + 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)}`; + + 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}`; + } + if (this.postsService.isLoggedIn) { - fullLocation += (this.subscriptionName ? '&' : '?') + `jwt=${this.postsService.token}`; + 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}`; From d4e5082039dbe05b4801c5897267d331a96e921c Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 15 Oct 2020 17:00:48 -0400 Subject: [PATCH 007/250] Confirm dialog can now optionally use warn colors (used for deletion or breaking changes) Category re-ordering is fixed Category deletion in settings is now functional --- .../logs-viewer/logs-viewer.component.ts | 3 +- .../confirm-dialog.component.html | 2 +- .../confirm-dialog.component.ts | 4 ++- src/app/settings/settings.component.html | 2 +- src/app/settings/settings.component.ts | 32 +++++++++++++++++-- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/app/components/logs-viewer/logs-viewer.component.ts b/src/app/components/logs-viewer/logs-viewer.component.ts index fbeeb46..5a218cc 100644 --- a/src/app/components/logs-viewer/logs-viewer.component.ts +++ b/src/app/components/logs-viewer/logs-viewer.component.ts @@ -61,7 +61,8 @@ export class LogsViewerComponent implements OnInit { data: { dialogTitle: 'Clear logs', dialogText: 'Would you like to clear your logs? This will delete all your current logs, permanently.', - submitText: 'Clear' + submitText: 'Clear', + warnSubmitColor: true } }); dialogRef.afterClosed().subscribe(confirmed => { diff --git a/src/app/dialogs/confirm-dialog/confirm-dialog.component.html b/src/app/dialogs/confirm-dialog/confirm-dialog.component.html index 5c6ef6b..ddfde17 100644 --- a/src/app/dialogs/confirm-dialog/confirm-dialog.component.html +++ b/src/app/dialogs/confirm-dialog/confirm-dialog.component.html @@ -6,7 +6,7 @@
- +
diff --git a/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts b/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts index 36892b8..31ec18a 100644 --- a/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts +++ b/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts @@ -15,12 +15,14 @@ export class ConfirmDialogComponent implements OnInit { doneEmitter: EventEmitter = null; onlyEmitOnDone = false; - + + warnSubmitColor = false; constructor(@Inject(MAT_DIALOG_DATA) public data: any, public dialogRef: MatDialogRef) { if (this.data.dialogTitle) { this.dialogTitle = this.data.dialogTitle }; if (this.data.dialogText) { this.dialogText = this.data.dialogText }; if (this.data.submitText) { this.submitText = this.data.submitText }; + if (this.data.warnSubmitColor) { this.warnSubmitColor = this.data.warnSubmitColor }; // checks if emitter exists, if so don't autoclose as it should be handled by caller if (this.data.doneEmitter) { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 8e6fc0d..1342457 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -135,7 +135,7 @@ {{category['name']}} - +
diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index ec0c6d4..26c6eaa 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -82,7 +82,11 @@ export class SettingsComponent implements OnInit { dropCategory(event: CdkDragDrop) { moveItemInArray(this.postsService.categories, event.previousIndex, event.currentIndex); - this.postsService.updateCategories(this.postsService.categories); + this.postsService.updateCategories(this.postsService.categories).subscribe(res => { + + }, err => { + this.postsService.openSnackBar('Failed to update categories!'); + }); } openAddCategoryDialog() { @@ -113,6 +117,29 @@ export class SettingsComponent implements OnInit { }); } + deleteCategory(category) { + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + data: { + dialogTitle: 'Delete category', + dialogText: `Would you like to delete ${category['name']}?`, + submitText: 'Delete', + warnSubmitColor: true + } + }); + dialogRef.afterClosed().subscribe(confirmed => { + if (confirmed) { + this.postsService.deleteCategory(category['uid']).subscribe(res => { + if (res['success']) { + this.postsService.openSnackBar(`Successfully deleted ${category['name']}!`); + this.postsService.reloadCategories(); + } + }, err => { + this.postsService.openSnackBar(`Failed to delete ${category['name']}!`); + }); + } + }); + } + openEditCategoryDialog(category) { this.dialog.open(EditCategoryDialogComponent, { data: { @@ -206,7 +233,8 @@ export class SettingsComponent implements OnInit { dialogTitle: 'Kill downloads', dialogText: 'Are you sure you want to kill all downloads? Any subscription and non-subscription downloads will end immediately, though this operation may take a minute or so to complete.', submitText: 'Kill all downloads', - doneEmitter: done + doneEmitter: done, + warnSubmitColor: true } }); done.subscribe(confirmed => { From deac54e8d62c75539b035543b4767567e78999b1 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 15 Oct 2020 17:00:56 -0400 Subject: [PATCH 008/250] Fixed bug in goToPlaylist --- .../components/custom-playlists/custom-playlists.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index 1f2549f..5668e32 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -50,7 +50,8 @@ export class CustomPlaylistsComponent implements OnInit { }); } - goToPlaylist(playlist) { + goToPlaylist(event_info) { + const playlist = event_info.file; const playlistID = playlist.id; const type = playlist.type; From 0189d292a8e7516ab3325f58604e2a0042550ee3 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 18 Oct 2020 02:20:06 -0400 Subject: [PATCH 009/250] Fixed bug that prevented categorized files from being deletes and simplified the two delete file API calls into one --- backend/app.js | 58 +++---------------- .../recent-videos/recent-videos.component.ts | 4 +- src/app/file-card/file-card.component.ts | 2 +- src/app/main/main.component.ts | 4 +- src/app/posts.services.ts | 8 +-- 5 files changed, 15 insertions(+), 61 deletions(-) diff --git a/backend/app.js b/backend/app.js index acbb45e..50af52e 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2523,56 +2523,25 @@ app.post('/api/deletePlaylist', optionalJwt, async (req, res) => { }) }); -// deletes mp3 file -app.post('/api/deleteMp3', optionalJwt, async (req, res) => { - // var name = req.body.name; +// 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; if (req.isAuthenticated()) { - let success = auth_api.deleteUserFile(req.user.uid, uid, 'audio', blacklistMode); + let success = auth_api.deleteUserFile(req.user.uid, uid, type, blacklistMode); res.send(success); return; } - var audio_obj = db.get('files.audio').find({uid: uid}).value(); - var name = audio_obj.id; - var fullpath = audioFolderPath + name + ".mp3"; + var file_obj = db.get(`files.${type}`).find({uid: uid}).value(); + var name = file_obj.id; + var fullpath = file_obj ? file_obj.path : null; var wasDeleted = false; if (fs.existsSync(fullpath)) { - deleteAudioFile(name, null, blacklistMode); - db.get('files.audio').remove({uid: uid}).write(); - wasDeleted = true; - res.send(wasDeleted); - } else if (audio_obj) { - db.get('files.audio').remove({uid: uid}).write(); - wasDeleted = true; - res.send(wasDeleted); - } else { - wasDeleted = false; - res.send(wasDeleted); - } -}); - -// deletes mp4 file -app.post('/api/deleteMp4', optionalJwt, async (req, res) => { - var uid = req.body.uid; - var blacklistMode = req.body.blacklistMode; - - if (req.isAuthenticated()) { - let success = auth_api.deleteUserFile(req.user.uid, uid, 'video', blacklistMode); - res.send(success); - return; - } - - var video_obj = db.get('files.video').find({uid: uid}).value(); - var name = video_obj.id; - var fullpath = videoFolderPath + name + ".mp4"; - var wasDeleted = false; - if (fs.existsSync(fullpath)) - { - wasDeleted = await deleteVideoFile(name, null, blacklistMode); + wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode); db.get('files.video').remove({uid: uid}).write(); // wasDeleted = true; res.send(wasDeleted); @@ -2638,17 +2607,6 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => { }); }); -app.post('/api/deleteFile', async (req, res) => { - let fileName = req.body.fileName; - let type = req.body.type; - if (type === 'audio') { - deleteAudioFile(fileName); - } else if (type === 'video') { - deleteVideoFile(fileName); - } - res.send({}); -}); - app.post('/api/downloadArchive', async (req, res) => { let sub = req.body.sub; let archive_dir = sub.archive; diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 8333f79..2eb7f36 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -210,7 +210,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, false).subscribe(delRes => { + this.postsService.deleteFile(name, type).subscribe(delRes => { // reload mp4s this.getAllFiles(); }); @@ -233,7 +233,7 @@ export class RecentVideosComponent implements OnInit { } deleteNormalFile(file, index, blacklistMode = false) { - this.postsService.deleteFile(file.uid, file.isAudio, blacklistMode).subscribe(result => { + this.postsService.deleteFile(file.uid, file.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => { if (result) { this.postsService.openSnackBar('Delete success!', 'OK.'); this.files.splice(index, 1); diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 280d131..68a8453 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, blacklistMode).subscribe(result => { + this.postsService.deleteFile(this.uid, this.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => { if (result) { this.openSnackBar('Delete success!', 'OK.'); this.removeFile.emit(this.name); diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index ef4f929..b50337a 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -740,7 +740,7 @@ export class MainComponent implements OnInit { if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded - this.postsService.deleteFile(name, true).subscribe(delRes => { + this.postsService.deleteFile(name, 'video').subscribe(delRes => { // reload mp3s this.getMp3s(); }); @@ -757,7 +757,7 @@ export class MainComponent implements OnInit { if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded - this.postsService.deleteFile(name, false).subscribe(delRes => { + this.postsService.deleteFile(name, 'audio').subscribe(delRes => { // reload mp4s this.getMp4s(); }); diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index f65af2e..8ee9117 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -199,12 +199,8 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'setConfig', {new_config_file: config}, this.httpOptions); } - deleteFile(uid: string, isAudio: boolean, blacklistMode = false) { - if (isAudio) { - return this.http.post(this.path + 'deleteMp3', {uid: uid, blacklistMode: blacklistMode}, this.httpOptions); - } else { - return this.http.post(this.path + 'deleteMp4', {uid: uid, blacklistMode: blacklistMode}, this.httpOptions); - } + deleteFile(uid: string, type: string, blacklistMode = false) { + return this.http.post(this.path + 'deleteFile', {uid: uid, type: type, blacklistMode: blacklistMode}, this.httpOptions); } getMp3s() { From edc22cc47b3e5dba47f4bb358c7389963557b8ed Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 23 Oct 2020 01:38:44 -0400 Subject: [PATCH 010/250] File cards now use the locale to format dates --- .../custom-playlists.component.html | 2 +- .../recent-videos.component.html | 4 ++-- .../unified-file-card.component.html | 2 +- .../unified-file-card.component.ts | 16 +++++++++++++ src/app/posts.services.ts | 13 +++++++++++ src/app/settings/locales_list.ts | 23 ++++++++++++++----- src/app/settings/settings.component.ts | 2 +- 7 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/app/components/custom-playlists/custom-playlists.component.html b/src/app/components/custom-playlists/custom-playlists.component.html index 31d626f..90d2586 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.html +++ b/src/app/components/custom-playlists/custom-playlists.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index 5e99f20..258ff81 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -32,12 +32,12 @@
- +
- +
diff --git a/src/app/components/unified-file-card/unified-file-card.component.html b/src/app/components/unified-file-card/unified-file-card.component.html index 780bee4..a86206b 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.html +++ b/src/app/components/unified-file-card/unified-file-card.component.html @@ -1,5 +1,5 @@
-
{{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}}  {{file_obj.registered | date:'shortDate'}}
+
{{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}}  {{file_obj.registered | date:'shortDate' : undefined : locale.ngID}}
(); @Output() goToSubscription = new EventEmitter(); @Output() deleteFile = new EventEmitter(); @Output() editPlaylist = new EventEmitter(); + @ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger; contextMenuPosition = { x: '0px', y: '0px' }; diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 416fd2d..3336a7f 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -11,6 +11,7 @@ import { BehaviorSubject } from 'rxjs'; import { v4 as uuid } from 'uuid'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; +import { isoLangs } from './settings/locales_list'; @Injectable() export class PostsService implements CanActivate { @@ -53,6 +54,7 @@ export class PostsService implements CanActivate { config = null; subscriptions = null; sidenav = null; + locale = isoLangs['en']; constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document, public snackBar: MatSnackBar) { @@ -114,6 +116,17 @@ export class PostsService implements CanActivate { if (localStorage.getItem('card_size')) { this.card_size = localStorage.getItem('card_size'); } + + // localization + const locale = localStorage.getItem('locale'); + if (!locale) { + localStorage.setItem('locale', 'en'); + } + + if (isoLangs[locale]) { + this.locale = isoLangs[locale]; + } + } canActivate(route, state): Promise { return new Promise(resolve => { diff --git a/src/app/settings/locales_list.ts b/src/app/settings/locales_list.ts index d4531aa..5e88b73 100644 --- a/src/app/settings/locales_list.ts +++ b/src/app/settings/locales_list.ts @@ -122,7 +122,8 @@ export const isoLangs = { }, 'zh': { 'name': 'Chinese', - 'nativeName': '中文 (Zhōngwén), 汉语, 漢語' + 'nativeName': '中文 (Zhōngwén), 汉语, 漢語', + 'ngID': 'zh' }, 'cv': { 'name': 'Chuvash', @@ -162,8 +163,14 @@ export const isoLangs = { }, 'en': { 'name': 'English', - 'nativeName': 'English' + 'nativeName': 'English', + 'ngID': 'en-US' }, + 'en-GB': { + 'name': 'British English', + 'nativeName': 'British English', + 'ngID': 'en-GB' + }, 'eo': { 'name': 'Esperanto', 'nativeName': 'Esperanto' @@ -190,7 +197,8 @@ export const isoLangs = { }, 'fr': { 'name': 'French', - 'nativeName': 'français' + 'nativeName': 'français', + 'ngID': 'fr' }, 'ff': { 'name': 'Fula; Fulah; Pulaar; Pular', @@ -206,7 +214,8 @@ export const isoLangs = { }, 'de': { 'name': 'German', - 'nativeName': 'Deutsch' + 'nativeName': 'Deutsch', + 'ngID': 'de' }, 'el': { 'name': 'Greek, Modern', @@ -438,7 +447,8 @@ export const isoLangs = { }, 'nb': { 'name': 'Norwegian Bokmål', - 'nativeName': 'Norsk bokmål' + 'nativeName': 'Norsk bokmål', + 'ngID': 'nb' }, 'nd': { 'name': 'North Ndebele', @@ -594,7 +604,8 @@ export const isoLangs = { }, 'es': { 'name': 'Spanish; Castilian', - 'nativeName': 'español' + 'nativeName': 'español', + 'ngID': 'es' }, 'su': { 'name': 'Sundanese', diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 37b14d0..9771251 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -17,7 +17,7 @@ import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialo }) export class SettingsComponent implements OnInit { all_locales = isoLangs; - supported_locales = ['en', 'es', 'de', 'fr', 'zh', 'nb']; + supported_locales = ['en', 'es', 'de', 'fr', 'zh', 'nb', 'en-GB']; initialLocale = localStorage.getItem('locale'); initial_config = null; From d659a7614fa0c1cb6f01467bf6f80a94194e9322 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 23 Oct 2020 01:49:27 -0400 Subject: [PATCH 011/250] updated default.json --- src/assets/default.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/assets/default.json b/src/assets/default.json index e03372b..893896f 100644 --- a/src/assets/default.json +++ b/src/assets/default.json @@ -7,9 +7,11 @@ "Downloader": { "path-audio": "audio/", "path-video": "video/", - "use_youtubedl_archive": true, + "use_youtubedl_archive": false, "custom_args": "", - "safe_download_override": false + "safe_download_override": false, + "include_thumbnail": false, + "include_metadata": true }, "Extra": { "title_top": "YoutubeDL-Material", @@ -33,12 +35,20 @@ "Subscriptions": { "allow_subscriptions": true, "subscriptions_base_path": "subscriptions/", - "subscriptions_check_interval": "30", + "subscriptions_check_interval": "300", "subscriptions_use_youtubedl_archive": true }, "Users": { "base_path": "users/", - "allow_registration": true + "allow_registration": true, + "auth_method": "internal", + "ldap_config": { + "url": "ldap://localhost:389", + "bindDN": "cn=root", + "bindCredentials": "secret", + "searchBase": "ou=passport-ldapauth", + "searchFilter": "(uid={{username}})" + } }, "Advanced": { "use_default_downloading_agent": true, @@ -47,7 +57,7 @@ "allow_advanced_download": true, "jwt_expiration": 86400, "logger_level": "debug", - "use_cookies": true + "use_cookies": false } } } \ No newline at end of file From 3aa08e18176a839aeb38f39749da7e79cbc44670 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 23 Oct 2020 02:44:24 -0400 Subject: [PATCH 012/250] Added scaffolding for tags --- backend/categories.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/backend/categories.js b/backend/categories.js index 545a6a1..d7df916 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -101,6 +101,26 @@ function applyCategoryRules(file_json, rules, category_name) { return rules_apply; } +async function addTagToVideo(tag, video, user_uid) { + +} + +async function removeTagFromVideo(tag, video, user_uid) { + +} + +async function incrementTagCount(tag) { + const current_value = db.get(`stats.tags.${tag}`).value(); + if (!current_value) { + db.set(`stats.tags.${tag}`, 1).write(); + } else { + db.update(`stats.tags.${tag}`, n => n + 1).write(); + } +} + +async function decrementTagCount(tag) { + db.update(`stats.tags.${tag}`, n => n - 1).write(); +} module.exports = { From 6ea4176d637a65006e8d83e3656d543414eeb918 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 24 Oct 2020 00:15:47 -0400 Subject: [PATCH 013/250] Added missing code that makes category paths relative to the root dir --- backend/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/app.js b/backend/app.js index 19331f0..f5138a6 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1420,7 +1420,8 @@ async function generateArgs(url, type, options) { } if (customOutput) { - downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", '--write-info-json', '--print-json']; + customOutput = options.noRelativePath ? customOutput : path.join(fileFolderPath, customOutput); + downloadConfig = ['-o', `${customOutput}.%(ext)s`, '--write-info-json', '--print-json']; } else { downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json']; } From 1ce85813fbe6f289d9f165991e9a748f5377cbe2 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 24 Oct 2020 00:20:39 -0400 Subject: [PATCH 014/250] Saving a category will now cause the UI to refresh the cache of categories --- .../edit-category-dialog/edit-category-dialog.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts index 928ff1c..294414e 100644 --- a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.ts @@ -88,6 +88,7 @@ export class EditCategoryDialogComponent implements OnInit { this.postsService.updateCategory(this.category).subscribe(res => { this.updating = false; this.original_category = JSON.parse(JSON.stringify(this.category)); + this.postsService.reloadCategories(); }, err => { this.updating = false; console.error(err); From 3318ac364d8587964b33d744ce74492c11a0101e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 24 Oct 2020 00:29:42 -0400 Subject: [PATCH 015/250] Code cleanup and changed proposed handling of existing tags for suggestions --- backend/categories.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/backend/categories.js b/backend/categories.js index d7df916..d0b249a 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -102,27 +102,21 @@ function applyCategoryRules(file_json, rules, category_name) { } async function addTagToVideo(tag, video, user_uid) { - + // TODO: Implement } async function removeTagFromVideo(tag, video, user_uid) { - + // TODO: Implement } -async function incrementTagCount(tag) { - const current_value = db.get(`stats.tags.${tag}`).value(); - if (!current_value) { - db.set(`stats.tags.${tag}`, 1).write(); - } else { - db.update(`stats.tags.${tag}`, n => n + 1).write(); +// adds tag to list of existing tags (used for tag suggestions) +async function addTagToExistingTags(tag) { + const existing_tags = db.get('tags').value(); + if (!existing_tags.includes(tag)) { + db.get('tags').push(tag).write(); } } -async function decrementTagCount(tag) { - db.update(`stats.tags.${tag}`, n => n - 1).write(); -} - - module.exports = { initialize: initialize, categorize: categorize, From b323b548ca6047978e4a9779796bb9e1b51129ca Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 1 Nov 2020 19:16:41 -0500 Subject: [PATCH 016/250] Added ability to use youtube-dl forks Downloader now defaults to youtube-dlc because of the recent DMCA requests --- backend/app.js | 68 ++++++++++++++++++------ backend/config.js | 1 + backend/consts.js | 4 ++ src/app/settings/settings.component.html | 15 ++++-- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/backend/app.js b/backend/app.js index f5138a6..58ef417 100644 --- a/backend/app.js +++ b/backend/app.js @@ -155,8 +155,8 @@ if (just_restarted) { fs.unlinkSync('restart.json'); } -// updates & starts youtubedl -startYoutubeDL(); +// updates & starts youtubedl (commented out b/c of repo takedown) +// startYoutubeDL(); var validDownloadingAgents = [ 'aria2c', @@ -558,6 +558,9 @@ async function loadConfig() { // creates archive path if missing await fs.ensureDir(archivePath); + // now this is done here due to youtube-dl's repo takedown + await startYoutubeDL(); + // get subscriptions if (allowSubscriptions) { // runs initially, then runs every ${subscriptionCheckInterval} seconds @@ -1613,12 +1616,16 @@ function checkDownloadPercent(download) { async function startYoutubeDL() { // auto update youtube-dl - if (!debugMode) await autoUpdateYoutubeDL(); + await autoUpdateYoutubeDL(); } // auto updates the underlying youtube-dl binary, not YoutubeDL-Material async function autoUpdateYoutubeDL() { - return new Promise(resolve => { + 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' // 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); @@ -1645,42 +1652,69 @@ async function autoUpdateYoutubeDL() { } // got version, now let's check the latest version from the youtube-dl API - let youtubedl_api_path = 'https://api.github.com/repos/ytdl-org/youtube-dl/tags'; + let youtubedl_api_path = using_youtube_dlc ? youtube_dlc_tags_url : youtube_dl_tags_url; fetch(youtubedl_api_path, {method: 'Get'}) .then(async res => res.json()) .then(async (json) => { // check if the versions are different if (!json || !json[0]) { + logger.error(`Failed to check ${default_downloader} version for an update.`) resolve(false); return false; } const latest_update_version = json[0]['name']; if (current_version !== latest_update_version) { - let binary_path = 'node_modules/youtube-dl/bin'; // versions different, download new update - logger.info('Found new update for youtube-dl. Updating binary...'); + logger.info(`Found new update for ${default_downloader}. Updating binary...`); try { await checkExistsWithTimeout(stored_binary_path, 10000); } catch(e) { - logger.error(`Failed to update youtube-dl - ${e}`); + logger.error(`Failed to update ${default_downloader} - ${e}`); } - downloader(binary_path, function error(err, done) { - if (err) { - logger.error(err); - resolve(false); - } - logger.info(`Binary successfully updated: ${current_version} -> ${latest_update_version}`); - resolve(true); - }); + if (using_youtube_dlc) await downloadLatestYoutubeDLCBinary(latest_update_version); + else await downloadLatestYoutubeDLBinary(current_version, latest_update_version); + + resolve(true); + } else { + resolve(false); } }) .catch(err => { - logger.error('Failed to check youtube-dl version for an update.') + logger.error(`Failed to check ${default_downloader} version for an update.`) logger.error(err) }); }); } +async function downloadLatestYoutubeDLBinary(current_version, new_version) { + return new Promise(resolve => { + let binary_path = 'node_modules/youtube-dl/bin'; + downloader(binary_path, function error(err, done) { + if (err) { + logger.error(err); + resolve(false); + } + logger.info(`youtube-dl successfully updated: ${current_version} -> ${new_version}`); + resolve(true); + }); + }); +} + +async function downloadLatestYoutubeDLCBinary(new_version) { + const file_ext = is_windows ? '.exe' : ''; + + const download_url = `https://github.com/blackjack4494/yt-dlc/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, `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; + + fs.writeJSONSync(details_path, details_json); +} + async function checkExistsWithTimeout(filePath, timeout) { return new Promise(function (resolve, reject) { diff --git a/backend/config.js b/backend/config.js index 8737785..87a44c5 100644 --- a/backend/config.js +++ b/backend/config.js @@ -226,6 +226,7 @@ DEFAULT_CONFIG = { } }, "Advanced": { + "default_downloader": "youtube-dlc", "use_default_downloading_agent": true, "custom_downloading_agent": "", "multi_user_mode": false, diff --git a/backend/consts.js b/backend/consts.js index 11ee67b..2fb5d3d 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -130,6 +130,10 @@ let CONFIG_ITEMS = { }, // Advanced + 'ytdl_default_downloader': { + 'key': 'ytdl_default_downloader', + 'path': 'YoutubeDLMaterial.Advanced.default_downloader' + }, 'ytdl_use_default_downloading_agent': { 'key': 'ytdl_use_default_downloading_agent', 'path': 'YoutubeDLMaterial.Advanced.use_default_downloading_agent' diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index d5fccb6..53f6802 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -258,11 +258,20 @@
+ + Select a downloader + + youtube-dlc + youtube-dl + + +
+
Use default downloading agent
- Select a downloader + Select a download agent aria2c avconv @@ -274,7 +283,7 @@
-
+
Log Level @@ -286,7 +295,7 @@
-
+
Login expiration From 3a049a99ac207ad47348ca6b17e2b12f2156204e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 1 Nov 2020 19:38:22 -0500 Subject: [PATCH 017/250] Fixed bug where non-youtube downloads would fail --- backend/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/app.js b/backend/app.js index 58ef417..bee8037 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1595,6 +1595,8 @@ function checkDownloadPercent(download) { const filename = path.format(path.parse(download['_filename'].substring(0, download['_filename'].length-4))); const resulting_file_size = download['filesize']; + if (!resulting_file_size) return; + glob(`${filename}*`, (err, files) => { let sum_size = 0; files.forEach(file => { From 9e4b328f917ca5600440879e73598bddc620c567 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 1 Nov 2020 20:21:36 -0500 Subject: [PATCH 018/250] Default youtube downloader switched back to youtube-dl after testing Fixed bug that caused some non-youtube downloads from failing --- backend/app.js | 9 ++++++++- backend/appdata/default.json | 1 + backend/config.js | 2 +- backend/utils.js | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index bee8037..ba0d3d8 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1655,6 +1655,13 @@ 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'}) .then(async res => res.json()) .then(async (json) => { @@ -1696,7 +1703,7 @@ async function downloadLatestYoutubeDLBinary(current_version, new_version) { logger.error(err); resolve(false); } - logger.info(`youtube-dl successfully updated: ${current_version} -> ${new_version}`); + logger.info(`youtube-dl successfully updated!`); resolve(true); }); }); diff --git a/backend/appdata/default.json b/backend/appdata/default.json index 745b038..c25bba6 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -49,6 +49,7 @@ } }, "Advanced": { + "default_downloader": "youtube-dl", "use_default_downloading_agent": true, "custom_downloading_agent": "", "multi_user_mode": false, diff --git a/backend/config.js b/backend/config.js index 87a44c5..0661a9f 100644 --- a/backend/config.js +++ b/backend/config.js @@ -226,7 +226,7 @@ DEFAULT_CONFIG = { } }, "Advanced": { - "default_downloader": "youtube-dlc", + "default_downloader": "youtube-dl", "use_default_downloading_agent": true, "custom_downloading_agent": "", "multi_user_mode": false, diff --git a/backend/utils.js b/backend/utils.js index 411a7da..ed3b584 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -114,6 +114,7 @@ function getExpectedFileSize(info_json) { const formats = info_json['format_id'].split('+'); let expected_filesize = 0; formats.forEach(format_id => { + if (!info_json.formats) return expected_filesize; info_json.formats.forEach(available_format => { if (available_format.format_id === format_id && available_format.filesize) { expected_filesize += available_format.filesize; From 82df92a72da6367c665d97406b1b8aafe4b1d213 Mon Sep 17 00:00:00 2001 From: hwalker928 <63236817+hwalker928@users.noreply.github.com> Date: Sat, 14 Nov 2020 09:00:45 +0000 Subject: [PATCH 019/250] Update README.md --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c22952..bfa2b75 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,19 @@ Dark mode: NOTE: If you would like to use Docker, you can skip down to the [Docker](#Docker) section for a setup guide. -Make sure you have these dependencies installed on your system: nodejs and youtube-dl. If you don't, run this command: - +Debian/Ubuntu: ``` -sudo apt-get install nodejs youtube-dl +sudo apt-get install nodejs youtube-dl ffmpeg +``` + +CentOS 7: +``` +sudo yum install epel-release +sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm +sudo yum install centos-release-scl-rh +sudo yum install rh-nodejs12 +scl enable rh-nodejs12 bash +sudo yum install nodejs youtube-dl ffmpeg ffmpeg-devel ``` Optional dependencies: From 1bb2f54eba4a20fc2c864bb7691d476940c2c7f0 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 14 Nov 2020 16:54:20 -0500 Subject: [PATCH 020/250] Fixed bug where updating a subscription would not work correctly --- .../edit-subscription-dialog.component.ts | 3 ++- src/app/subscription/subscription/subscription.component.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts index 0271e9e..5d50872 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts @@ -71,9 +71,10 @@ export class EditSubscriptionDialogComponent implements OnInit { } saveSubscription() { - this.postsService.updateSubscription(this.sub).subscribe(res => { + this.postsService.updateSubscription(this.new_sub).subscribe(res => { this.sub = this.new_sub; this.new_sub = JSON.parse(JSON.stringify(this.sub)); + this.postsService.reloadSubscriptions(); }) } diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 794a268..df0561d 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -164,7 +164,7 @@ export class SubscriptionComponent implements OnInit { editSubscription() { this.dialog.open(EditSubscriptionDialogComponent, { data: { - sub: this.subscription + sub: this.postsService.getSubscriptionByID(this.subscription.id) } }); } From b059c7ed5e8be57a37dba6570cf34b0a864ecacf Mon Sep 17 00:00:00 2001 From: Tzahi12345 Date: Wed, 18 Nov 2020 01:55:49 -0500 Subject: [PATCH 021/250] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bfa2b75..513290b 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,8 @@ If you are looking to setup YoutubeDL-Material with Docker, this section is for 3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 8998" or something similar. 4. Make sure you can connect to the specified URL + port, and if so, you are done! +NOTE: It is currently recommended that you use the `nightly` tag on Docker. To do so, simply update the docker-compose.yml `image` field so that it points to `tzahi12345/youtubedl-material:nightly`. + ### Custom UID/GID By default, the Docker container runs as non-root with UID=1000 and GID=1000. To set this to your own UID/GID, simply update the `environment` section in your `docker-compose.yml` like so: From b32fdb2445dc464013755b7cc743fe398ae5618e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 20 Nov 2020 17:39:38 -0500 Subject: [PATCH 022/250] Tab title now matches the top title set in the settings --- src/app/posts.services.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 6e43e47..d8bc8b3 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -12,6 +12,7 @@ import { v4 as uuid } from 'uuid'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as Fingerprint2 from 'fingerprintjs2'; import { isoLangs } from './settings/locales_list'; +import { Title } from '@angular/platform-browser'; @Injectable() export class PostsService implements CanActivate { @@ -58,7 +59,7 @@ export class PostsService implements CanActivate { locale = isoLangs['en']; constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document, - public snackBar: MatSnackBar) { + public snackBar: MatSnackBar, private titleService: Title) { console.log('PostsService Initialized...'); this.path = this.document.location.origin + '/api/'; @@ -88,6 +89,7 @@ export class PostsService implements CanActivate { const result = !this.debugMode ? res['config_file'] : res; if (result) { this.config = result['YoutubeDLMaterial']; + this.titleService.setTitle(this.config['Extra']['title_top']); if (this.config['Advanced']['multi_user_mode']) { this.checkAdminCreationStatus(); // login stuff From 6b7d0681d2e1f7c6ae8f68e715e78f54e34ca77e Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sat, 21 Nov 2020 20:15:12 +0100 Subject: [PATCH 023/250] add automated multi-arch docker image build and push to dockerhub --- .github/workflows/docker.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..3e2e406 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,30 @@ +name: docker + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: setup multi-arch docker build + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build & push images + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm,linux/arm64/v8 + push: true + tags: floriang89/youtubedl-material:latest + \ No newline at end of file From d4f81eb0ab0aac610735fb8e21e38cf9a90965c7 Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sat, 21 Nov 2020 20:16:49 +0100 Subject: [PATCH 024/250] add platform emulator --- .github/workflows/docker.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3e2e406..ffd2860 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,6 +12,8 @@ jobs: steps: - name: checkout code uses: actions/checkout@v2 + - name: setup platform emulator + uses: docker/setup-qemu-action@v1 - name: setup multi-arch docker build uses: docker/setup-buildx-action@v1 - name: Login to DockerHub From 89dfac1249e910fdd623c9259f642f40313a215f Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sat, 21 Nov 2020 20:27:28 +0100 Subject: [PATCH 025/250] update job name to better reflect what it's actually doing --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ffd2860..a9a1d45 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,7 +7,7 @@ on: branches: [master] jobs: - build: + build-and-push: runs-on: ubuntu-latest steps: - name: checkout code From 4d2d9a6b10e6cf1dabdd5b82f9fe3efc123ff959 Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sat, 21 Nov 2020 20:58:58 +0100 Subject: [PATCH 026/250] change docker image tag name to align with upstream --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a9a1d45..9a8cc50 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,5 +28,5 @@ jobs: file: ./Dockerfile platforms: linux/amd64,linux/arm,linux/arm64/v8 push: true - tags: floriang89/youtubedl-material:latest + tags: tzahi12345/youtubedl-material:latest \ No newline at end of file From ab355d62a020ec23d2ca4b7b0edd33abe589b566 Mon Sep 17 00:00:00 2001 From: Tzahi12345 Date: Sat, 21 Nov 2020 23:13:58 -0500 Subject: [PATCH 027/250] GitHub autobuild now uses nightly tag Co-authored-by: Sandro --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9a8cc50..a94bdc0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,5 +28,5 @@ jobs: file: ./Dockerfile platforms: linux/amd64,linux/arm,linux/arm64/v8 push: true - tags: tzahi12345/youtubedl-material:latest - \ No newline at end of file + tags: tzahi12345/youtubedl-material:nightly + From 8f0739c0f986f7a9000c7805650f4a5f6d3c8bcc Mon Sep 17 00:00:00 2001 From: Tzahi12345 Date: Sun, 22 Nov 2020 00:40:53 -0500 Subject: [PATCH 028/250] Removes extra line Co-authored-by: Sandro --- .github/workflows/docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a94bdc0..5521f4f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -29,4 +29,3 @@ jobs: platforms: linux/amd64,linux/arm,linux/arm64/v8 push: true tags: tzahi12345/youtubedl-material:nightly - From cb88c7bc7cdabc44cc86c82cb73973b8db76020f Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Mon, 23 Nov 2020 14:39:16 +0100 Subject: [PATCH 029/250] do not push new docker images for pull requests --- .github/workflows/docker.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5521f4f..c74f29b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,8 +3,6 @@ name: docker on: push: branches: [master] - pull_request: - branches: [master] jobs: build-and-push: From 09832ad15bd72996bd8611c257de0427596069b9 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 24 Nov 2020 03:38:49 -0500 Subject: [PATCH 030/250] Multi download mode and download-only mode now reloads recent videos --- src/app/main/main.component.html | 2 +- src/app/main/main.component.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 217ef6d..59956c8 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -182,7 +182,7 @@ - +

Custom playlists

diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 6ad4260..e6cfe48 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 { RecentVideosComponent } from 'app/components/recent-videos/recent-videos.component'; export let audioFilesMouseHovering = false; export let videoFilesMouseHovering = false; @@ -200,6 +201,7 @@ export class MainComponent implements OnInit { formats_loading = false; @ViewChild('urlinput', { read: ElementRef }) urlInput: ElementRef; + @ViewChild('recentVideos') recentVideos: RecentVideosComponent; @ViewChildren('audiofilecard') audioFileCards: QueryList; @ViewChildren('videofilecard') videoFileCards: QueryList; last_valid_url = ''; @@ -487,6 +489,7 @@ export class MainComponent implements OnInit { 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) { @@ -496,6 +499,7 @@ export class MainComponent implements OnInit { } else { this.downloadAudioFile(decodeURI(name)); } + this.reloadRecentVideos(); } else { localStorage.setItem('player_navigator', this.router.url.split(';')[0]); if (is_playlist) { @@ -524,6 +528,7 @@ export class MainComponent implements OnInit { 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) { @@ -533,6 +538,7 @@ export class MainComponent implements OnInit { } else { this.downloadVideoFile(decodeURI(name)); } + this.reloadRecentVideos(); } else { localStorage.setItem('player_navigator', this.router.url.split(';')[0]); if (is_playlist) { @@ -1161,4 +1167,10 @@ export class MainComponent implements OnInit { } }); } + + reloadRecentVideos() { + if (this.recentVideos) { + this.recentVideos.getAllFiles(); + } + } } From 71814cbdc9fc908208a23ef43af8dc7d71d9eab2 Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sun, 22 Nov 2020 14:28:09 +0100 Subject: [PATCH 031/250] build via github actions --- .github/workflows/build.yml | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8288253 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: continuous integration + +on: + push: + branches: [master, feat/*] + tags: + - v* + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: setup node + uses: actions/setup-node@v1 + - name: install dependencies + run: | + npm install + cd backend + npm install + sudo npm install -g @angular/cli + - name: build + run: ng build --prod + - name: prepare artifact upload + shell: pwsh + run: | + New-Item -Name build -ItemType Directory + New-Item -Path build -Name youtubedl-material -ItemType Directory + Copy-Item -Path ./backend/appdata -Recurse -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/audio -Recurse -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/authentication -Recurse -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/public -Recurse -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/subscriptions -Recurse -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/video -Recurse -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/*.js -Destination ./build/youtubedl-material + Copy-Item -Path ./backend/*.json -Destination ./build/youtubedl-material + - name: upload build artifacts + uses: actions/upload-artifact@v1 + with: + name: youtubedl-material + path: build + From d93481640c3096237b29c7b4255c27e02285bb4b Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sun, 22 Nov 2020 15:05:29 +0100 Subject: [PATCH 032/250] automated release creation for tagged commits --- .github/workflows/build.yml | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8288253..9eb71be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,11 +35,58 @@ jobs: Copy-Item -Path ./backend/public -Recurse -Destination ./build/youtubedl-material Copy-Item -Path ./backend/subscriptions -Recurse -Destination ./build/youtubedl-material Copy-Item -Path ./backend/video -Recurse -Destination ./build/youtubedl-material + New-Item -Path ./build/youtubedl-material -Name users Copy-Item -Path ./backend/*.js -Destination ./build/youtubedl-material Copy-Item -Path ./backend/*.json -Destination ./build/youtubedl-material - - name: upload build artifacts + - name: upload build artifact uses: actions/upload-artifact@v1 with: name: youtubedl-material path: build - + release: + runs-on: ubuntu-latest + needs: build + if: contains(github.ref, '/tags/v') + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: YoutubeDL-Material ${{ github.ref }} + body: | + # New features + # Minor additions + # Bug fixes + draft: true + prerelease: false + - name: download build artifact + uses: actions/download-artifact@v1 + with: + name: youtubedl-material + path: ${{runner.temp}}/youtubedl-material + - name: prepare release asset + shell: pwsh + run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ github.ref }}.zip + - name: upload build asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./youtubedl-material-${{ github.ref }}.zip + asset_name: youtubedl-material-${{ github.ref }}.zip + asset_content_type: application/zip + - name: upload docker-compose asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./docker-compose.yml + asset_name: docker-compose.yml + asset_content_type: text/plain From 2f541a49dfa961501e73aac027ba3e1cd588e7ab Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 25 Nov 2020 15:36:00 -0500 Subject: [PATCH 033/250] Thumbnails now load using a faster method with a dedicated API route rather than sending blobs directly. - In cases of lots of files, loading should be significantly faster --- backend/app.js | 15 +++++++++++---- .../recent-videos/recent-videos.component.html | 2 +- .../unified-file-card.component.html | 2 +- .../unified-file-card.component.ts | 11 +++++++---- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/backend/app.js b/backend/app.js index ba0d3d8..1a5e9f1 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1700,6 +1700,7 @@ async function downloadLatestYoutubeDLBinary(current_version, new_version) { 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); } @@ -1775,7 +1776,7 @@ app.use(function(req, res, next) { next(); } else if (req.query.apiKey && config_api.getConfigItem('ytdl_use_api_key') && req.query.apiKey === config_api.getConfigItem('ytdl_api_key')) { next(); - } else if (req.path.includes('/api/stream/')) { + } else if (req.path.includes('/api/stream/') || req.path.includes('/api/thumbnail/')) { next(); } else { logger.verbose(`Rejecting request - invalid API use for endpoint: ${req.path}. API key received: ${req.query.apiKey}`); @@ -1930,7 +1931,7 @@ app.get('/api/getMp3s', optionalJwt, async function(req, res) { if (config_api.getConfigItem('ytdl_include_thumbnail')) { // add thumbnails if present - await addThumbnails(mp3s); + // await addThumbnails(mp3s); } @@ -1957,7 +1958,7 @@ app.get('/api/getMp4s', optionalJwt, async function(req, res) { if (config_api.getConfigItem('ytdl_include_thumbnail')) { // add thumbnails if present - await addThumbnails(mp4s); + // await addThumbnails(mp4s); } res.send({ @@ -2048,7 +2049,7 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { if (config_api.getConfigItem('ytdl_include_thumbnail')) { // add thumbnails if present - await addThumbnails(files); + // await addThumbnails(files); } res.send({ @@ -2731,6 +2732,12 @@ app.get('/api/stream/:id', optionalJwt, (req, res) => { } }); +app.get('/api/thumbnail/:path', optionalJwt, async (req, res) => { + let file_path = decodeURIComponent(req.params.path); + if (fs.existsSync(file_path)) path.isAbsolute(file_path) ? res.sendFile(file_path) : res.sendFile(path.join(__dirname, file_path)); + else res.sendStatus(404); +}); + // Downloads management app.get('/api/downloads', async (req, res) => { diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index 258ff81..bf6d596 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -32,7 +32,7 @@
- +
diff --git a/src/app/components/unified-file-card/unified-file-card.component.html b/src/app/components/unified-file-card/unified-file-card.component.html index a86206b..a080324 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.html +++ b/src/app/components/unified-file-card/unified-file-card.component.html @@ -41,7 +41,7 @@
- Thumbnail + Thumbnail
{{file_length}}
diff --git a/src/app/components/unified-file-card/unified-file-card.component.ts b/src/app/components/unified-file-card/unified-file-card.component.ts index a2a9947..38461bc 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.ts +++ b/src/app/components/unified-file-card/unified-file-card.component.ts @@ -44,11 +44,13 @@ export class UnifiedFileCardComponent implements OnInit { @Input() is_playlist = false; @Input() index: number; @Input() locale = null; + @Input() baseStreamPath = null; + @Input() jwtString = null; @Output() goToFile = new EventEmitter(); @Output() goToSubscription = new EventEmitter(); @Output() deleteFile = new EventEmitter(); @Output() editPlaylist = new EventEmitter(); - + @ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger; contextMenuPosition = { x: '0px', y: '0px' }; @@ -67,11 +69,12 @@ export class UnifiedFileCardComponent implements OnInit { this.file_length = fancyTimeFormat(this.file_obj.duration); } - if (this.file_obj && this.file_obj.thumbnailBlob) { - const mime = getMimeByFilename(this.file_obj.thumbnailPath); + if (this.file_obj && this.file_obj.thumbnailPath) { + this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${encodeURIComponent(this.file_obj.thumbnailPath)}${this.jwtString}`; + /*const mime = getMimeByFilename(this.file_obj.thumbnailPath); const blob = new Blob([new Uint8Array(this.file_obj.thumbnailBlob.data)], {type: mime}); const bloburl = URL.createObjectURL(blob); - this.thumbnailBlobURL = this.sanitizer.bypassSecurityTrustUrl(bloburl); + this.thumbnailBlobURL = this.sanitizer.bypassSecurityTrustUrl(bloburl);*/ } } From d15d262b87cdfac01a8bd3de78ff03f45b9e908b Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 26 Nov 2020 15:48:37 -0500 Subject: [PATCH 034/250] Fixed bug that resulted in the "download videos in the last X days" timerange in edit subscriptions to come up blank --- .../edit-subscription-dialog.component.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts index 5d50872..0be5253 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts @@ -39,16 +39,12 @@ export class EditSubscriptionDialogComponent implements OnInit { if (this.sub.timerange) { const timerange_str = this.sub.timerange.split('-')[1]; - console.log(timerange_str); const number = timerange_str.replace(/\D/g,''); let units = timerange_str.replace(/[0-9]/g, ''); - console.log(units); - - // // remove plural on units - // if (units[units.length-1] === 's') { - // units = units.substring(0, units.length-1); - // } + if (+number === 1) { + units = units.replace('s', ''); + } this.timerange_amount = parseInt(number); this.timerange_unit = units; @@ -86,12 +82,16 @@ export class EditSubscriptionDialogComponent implements OnInit { } timerangeChanged(value, select_changed) { - console.log(this.timerange_amount); - console.log(this.timerange_unit); + if (+this.timerange_amount === 1) { + this.timerange_unit = this.timerange_unit.replace('s', ''); + } else { + if (!this.timerange_unit.includes('s')) { + this.timerange_unit += 's'; + } + } if (this.timerange_amount && this.timerange_unit && !this.download_all) { this.new_sub.timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit; - console.log(this.new_sub.timerange); } else { this.new_sub.timerange = null; } From b730bc5adc1921c0fb79365ae4a80dded4c46a00 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 27 Nov 2020 00:25:31 -0500 Subject: [PATCH 035/250] Added option to set a default file output - custom file output in the advanced expansion panel will override this --- backend/app.js | 2 +- backend/appdata/default.json | 1 + backend/config.js | 1 + backend/consts.js | 4 ++++ src/app/settings/settings.component.html | 12 +++++++++++- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index 1a5e9f1..6bd7774 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1379,7 +1379,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) { } async function generateArgs(url, type, options) { - var videopath = '%(title)s'; + var videopath = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s'; var globalArgs = config_api.getConfigItem('ytdl_custom_args'); let useCookies = config_api.getConfigItem('ytdl_use_cookies'); var is_audio = type === 'audio'; diff --git a/backend/appdata/default.json b/backend/appdata/default.json index c25bba6..46dca1b 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -7,6 +7,7 @@ "Downloader": { "path-audio": "audio/", "path-video": "video/", + "default_file_output": "", "use_youtubedl_archive": false, "custom_args": "", "safe_download_override": false, diff --git a/backend/config.js b/backend/config.js index 0661a9f..c0c9402 100644 --- a/backend/config.js +++ b/backend/config.js @@ -184,6 +184,7 @@ DEFAULT_CONFIG = { "Downloader": { "path-audio": "audio/", "path-video": "video/", + "default_file_output": "", "use_youtubedl_archive": false, "custom_args": "", "safe_download_override": false, diff --git a/backend/consts.js b/backend/consts.js index 2fb5d3d..b0b8b22 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -18,6 +18,10 @@ let CONFIG_ITEMS = { 'key': 'ytdl_video_folder_path', 'path': 'YoutubeDLMaterial.Downloader.path-video' }, + 'ytdl_default_file_output': { + 'key': 'ytdl_default_file_output', + 'path': 'YoutubeDLMaterial.Downloader.default_file_output' + }, 'ytdl_use_youtubedl_archive': { 'key': 'ytdl_use_youtubedl_archive', 'path': 'YoutubeDLMaterial.Downloader.use_youtubedl_archive' diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 53f6802..f551a9d 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -115,9 +115,19 @@
+
+ + + + Documentation. + Path is relative to the above download paths. Don't include extension. + + +
+
- + Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, From 27437a615f322fc5a7478364ffbff03023d8abac Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Sun, 22 Nov 2020 13:41:25 +0100 Subject: [PATCH 036/250] Cleanup README and clarify docker port usage --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 513290b..0eadd3b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # YoutubeDL-Material -[![](https://img.shields.io/docker/pulls/tzahi12345/youtubedl-material.svg)](https://hub.docker.com/r/tzahi12345/youtubedl-material) -[![](https://img.shields.io/docker/image-size/tzahi12345/youtubedl-material?sort=date)](https://hub.docker.com/r/tzahi12345/youtubedl-material) -[![](https://img.shields.io/badge/%E2%86%91_Deploy_to-Heroku-7056bf.svg)](https://heroku.com/deploy?template=https://github.com/Tzahi12345/YoutubeDL-Material) -[![](https://img.shields.io/github/issues/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/issues) -[![](https://img.shields.io/github/license/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md) +[![Docker pulls badge](https://img.shields.io/docker/pulls/tzahi12345/youtubedl-material.svg)](https://hub.docker.com/r/tzahi12345/youtubedl-material) +[![Docker image size badge](https://img.shields.io/docker/image-size/tzahi12345/youtubedl-material?sort=date)](https://hub.docker.com/r/tzahi12345/youtubedl-material) +[![Heroku deploy badge](https://img.shields.io/badge/%E2%86%91_Deploy_to-Heroku-7056bf.svg)](https://heroku.com/deploy?template=https://github.com/Tzahi12345/YoutubeDL-Material) +[![GitHub issues badge](https://img.shields.io/github/issues/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/issues) +[![License badge](https://img.shields.io/github/license/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md) YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 9](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend. @@ -31,12 +31,14 @@ Dark mode: NOTE: If you would like to use Docker, you can skip down to the [Docker](#Docker) section for a setup guide. Debian/Ubuntu: -``` + +```bash sudo apt-get install nodejs youtube-dl ffmpeg ``` CentOS 7: -``` + +```bash sudo yum install epel-release sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm sudo yum install centos-release-scl-rh @@ -46,6 +48,7 @@ sudo yum install nodejs youtube-dl ffmpeg ffmpeg-devel ``` Optional dependencies: + * AtomicParsley (for embedding thumbnails, package name `atomicparsley`) ### Installing @@ -84,8 +87,8 @@ If you are looking to setup YoutubeDL-Material with Docker, this section is for 1. Run `curl -L https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest/download/docker-compose.yml -o docker-compose.yml` to download the latest Docker Compose, or go to the [releases](https://github.com/Tzahi12345/YoutubeDL-Material/releases/) page to grab the version you'd like. 2. Run `docker-compose pull`. This will download the official YoutubeDL-Material docker image. -3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 8998" or something similar. -4. Make sure you can connect to the specified URL + port, and if so, you are done! +3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 17443" or something similar. This tells you the *container-internal* port of the application. Please check your `docker-compose.yml` file for the *external* port. If you downloaded the file as described above, it defaults to **8998**. +4. Make sure you can connect to the specified URL + *external* port, and if so, you are done! NOTE: It is currently recommended that you use the `nightly` tag on Docker. To do so, simply update the docker-compose.yml `image` field so that it points to `tzahi12345/youtubedl-material:nightly`. @@ -93,7 +96,7 @@ NOTE: It is currently recommended that you use the `nightly` tag on Docker. To d By default, the Docker container runs as non-root with UID=1000 and GID=1000. To set this to your own UID/GID, simply update the `environment` section in your `docker-compose.yml` like so: -``` +```yml environment: UID: YOUR_UID GID: YOUR_GID @@ -120,6 +123,7 @@ If you're interested in translating the app into a new language, check out the [ * **Isaac Grynsztein** (me!) - *Initial work* Official translators: + * Spanish - tzahi12345 * German - UnlimitedCookies * Chinese - TyRoyal From 8938844ffad704bca81fa339467526083e1404bc Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 28 Nov 2020 00:45:01 -0500 Subject: [PATCH 037/250] Added ability to select the max quality for a subscription. It defaults to 'best' which will get the best native mp4 video --- backend/app.js | 2 + backend/subscriptions.js | 11 ++++-- .../edit-subscription-dialog.component.html | 7 ++++ .../edit-subscription-dialog.component.ts | 30 +++++++++++++++ .../subscribe-dialog.component.html | 7 ++++ .../subscribe-dialog.component.ts | 37 ++++++++++++++++++- src/app/posts.services.ts | 8 ++-- 7 files changed, 94 insertions(+), 8 deletions(-) diff --git a/backend/app.js b/backend/app.js index 6bd7774..3c1af08 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2196,6 +2196,7 @@ app.post('/api/updateCategories', optionalJwt, async (req, res) => { app.post('/api/subscribe', optionalJwt, async (req, res) => { let name = req.body.name; let url = req.body.url; + let maxQuality = req.body.maxQuality; let timerange = req.body.timerange; let streamingOnly = req.body.streamingOnly; let audioOnly = req.body.audioOnly; @@ -2205,6 +2206,7 @@ app.post('/api/subscribe', optionalJwt, async (req, res) => { const new_sub = { name: name, url: url, + maxQuality: maxQuality, id: uuid(), streamingOnly: streamingOnly, user_uid: user_uid, diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 5eafc4e..4bd071b 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -114,7 +114,11 @@ async function getSubscriptionInfo(sub, user_uid = null) { continue; } if (!sub.name) { - sub.name = sub.isPlaylist ? output_json.playlist_title : output_json.uploader; + if (sub.isPlaylist) { + sub.name = output_json.playlist_title ? output_json.playlist_title : output_json.playlist; + } else { + sub.name = output_json.uploader; + } // if it's now valid, update if (sub.name) { if (user_uid) @@ -296,7 +300,8 @@ async function getVideosForSub(sub, user_uid = null) { qualityPath.push('-x'); qualityPath.push('--audio-format', 'mp3'); } else { - qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4'] + if (!sub.maxQuality || sub.maxQuality === 'best') qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4']; + else qualityPath = ['-f', `bestvideo[height<=${sub.maxQuality}]+bestaudio/best[height<=${sub.maxQuality}]`, '--merge-output-format', 'mp4']; } downloadConfig.push(...qualityPath) @@ -351,7 +356,7 @@ async function getVideosForSub(sub, user_uid = null) { youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) { logger.verbose('Subscription: finished check for ' + sub.name); if (err && !output) { - logger.error(err.stderr); + logger.error(err.stderr ? err.stderr : err.message); if (err.stderr.includes('This video is unavailable')) { logger.info('An error was encountered with at least one video, backup method will be used.') try { diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html index 58a7670..00d5485 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html @@ -24,6 +24,13 @@ Audio-only mode
+
+ + + {{available_quality['label']}} + + +
Streaming-only mode diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts index 0be5253..b1d2dd4 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts @@ -22,6 +22,36 @@ export class EditSubscriptionDialogComponent implements OnInit { audioOnlyMode = null; download_all = null; + available_qualities = [ + { + 'label': 'Best', + 'value': 'best' + }, + { + 'label': '4K', + 'value': '2160' + }, + { + 'label': '1440p', + 'value': '1440' + }, + { + 'label': '1080p', + 'value': '1080' + }, + { + 'label': '720p', + 'value': '720' + }, + { + 'label': '480p', + 'value': '480' + }, + { + 'label': '360p', + 'value': '360' + } + ]; time_units = [ 'day', diff --git a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html index 0e5665c..7b53c01 100644 --- a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html +++ b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html @@ -35,6 +35,13 @@
+
+ + + {{available_quality['label']}} + + +
Audio-only mode diff --git a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.ts b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.ts index 2c33e92..0d3ab68 100644 --- a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.ts +++ b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.ts @@ -17,6 +17,8 @@ export class SubscribeDialogComponent implements OnInit { url = null; name = null; + maxQuality = 'best'; + // state subscribing = false; @@ -29,12 +31,43 @@ export class SubscribeDialogComponent implements OnInit { customFileOutput = ''; customArgs = ''; + available_qualities = [ + { + 'label': 'Best', + 'value': 'best' + }, + { + 'label': '4K', + 'value': '2160' + }, + { + 'label': '1440p', + 'value': '1440' + }, + { + 'label': '1080p', + 'value': '1080' + }, + { + 'label': '720p', + 'value': '720' + }, + { + 'label': '480p', + 'value': '480' + }, + { + 'label': '360p', + 'value': '360' + } + ]; + time_units = [ 'day', 'week', 'month', 'year' - ] + ]; constructor(private postsService: PostsService, private snackBar: MatSnackBar, @@ -57,7 +90,7 @@ export class SubscribeDialogComponent implements OnInit { if (!this.download_all) { timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit; } - this.postsService.createSubscription(this.url, this.name, timerange, this.streamingOnlyMode, + this.postsService.createSubscription(this.url, this.name, timerange, this.streamingOnlyMode, this.maxQuality, this.audioOnlyMode, this.customArgs, this.customFileOutput).subscribe(res => { this.subscribing = false; if (res['new_sub']) { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index d8bc8b3..307c3c5 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -337,9 +337,11 @@ export class PostsService implements CanActivate { }); } - 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, name, timerange = null, streamingOnly = false, maxQuality = 'best', audioOnly = false, + customArgs = null, customFileOutput = null) { + return this.http.post(this.path + 'subscribe', {url: url, name: name, timerange: timerange, maxQuality: maxQuality, + streamingOnly: streamingOnly, audioOnly: audioOnly, customArgs: customArgs, + customFileOutput: customFileOutput}, this.httpOptions); } updateSubscription(subscription) { From d08fee122310d015a5f69b636c1d85974ccb4fe0 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 29 Nov 2020 03:18:28 -0500 Subject: [PATCH 038/250] Added v1 of chat sidebar for Twitch VODs --- backend/app.js | 93 ++++++++++++ backend/appdata/default.json | 5 +- backend/config.js | 5 +- backend/consts.js | 12 ++ backend/twitch.js | 71 +++++++++ src/app/app.module.ts | 4 +- .../twitch-chat/twitch-chat.component.html | 11 ++ .../twitch-chat/twitch-chat.component.scss | 13 ++ .../twitch-chat/twitch-chat.component.spec.ts | 25 ++++ .../twitch-chat/twitch-chat.component.ts | 135 ++++++++++++++++++ src/app/player/player.component.html | 75 +++++----- src/app/player/player.component.ts | 13 +- src/app/posts.services.ts | 8 ++ src/app/settings/settings.component.html | 14 +- 14 files changed, 447 insertions(+), 37 deletions(-) create mode 100644 backend/twitch.js create mode 100644 src/app/components/twitch-chat/twitch-chat.component.html create mode 100644 src/app/components/twitch-chat/twitch-chat.component.scss create mode 100644 src/app/components/twitch-chat/twitch-chat.component.spec.ts create mode 100644 src/app/components/twitch-chat/twitch-chat.component.ts diff --git a/backend/app.js b/backend/app.js index 3c1af08..e50dc8d 100644 --- a/backend/app.js +++ b/backend/app.js @@ -27,6 +27,7 @@ const url_api = require('url'); var config_api = require('./config.js'); var subscriptions_api = require('./subscriptions') var categories_api = require('./categories'); +var twitch_api = require('./twitch'); const CONSTS = require('./consts') const { spawn } = require('child_process') const read_last_lines = require('read-last-lines'); @@ -38,6 +39,7 @@ var app = express(); // database setup const FileSync = require('lowdb/adapters/FileSync'); +const config = require('./config.js'); const adapter = new FileSync('./appdata/db.json'); const db = low(adapter) @@ -1186,6 +1188,13 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { var full_file_path = filepath_no_extension + ext; var file_name = filepath_no_extension.substring(fileFolderPath.length, filepath_no_extension.length); + if (type === 'video' && url.includes('twitch.tv/videos/') && url.split('twitch.tv/videos/').length > 1 + && config.getConfigItem('ytdl_use_twitch_api') && config.getConfigItem('ytdl_twitch_auto_download_chat')) { + let vodId = url.split('twitch.tv/videos/')[1]; + vodId = vodId.split('?')[0]; + downloadTwitchChatByVODID(vodId, file_name, type, options.user); + } + // renames file if necessary due to bug if (!fs.existsSync(output_json['_filename'] && fs.existsSync(output_json['_filename'] + '.webm'))) { try { @@ -1759,6 +1768,42 @@ function removeFileExtension(filename) { return filename_parts.join('.'); } +async function getTwitchChatByFileID(id, type, user_uid, uuid) { + let file_path = null; + + if (user_uid) { + file_path = path.join('users', user_uid, type, id + '.twitch_chat.json'); + } else { + file_path = path.join(type, id + '.twitch_chat.json'); + } + + var chat_file = null; + if (fs.existsSync(file_path)) { + chat_file = fs.readJSONSync(file_path); + } + + return chat_file; +} + +async function downloadTwitchChatByVODID(vodId, id, type, user_uid) { + const twitch_api_key = config_api.getConfigItem('ytdl_twitch_api_key'); + const chat = await twitch_api.getCommentsForVOD(twitch_api_key, vodId); + + // save file if needec params are included + if (id && type) { + let file_path = null; + if (user_uid) { + file_path = path.join('users', user_uid, type, id + '.twitch_chat.json'); + } else { + file_path = path.join(type, id + '.twitch_chat.json'); + } + + if (chat) fs.writeJSONSync(file_path, chat); + } + + return chat; +} + app.use(function(req, res, next) { res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization"); res.header("Access-Control-Allow-Origin", getOrigin()); @@ -2058,6 +2103,54 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { }); }); +app.post('/api/getFullTwitchChat', optionalJwt, async (req, res) => { + var id = req.body.id; + var type = req.body.type; + var uuid = req.body.uuid; + var user_uid = null; + + if (req.isAuthenticated()) user_uid = req.user.uid; + + const chat_file = await getTwitchChatByFileID(id, type, user_uid, uuid); + + res.send({ + chat: chat_file + }); +}); + +app.post('/api/downloadTwitchChatByVODID', optionalJwt, async (req, res) => { + var id = req.body.id; + var type = req.body.type; + var vodId = req.body.vodId; + var uuid = req.body.uuid; + var user_uid = null; + + if (req.isAuthenticated()) user_uid = req.user.uid; + + // check if file already exists. if so, send that instead + const file_exists_check = await getTwitchChatByFileID(id, type, user_uid, uuid); + if (file_exists_check) { + res.send({chat: file_exists_check}); + return; + } + + const full_chat = await downloadTwitchChatByVODID(vodId); + + let file_path = null; + + if (user_uid) { + file_path = path.join('users', req.user.uid, type, id + '.twitch_chat.json'); + } else { + file_path = path.join(type, id + '.twitch_chat.json'); + } + + if (full_chat) fs.writeJSONSync(file_path, full_chat); + + res.send({ + chat: full_chat + }); +}); + // video sharing app.post('/api/enableSharing', optionalJwt, function(req, res) { var type = req.body.type; diff --git a/backend/appdata/default.json b/backend/appdata/default.json index 46dca1b..001f515 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -26,7 +26,10 @@ "use_API_key": false, "API_key": "", "use_youtube_API": false, - "youtube_API_key": "" + "youtube_API_key": "", + "use_twitch_API": false, + "twitch_API_key": "", + "twitch_auto_download_chat": false }, "Themes": { "default_theme": "default", diff --git a/backend/config.js b/backend/config.js index c0c9402..b2aff63 100644 --- a/backend/config.js +++ b/backend/config.js @@ -203,7 +203,10 @@ DEFAULT_CONFIG = { "use_API_key": false, "API_key": "", "use_youtube_API": false, - "youtube_API_key": "" + "youtube_API_key": "", + "use_twitch_API": false, + "twitch_API_key": "", + "twitch_auto_download_chat": false }, "Themes": { "default_theme": "default", diff --git a/backend/consts.js b/backend/consts.js index b0b8b22..bffe486 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -86,6 +86,18 @@ let CONFIG_ITEMS = { 'key': 'ytdl_youtube_api_key', 'path': 'YoutubeDLMaterial.API.youtube_API_key' }, + 'ytdl_use_twitch_api': { + 'key': 'ytdl_use_twitch_api', + 'path': 'YoutubeDLMaterial.API.use_twitch_API' + }, + 'ytdl_twitch_api_key': { + 'key': 'ytdl_twitch_api_key', + 'path': 'YoutubeDLMaterial.API.twitch_API_key' + }, + 'ytdl_twitch_auto_download_chat': { + 'key': 'ytdl_twitch_auto_download_chat', + 'path': 'YoutubeDLMaterial.API.twitch_auto_download_chat' + }, // Themes 'ytdl_default_theme': { diff --git a/backend/twitch.js b/backend/twitch.js new file mode 100644 index 0000000..62c390d --- /dev/null +++ b/backend/twitch.js @@ -0,0 +1,71 @@ +var moment = require('moment'); +var Axios = require('axios'); + +async function getCommentsForVOD(clientID, vodId) { + let url = `https://api.twitch.tv/v5/videos/${vodId}/comments?content_offset_seconds=0`, + batch, + cursor; + + let comments = null; + + try { + do { + batch = (await Axios.get(url, { + headers: { + 'Client-ID': clientID, + Accept: 'application/vnd.twitchtv.v5+json; charset=UTF-8', + 'Content-Type': 'application/json; charset=UTF-8', + } + })).data; + + const str = batch.comments.map(c => { + let { + created_at: msgCreated, + content_offset_seconds: timestamp, + commenter: { + name, + _id, + created_at: acctCreated + }, + message: { + body: msg + } + } = c; + + const timestamp_str = moment.duration(timestamp, 'seconds') + .toISOString() + .replace(/P.*?T(?:(\d+?)H)?(?:(\d+?)M)?(?:(\d+).*?S)?/, + (_, ...ms) => { + const seg = v => v ? v.padStart(2, '0') : '00'; + return `${seg(ms[0])}:${seg(ms[1])}:${seg(ms[2])}`; + }); + + acctCreated = moment(acctCreated).utc(); + msgCreated = moment(msgCreated).utc(); + + if (!comments) comments = []; + + comments.push({ + timestamp: timestamp, + timestamp_str: timestamp_str, + name: name, + message: msg + }); + // let line = `${timestamp},${msgCreated.format(tsFormat)},${name},${_id},"${msg.replace(/"/g, '""')}",${acctCreated.format(tsFormat)}`; + // return line; + }).join('\n'); + + cursor = batch._next; + url = `https://api.twitch.tv/v5/videos/${vodId}/comments?cursor=${cursor}`; + await new Promise(res => setTimeout(res, 300)); + } while (cursor); + } catch (err) { + console.error(err); + } + + return comments; +} + +module.exports = { + getCommentsForVOD: getCommentsForVOD +} \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 423f714..72c2658 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -80,6 +80,7 @@ import { RecentVideosComponent } from './components/recent-videos/recent-videos. import { EditSubscriptionDialogComponent } from './dialogs/edit-subscription-dialog/edit-subscription-dialog.component'; import { CustomPlaylistsComponent } from './components/custom-playlists/custom-playlists.component'; import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit-category-dialog.component'; +import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component'; registerLocaleData(es, 'es'); @@ -125,7 +126,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible RecentVideosComponent, EditSubscriptionDialogComponent, CustomPlaylistsComponent, - EditCategoryDialogComponent + EditCategoryDialogComponent, + TwitchChatComponent ], imports: [ CommonModule, diff --git a/src/app/components/twitch-chat/twitch-chat.component.html b/src/app/components/twitch-chat/twitch-chat.component.html new file mode 100644 index 0000000..7ad40fc --- /dev/null +++ b/src/app/components/twitch-chat/twitch-chat.component.html @@ -0,0 +1,11 @@ +
+
Twitch Chat
+
+ {{chat.timestamp_str}} - {{chat.name}}: {{chat.message}} +
+
+ + + + + \ No newline at end of file diff --git a/src/app/components/twitch-chat/twitch-chat.component.scss b/src/app/components/twitch-chat/twitch-chat.component.scss new file mode 100644 index 0000000..3c8f4ab --- /dev/null +++ b/src/app/components/twitch-chat/twitch-chat.component.scss @@ -0,0 +1,13 @@ +.chat-container { + height: 100%; + overflow-y: scroll; +} + +.download-button { + margin: 10px; +} + +.downloading-spinner { + top: 50%; + left: 80px; +} \ No newline at end of file diff --git a/src/app/components/twitch-chat/twitch-chat.component.spec.ts b/src/app/components/twitch-chat/twitch-chat.component.spec.ts new file mode 100644 index 0000000..eafdece --- /dev/null +++ b/src/app/components/twitch-chat/twitch-chat.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TwitchChatComponent } from './twitch-chat.component'; + +describe('TwitchChatComponent', () => { + let component: TwitchChatComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TwitchChatComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TwitchChatComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/twitch-chat/twitch-chat.component.ts b/src/app/components/twitch-chat/twitch-chat.component.ts new file mode 100644 index 0000000..eace952 --- /dev/null +++ b/src/app/components/twitch-chat/twitch-chat.component.ts @@ -0,0 +1,135 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { PostsService } from 'app/posts.services'; + +@Component({ + selector: 'app-twitch-chat', + templateUrl: './twitch-chat.component.html', + styleUrls: ['./twitch-chat.component.scss'] +}) +export class TwitchChatComponent implements OnInit, AfterViewInit { + + full_chat = null; + visible_chat = null; + chat_response_received = false; + downloading_chat = false; + + current_chat_index = null; + + CHAT_CHECK_INTERVAL_MS = 200; + chat_check_interval_obj = null; + + scrollContainer = null; + + @Input() db_file = null; + @Input() current_timestamp = null; + + @ViewChild('scrollContainer') scrollRef: ElementRef; + + constructor(private postsService: PostsService) { } + + ngOnInit(): void { + this.getFullChat(); + } + + ngAfterViewInit() { + } + + private isUserNearBottom(): boolean { + const threshold = 300; + const position = this.scrollContainer.scrollTop + this.scrollContainer.offsetHeight; + const height = this.scrollContainer.scrollHeight; + return position > height - threshold; + } + + scrollToBottom = () => { + this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight; + } + + addNewChatMessages() { + if (!this.scrollContainer) { + this.scrollContainer = this.scrollRef.nativeElement; + } + if (this.current_chat_index === null) { + this.current_chat_index = this.getIndexOfNextChat(); + } + + const latest_chat_timestamp = this.visible_chat.length ? this.visible_chat[this.visible_chat.length - 1]['timestamp'] : 0; + + for (let i = this.current_chat_index + 1; i < this.full_chat.length; i++) { + if (this.full_chat[i]['timestamp'] >= latest_chat_timestamp && this.full_chat[i]['timestamp'] <= this.current_timestamp) { + this.visible_chat.push(this.full_chat[i]); + this.current_chat_index = i; + if (this.isUserNearBottom()) { + this.scrollToBottom(); + } + } else if (this.full_chat[i]['timestamp'] > this.current_timestamp) { + break; + } + } + } + + getIndexOfNextChat() { + const index = binarySearch(this.full_chat, 'timestamp', this.current_timestamp); + return index; + } + + getFullChat() { + this.postsService.getFullTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', null).subscribe(res => { + this.chat_response_received = true; + if (res['chat']) { + this.initializeChatCheck(res['chat']); + } + }); + } + + renewChat() { + this.visible_chat = []; + this.current_chat_index = this.getIndexOfNextChat(); + } + + downloadTwitchChat() { + this.downloading_chat = true; + let vodId = this.db_file.url.split('videos/').length > 1 && this.db_file.url.split('videos/')[1]; + vodId = vodId.split('?')[0]; + if (!vodId) { + this.postsService.openSnackBar('VOD url for this video is not supported. VOD ID must be after "twitch.tv/videos/"'); + } + this.postsService.downloadTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', vodId, null).subscribe(res => { + if (res['chat']) { + this.initializeChatCheck(res['chat']); + } else { + this.downloading_chat = false; + this.postsService.openSnackBar('Download failed.') + } + }, err => { + this.downloading_chat = false; + this.postsService.openSnackBar('Chat could not be downloaded.') + }); + } + + initializeChatCheck(full_chat) { + this.full_chat = full_chat; + this.visible_chat = []; + this.chat_check_interval_obj = setInterval(() => this.addNewChatMessages(), this.CHAT_CHECK_INTERVAL_MS); + } + +} + +function binarySearch(arr, key, n) { + let min = 0; + let max = arr.length - 1; + let mid; + while (min <= max) { + // tslint:disable-next-line: no-bitwise + mid = (min + max) >>> 1; + if (arr[mid][key] === n) { + return mid; + } else if (arr[mid][key] < n) { + min = mid + 1; + } else { + max = mid - 1; + } + } + + return min; +} diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index ad65493..c70800d 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -1,35 +1,46 @@ -
-
-
-
- - - -
-
- - {{playlist_item.label}} - -
-
-
+
+
+
+ +
+ + + +
+
+ + {{playlist_item.label}} + +
+ + + + + + +
+ +
-
-
- -
- - -
- -
- - - -
-
- - +
+
+ +
+ + +
+ +
+ + + +
+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 477c112..0ce60ab 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, HostListener, EventEmitter, OnDestroy, AfterViewInit } from '@angular/core'; +import { Component, OnInit, HostListener, EventEmitter, OnDestroy, AfterViewInit, ViewChild } from '@angular/core'; import { VgAPI } from 'ngx-videogular'; import { PostsService } from 'app/posts.services'; import { ActivatedRoute, Router } from '@angular/router'; @@ -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 { TwitchChatComponent } from 'app/components/twitch-chat/twitch-chat.component'; export interface IMedia { title: string; @@ -31,6 +32,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { currentIndex = 0; currentItem: IMedia = null; api: VgAPI; + api_ready = false; // params fileNames: string[]; @@ -65,6 +67,8 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { save_volume_timer = null; original_volume = null; + @ViewChild('twitchchat') twitchChat: TwitchChatComponent; + @HostListener('window:resize', ['$event']) onResize(event) { this.innerWidth = window.innerWidth; @@ -270,6 +274,13 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { onPlayerReady(api: VgAPI) { this.api = api; + this.api_ready = true; + + this.api.subscriptions.seeked.subscribe(data => { + if (this.twitchChat) { + this.twitchChat.renewChat(); + } + }); // checks if volume has been previously set. if so, use that as default if (localStorage.getItem('player_volume')) { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 307c3c5..da479fb 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -234,6 +234,14 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions); } + getFullTwitchChat(id, type, uuid = null) { + return this.http.post(this.path + 'getFullTwitchChat', {id: id, type: type, uuid: uuid}, this.httpOptions); + } + + downloadTwitchChat(id, type, vodId, uuid = null) { + return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid}, 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, diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index f551a9d..08d4970 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -228,12 +228,24 @@
Use YouTube API
-
+ +
+ Use Twitch API +
+
+ Auto-download Twitch Chat +
+
+ + + Also known as a Client ID. Generating a key is easy! + +
From b0acb6312322b4f0a976655cd210c41bd7b02c45 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 29 Nov 2020 13:06:57 -0500 Subject: [PATCH 039/250] Updated backend dependencies (caused build to fail) --- backend/package-lock.json | 20 ++++++++++++++++---- backend/package.json | 2 ++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index c969112..27c6ef8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -252,6 +252,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, + "axios": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "backoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", @@ -1072,6 +1080,11 @@ } } }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1850,10 +1863,9 @@ } }, "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", - "optional": true + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, "ms": { "version": "2.0.0", diff --git a/backend/package.json b/backend/package.json index 91a1777..2c837c4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,6 +30,7 @@ "dependencies": { "archiver": "^3.1.1", "async": "^3.1.0", + "axios": "^0.21.0", "bcryptjs": "^2.4.0", "compression": "^1.7.4", "config": "^3.2.3", @@ -42,6 +43,7 @@ "lowdb": "^1.0.0", "md5": "^2.2.1", "merge-files": "^0.1.2", + "moment": "^2.29.1", "multer": "^1.4.2", "node-fetch": "^2.6.1", "node-id3": "^0.1.14", From 1542436e967676a6861986b86bdde04f971517a9 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 29 Nov 2020 18:37:16 -0500 Subject: [PATCH 040/250] Passwords now must be provided when registering a user --- backend/authentication/auth.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index f16fbf1..9fdc7e7 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -89,6 +89,12 @@ exports.registerUser = function(req, res) { return; } + if (plaintextPassword === "") { + res.sendStatus(400); + logger.error(`Registration failed for user ${userid}. A password must be provided.`); + return; + } + bcrypt.hash(plaintextPassword, saltRounds) .then(function(hash) { let new_user = generateUserObject(userid, username, hash); From bb18e1427e798d06e81072c6878c1acb92895e2d Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 5 Dec 2020 03:40:59 -0500 Subject: [PATCH 041/250] Added paginator to the home page --- .../recent-videos/recent-videos.component.html | 9 +++++++-- .../recent-videos/recent-videos.component.ts | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index bf6d596..3596c5a 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -30,8 +30,8 @@
- -
+ +
@@ -42,4 +42,9 @@
+ + +
\ No newline at end of file diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 2eb7f36..0ec4bea 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -50,6 +50,8 @@ export class RecentVideosComponent implements OnInit { }; filterProperty = this.filterProperties['upload_date']; + paged_data = null; + constructor(public postsService: PostsService, private router: Router) { // get cached file count if (localStorage.getItem('cached_file_count')) { @@ -133,6 +135,8 @@ export class RecentVideosComponent implements OnInit { localStorage.setItem('cached_file_count', '' + this.files.length); this.normal_files_received = true; + + this.paged_data = this.filtered_files.slice(0, 10); }); } @@ -276,13 +280,18 @@ 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(':'); - 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)); + 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; } + + pageChangeEvent(event) { + const offset = ((event.pageIndex + 1) - 1) * event.pageSize; + this.paged_data = this.filtered_files.slice(offset).slice(0, event.pageSize); + } } From 8c916d8fe49a6c17134c0e4103e5f5f53bfdae15 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 5 Dec 2020 04:19:48 -0500 Subject: [PATCH 042/250] Fixed bug that prevented search on the home page from working --- .../components/recent-videos/recent-videos.component.html | 4 ++-- .../components/recent-videos/recent-videos.component.ts | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index 3596c5a..f530163 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -43,8 +43,8 @@
-
\ No newline at end of file diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 0ec4bea..5c2503d 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 { Component, OnInit, ViewChild } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { Router } from '@angular/router'; +import { MatPaginator } from '@angular/material/paginator'; @Component({ selector: 'app-recent-videos', @@ -50,8 +51,11 @@ export class RecentVideosComponent implements OnInit { }; filterProperty = this.filterProperties['upload_date']; + pageSize = 10; paged_data = null; + @ViewChild('paginator') paginator: MatPaginator + constructor(public postsService: PostsService, private router: Router) { // get cached file count if (localStorage.getItem('cached_file_count')) { @@ -94,6 +98,7 @@ export class RecentVideosComponent implements OnInit { private filterFiles(value: string) { const filterValue = value.toLowerCase(); this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue)); + this.pageChangeEvent({pageSize: this.pageSize, pageIndex: this.paginator.pageIndex}); } filterByProperty(prop) { @@ -102,6 +107,7 @@ export class RecentVideosComponent implements OnInit { } else { this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? 1 : -1)); } + if (this.paginator) { this.pageChangeEvent({pageSize: this.pageSize, pageIndex: this.paginator.pageIndex}) }; } filterOptionChanged(value) { From f425b9842f9e20282218331099ef6a07094a5f71 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 8 Dec 2020 22:57:09 -0500 Subject: [PATCH 043/250] Updated twitch chat component to support user colors and to auto open if the chat has already been downloaded --- backend/app.js | 2 ++ backend/twitch.js | 6 ++++-- src/app/components/twitch-chat/twitch-chat.component.html | 4 ++-- src/app/player/player.component.html | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/app.js b/backend/app.js index e50dc8d..0cdd414 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2037,6 +2037,8 @@ app.post('/api/getFile', optionalJwt, function (req, res) { if (!file && type) file = db.get(`files.${type}`).find({uid: uid}).value(); } + // check if chat exists for twitch videos + if (file['url'].includes('twitch.tv')) file['chat_exists'] = fs.existsSync(file['path'].substring(0, file['path'].length - 4) + '.twitch_chat.json'); if (file) { res.send({ diff --git a/backend/twitch.js b/backend/twitch.js index 62c390d..fb05fbb 100644 --- a/backend/twitch.js +++ b/backend/twitch.js @@ -28,7 +28,8 @@ async function getCommentsForVOD(clientID, vodId) { created_at: acctCreated }, message: { - body: msg + body: msg, + user_color: user_color } } = c; @@ -49,7 +50,8 @@ async function getCommentsForVOD(clientID, vodId) { timestamp: timestamp, timestamp_str: timestamp_str, name: name, - message: msg + message: msg, + user_color: user_color }); // let line = `${timestamp},${msgCreated.format(tsFormat)},${name},${_id},"${msg.replace(/"/g, '""')}",${acctCreated.format(tsFormat)}`; // return line; diff --git a/src/app/components/twitch-chat/twitch-chat.component.html b/src/app/components/twitch-chat/twitch-chat.component.html index 7ad40fc..634cac6 100644 --- a/src/app/components/twitch-chat/twitch-chat.component.html +++ b/src/app/components/twitch-chat/twitch-chat.component.html @@ -1,11 +1,11 @@
Twitch Chat
- {{chat.timestamp_str}} - {{chat.name}}: {{chat.message}} + {{chat.timestamp_str}} - {{chat.name}}: {{chat.message}}
- + \ No newline at end of file diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index c70800d..6774ace 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -13,7 +13,7 @@ {{playlist_item.label}}
- + From c6fc5352c5588e0617ff980b459cc99b4a7811d5 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 9 Dec 2020 17:28:00 -0500 Subject: [PATCH 044/250] Added ability to add more metadata to db through migrations, and added scaffolding for supporting description and play count in the player component --- backend/app.js | 32 ++++++++++++++++++++++++++++ backend/db.js | 9 +++++++- backend/utils.js | 7 +++++- src/app/player/player.component.html | 12 +++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index 0cdd414..148e94c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -216,6 +216,16 @@ async function checkMigrations() { else { logger.error('Migration failed: 3.5->3.6+'); } } + // 4.1->4.2 migration + + const add_description_migration_complete = false; // db.get('add_description_migration_complete').value(); + if (!add_description_migration_complete) { + logger.info('Beginning migration: 4.1->4.2+') + const success = await addMetadataPropertyToDB('description'); + if (success) { logger.info('4.1->4.2+ migration complete!'); } + else { logger.error('Migration failed: 4.1->4.2+'); } + } + return true; } @@ -251,6 +261,28 @@ async function runFilesToDBMigration() { } } +async function addMetadataPropertyToDB(property_key) { + try { + const dirs_to_check = db_api.getFileDirectoriesAndDBs(); + for (const dir_to_check of dirs_to_check) { + // recursively get all files in dir's path + const files = await utils.getDownloadedFilesByType(dir_to_check.basePath, dir_to_check.type, true); + for (const file of files) { + if (file[property_key]) { + dir_to_check.dbPath.find({id: file.id}).assign({[property_key]: file[property_key]}).write(); + } + } + } + + // sets migration to complete + db.set('add_description_migration_complete', true).write(); + return true; + } catch(err) { + logger.error(err); + return false; + } +} + async function startServer() { if (process.env.USING_HEROKU && process.env.PORT) { // default to heroku port if using heroku diff --git a/backend/db.js b/backend/db.js index a2ddb7a..9436715 100644 --- a/backend/db.js +++ b/backend/db.js @@ -115,7 +115,7 @@ function getAppendedBasePathSub(sub, base_path) { return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name); } -async function importUnregisteredFiles() { +function 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 @@ -181,6 +181,12 @@ async function importUnregisteredFiles() { }); } + return dirs_to_check; +} + +async function importUnregisteredFiles() { + const dirs_to_check = 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) { // recursively get all files in dir's path @@ -203,5 +209,6 @@ module.exports = { initialize: initialize, registerFileDB: registerFileDB, updatePlaylist: updatePlaylist, + getFileDirectoriesAndDBs: getFileDirectoriesAndDBs, importUnregisteredFiles: importUnregisteredFiles } diff --git a/backend/utils.js b/backend/utils.js index ed3b584..33b877a 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -20,7 +20,7 @@ function getTrueFileName(unfixed_path, type) { return fixed_path; } -async function getDownloadedFilesByType(basePath, type) { +async function getDownloadedFilesByType(basePath, type, full_metadata = false) { // return empty array if the path doesn't exist if (!(await fs.pathExists(basePath))) return []; @@ -36,6 +36,11 @@ async function getDownloadedFilesByType(basePath, type) { var id = file_path.substring(0, file_path.length-4); var jsonobj = await getJSONByType(type, id, basePath); if (!jsonobj) continue; + if (full_metadata) { + jsonobj['id'] = id; + files.push(jsonobj); + continue; + } var title = jsonobj.title; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 6774ace..73ea9eb 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -8,6 +8,18 @@
+
+
+
+
+ {{file_obj['local_play_count'] ? file_obj['local_play_count'] : 0}} views +
+
+ +
+
+
+
{{playlist_item.label}} From 3f10986cdfca31af0dd7c7bf8c80bd1ef656d719 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 10 Dec 2020 19:33:53 -0500 Subject: [PATCH 045/250] Updated Angular to version 11 - ngx-videogular was replaced by @videogular/ngx-videogular --- browserslist => .browserslistrc | 0 README.md | 2 +- angular.json | 2 - package-lock.json | 13012 +++++++--------- package.json | 62 +- src/app/app-routing.module.ts | 2 +- src/app/app.component.spec.ts | 10 +- src/app/app.module.ts | 5 +- .../custom-playlists.component.spec.ts | 4 +- .../downloads/downloads.component.spec.ts | 4 +- .../components/login/login.component.spec.ts | 4 +- .../logs-viewer/logs-viewer.component.spec.ts | 4 +- .../manage-role/manage-role.component.spec.ts | 4 +- .../manage-user/manage-user.component.spec.ts | 4 +- .../modify-users.component.spec.ts | 4 +- .../recent-videos.component.spec.ts | 4 +- .../twitch-chat/twitch-chat.component.spec.ts | 4 +- .../unified-file-card.component.spec.ts | 4 +- .../create-playlist.component.spec.ts | 4 +- .../about-dialog.component.spec.ts | 4 +- .../add-user-dialog.component.spec.ts | 4 +- .../arg-modifier-dialog.component.spec.ts | 4 +- .../confirm-dialog.component.spec.ts | 4 +- .../cookies-uploader-dialog.component.spec.ts | 4 +- .../edit-category-dialog.component.spec.ts | 4 +- ...edit-subscription-dialog.component.spec.ts | 4 +- .../modify-playlist.component.spec.ts | 4 +- ...set-default-admin-dialog.component.spec.ts | 4 +- .../share-media-dialog.component.spec.ts | 4 +- .../subscribe-dialog.component.spec.ts | 4 +- ...subscription-info-dialog.component.spec.ts | 4 +- .../update-progress-dialog.component.spec.ts | 4 +- .../user-profile-dialog.component.spec.ts | 4 +- .../video-info-dialog.component.spec.ts | 4 +- .../download-item.component.spec.ts | 4 +- src/app/file-card/file-card.component.spec.ts | 4 +- .../input-dialog.component.spec.ts | 4 +- src/app/main/main.component.spec.ts | 4 +- src/app/player/player.component.spec.ts | 4 +- src/app/player/player.component.ts | 6 +- src/app/settings/settings.component.spec.ts | 4 +- .../subscription-file-card.component.spec.ts | 4 +- .../subscription.component.spec.ts | 4 +- .../subscriptions.component.spec.ts | 4 +- src/app/updater/updater.component.spec.ts | 4 +- tsconfig.json | 2 +- tslint.json | 4 +- 47 files changed, 5782 insertions(+), 7469 deletions(-) rename browserslist => .browserslistrc (100%) diff --git a/browserslist b/.browserslistrc similarity index 100% rename from browserslist rename to .browserslistrc diff --git a/README.md b/README.md index 0eadd3b..0599607 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![GitHub issues badge](https://img.shields.io/github/issues/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/issues) [![License badge](https://img.shields.io/github/license/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md) -YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 9](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend. +YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 11](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend. Now with [Docker](#Docker) support! diff --git a/angular.json b/angular.json index c32139b..46e84ea 100644 --- a/angular.json +++ b/angular.json @@ -45,8 +45,6 @@ ], "optimization": true, "outputHashing": "all", - "sourceMap": false, - "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, diff --git a/package-lock.json b/package-lock.json index 08cd667..f0e3621 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,103 +5,111 @@ "requires": true, "dependencies": { "@angular-devkit/architect": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.0.tgz", - "integrity": "sha512-SlqEBkPrT40zMCy5344AsUqC76pEPCaGPaAkCIvadaz2dC9vNMzQrvubCPJHViD/TumkSX1kYmLS3iYASVM9GQ==", + "version": "0.1100.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1100.4.tgz", + "integrity": "sha512-hzTfcSUwM0jsSt9HvvSFyaoAhX9k73L7y4kmkghzIFhKhIKOp/7o3n7hAFwN/jWKKmVQpPKnYmqzm9H9OveaCQ==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" + "@angular-devkit/core": "11.0.4", + "rxjs": "6.6.3" } }, "@angular-devkit/build-angular": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.0.tgz", - "integrity": "sha512-ftJVNlKvIomqRfr5jFVraPqlLSUJu8YyVbFv/aCsvhNpuZGkYpTOMoJDwyywdslSTH608BIoU63IAnIz9PwUdw==", + "version": "0.1100.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.1100.4.tgz", + "integrity": "sha512-qVkMbtOwlo+k8fvOBOwwfKWMx06k4I1qrdjpRYAoZCt3cdje4EBepSciLrHnTB+ouIqWxpEDfEXTYBS98tXbBg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/build-optimizer": "0.901.0", - "@angular-devkit/build-webpack": "0.901.0", - "@angular-devkit/core": "9.1.0", - "@babel/core": "7.9.0", - "@babel/generator": "7.9.3", - "@babel/preset-env": "7.9.0", - "@babel/template": "7.8.6", - "@jsdevtools/coverage-istanbul-loader": "3.0.3", - "@ngtools/webpack": "9.1.0", - "ajv": "6.12.0", - "autoprefixer": "9.7.4", - "babel-loader": "8.0.6", + "@angular-devkit/architect": "0.1100.4", + "@angular-devkit/build-optimizer": "0.1100.4", + "@angular-devkit/build-webpack": "0.1100.4", + "@angular-devkit/core": "11.0.4", + "@babel/core": "7.12.3", + "@babel/generator": "7.12.1", + "@babel/plugin-transform-runtime": "7.12.1", + "@babel/preset-env": "7.12.1", + "@babel/runtime": "7.12.1", + "@babel/template": "7.10.4", + "@jsdevtools/coverage-istanbul-loader": "3.0.5", + "@ngtools/webpack": "11.0.4", + "ansi-colors": "4.1.1", + "autoprefixer": "9.8.6", + "babel-loader": "8.1.0", "browserslist": "^4.9.1", - "cacache": "15.0.0", + "cacache": "15.0.5", "caniuse-lite": "^1.0.30001032", "circular-dependency-plugin": "5.2.0", - "copy-webpack-plugin": "5.1.1", - "core-js": "3.6.4", + "copy-webpack-plugin": "6.2.1", + "core-js": "3.6.5", + "css-loader": "4.3.0", "cssnano": "4.1.10", - "file-loader": "6.0.0", + "file-loader": "6.1.1", "find-cache-dir": "3.3.1", "glob": "7.1.6", - "jest-worker": "25.1.0", + "inquirer": "7.3.3", + "jest-worker": "26.5.0", "karma-source-map-support": "1.4.0", - "less": "3.11.1", - "less-loader": "5.0.0", - "license-webpack-plugin": "2.1.4", + "less": "3.12.2", + "less-loader": "7.0.2", + "license-webpack-plugin": "2.3.1", "loader-utils": "2.0.0", - "mini-css-extract-plugin": "0.9.0", + "mini-css-extract-plugin": "1.2.1", "minimatch": "3.0.4", - "open": "7.0.3", - "parse5": "4.0.0", - "postcss": "7.0.27", + "open": "7.3.0", + "ora": "5.1.0", + "parse5-html-rewriting-stream": "6.0.1", + "pnp-webpack-plugin": "1.6.4", + "postcss": "7.0.32", "postcss-import": "12.0.1", - "postcss-loader": "3.0.0", - "raw-loader": "4.0.0", - "regenerator-runtime": "0.13.5", + "postcss-loader": "4.0.4", + "raw-loader": "4.0.2", + "regenerator-runtime": "0.13.7", + "resolve-url-loader": "3.1.2", "rimraf": "3.0.2", - "rollup": "2.1.0", - "rxjs": "6.5.4", - "sass": "1.26.3", - "sass-loader": "8.0.2", - "semver": "7.1.3", + "rollup": "2.32.1", + "rxjs": "6.6.3", + "sass": "1.27.0", + "sass-loader": "10.0.5", + "semver": "7.3.2", "source-map": "0.7.3", - "source-map-loader": "0.2.4", - "source-map-support": "0.5.16", - "speed-measure-webpack-plugin": "1.3.1", - "style-loader": "1.1.3", - "stylus": "0.54.7", - "stylus-loader": "3.0.2", - "terser": "4.6.7", - "terser-webpack-plugin": "2.3.5", + "source-map-loader": "1.1.2", + "source-map-support": "0.5.19", + "speed-measure-webpack-plugin": "1.3.3", + "style-loader": "2.0.0", + "stylus": "0.54.8", + "stylus-loader": "4.3.1", + "terser": "5.3.7", + "terser-webpack-plugin": "4.2.3", + "text-table": "0.2.0", "tree-kill": "1.2.2", - "webpack": "4.42.0", + "webpack": "4.44.2", "webpack-dev-middleware": "3.7.2", - "webpack-dev-server": "3.10.3", - "webpack-merge": "4.2.2", - "webpack-sources": "1.4.3", - "webpack-subresource-integrity": "1.4.0", - "worker-plugin": "4.0.2" + "webpack-dev-server": "3.11.0", + "webpack-merge": "5.2.0", + "webpack-sources": "2.0.1", + "webpack-subresource-integrity": "1.5.1", + "worker-plugin": "5.0.0" }, "dependencies": { "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -122,14 +130,13 @@ } }, "@babel/generator": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.3.tgz", - "integrity": "sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", + "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", "dev": true, "requires": { - "@babel/types": "^7.9.0", + "@babel/types": "^7.12.1", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" }, "dependencies": { @@ -141,10 +148,21 @@ } } }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", "dev": true }, "glob": { @@ -161,201 +179,205 @@ "path-is-absolute": "^1.0.0" } }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } }, "@angular-devkit/build-optimizer": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.0.tgz", - "integrity": "sha512-Y9sz8uf2zjilhPUVYb0K9Mio6c1d5c+csuDc15CCKzELXJwyyDxilIFgn6Eu+edM0HNQGzbIwkjy4DkR9mtuTQ==", + "version": "0.1100.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1100.4.tgz", + "integrity": "sha512-C05y4qMb05PWR7l1gZwRQKiB6KIDq+p72r8Yr6jm0UO6raOtMM72R8nHnioMnGJcFtZDEAYXEF+X7soI3MMlfw==", "dev": true, "requires": { "loader-utils": "2.0.0", "source-map": "0.7.3", - "tslib": "1.11.1", - "typescript": "3.8.3", - "webpack-sources": "1.4.3" + "tslib": "2.0.3", + "typescript": "4.0.5", + "webpack-sources": "2.0.1" }, "dependencies": { - "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", "dev": true } } }, "@angular-devkit/build-webpack": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.0.tgz", - "integrity": "sha512-Oze0VzIvHnoW12C80fiNH4HBu/GWmhJPXdNA7nRkU/tBQlIKnfngf8rQ0QbgecN2qdEXQpZJsP/XclTi3zugsg==", + "version": "0.1100.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1100.4.tgz", + "integrity": "sha512-uxe8gNSej3KF1FgqNtJmuRDbbINh3yLtXanXhRxFQLUj8IiNR8IciIVvy6RfXC5gqxcWwy1cOefJLLnuN9AOxQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" + "@angular-devkit/architect": "0.1100.4", + "@angular-devkit/core": "11.0.4", + "rxjs": "6.6.3" } }, "@angular-devkit/core": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.0.tgz", - "integrity": "sha512-vHTsrB4JaVUQ95FRnKrgo79Y3F6FokImrZdrmwkQmwAThpjXeXmpUEKZS+ZSTFRgesjiIysVGOFijARP4BQ7Bg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-11.0.4.tgz", + "integrity": "sha512-LgTvhZ3Ycz0QvNAH/zO1rpQQDn2JN8u9/Awy1gW/XeCC3FYmxeOj/2JCFzlKah3wJv16nMqro5WTppHt8Y++PA==", "requires": { - "ajv": "6.12.0", + "ajv": "6.12.6", "fast-json-stable-stringify": "2.1.0", "magic-string": "0.25.7", - "rxjs": "6.5.4", + "rxjs": "6.6.3", "source-map": "0.7.3" } }, "@angular-devkit/schematics": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.1.0.tgz", - "integrity": "sha512-cb9PSvskMwWlL54fPfCcpJoyNDWAX6Wo7CzL5qpIB2cJCPLAuyfRUYYrkO77YUST+n2HvypHz0cZ5SNGMfaaBQ==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-11.0.4.tgz", + "integrity": "sha512-fFC7qW9A1bFAZgpCfkezBA4WCRzfVFgOzwPpyt65rgSrzw0+EeHjcrUIcXlhyOXAFrTHtA9oLCfEeSjSx5HBEA==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.0", - "ora": "4.0.3", - "rxjs": "6.5.4" + "@angular-devkit/core": "11.0.4", + "ora": "5.1.0", + "rxjs": "6.6.3" } }, "@angular/animations": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.0.tgz", - "integrity": "sha512-o7X3HM+eocoryw3VrDUtG6Wci2KwtzyBFo3KBJXjQ16X6fwdkjTG+hLb7pp2CBFBEJW4tPYEy7cSBmEfMRTqag==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.0.4.tgz", + "integrity": "sha512-NI7UdLNdzTfLCDu0zVqwhdKq2z1flRsM2GCD9RHG/NRjlohh73uRTBW+BcYpfh+o+Wq4giiq8UkTIgS2ReqDGg==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/cdk": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.0.tgz", - "integrity": "sha512-jeeznvNDpR9POuxzz8Y0zFvMynG9HCJo3ZPTqOjlOq8Lj8876+rLsHDvKEMeLdwlkdi1EweYJW1CLQzI+TwqDA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.0.2.tgz", + "integrity": "sha512-hdZ9UJVGgCFhdOuB4RPS1Ku45VSG/WfRjbyxu/7teYyFKqAvcd3vawkeQfZf+lExmFaeW43+5hnpu/JIlGTrSA==", "requires": { - "parse5": "^5.0.0" + "parse5": "^5.0.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } } }, "@angular/cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.0.tgz", - "integrity": "sha512-ofum4gPE/W3fKyzuJrpdHeOS0ZL8x0eYCgsrMyUoFodSpb5LWPqeW+56NgDTpIeny+Trx3pM9dr9QTUVTJ0vYg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-11.0.4.tgz", + "integrity": "sha512-VkE/gx6P80EJHg13fG+gkZfd2DJmRaDAtnamcCGM4AThzoUN9XBdxc24uMLEzBb0/mJ4vpMK9+WTNIdMmzl+Tg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.901.0", - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0", - "@schematics/angular": "9.1.0", - "@schematics/update": "0.901.0", + "@angular-devkit/architect": "0.1100.4", + "@angular-devkit/core": "11.0.4", + "@angular-devkit/schematics": "11.0.4", + "@schematics/angular": "11.0.4", + "@schematics/update": "0.1100.4", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", - "debug": "4.1.1", + "debug": "4.2.0", "ini": "1.3.5", - "inquirer": "7.1.0", - "npm-package-arg": "8.0.1", - "npm-pick-manifest": "6.0.0", - "open": "7.0.3", - "pacote": "11.1.4", - "read-package-tree": "5.3.1", + "inquirer": "7.3.3", + "npm-package-arg": "8.1.0", + "npm-pick-manifest": "6.1.0", + "open": "7.3.0", + "pacote": "9.5.12", + "resolve": "1.18.1", "rimraf": "3.0.2", - "semver": "7.1.3", - "symbol-observable": "1.2.0", - "universal-analytics": "0.4.20", - "uuid": "7.0.2" + "semver": "7.3.2", + "symbol-observable": "2.0.3", + "universal-analytics": "0.4.23", + "uuid": "8.3.1" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.0.tgz", - "integrity": "sha512-SlqEBkPrT40zMCy5344AsUqC76pEPCaGPaAkCIvadaz2dC9vNMzQrvubCPJHViD/TumkSX1kYmLS3iYASVM9GQ==", + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.0", - "rxjs": "6.5.4" + "ms": "2.1.2" } }, - "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==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "open": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz", - "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", - "dev": true, - "requires": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" } }, "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, "uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", "dev": true } } }, "@angular/common": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.1.0.tgz", - "integrity": "sha512-6JPLNtMhI03bGTVQJeSwc+dTjV6DtP7M/BAyzIV0InZP1D6XsOh2QahLFIaaN2sSxYA2ClKuwfX1v+rx9AbXQA==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.0.4.tgz", + "integrity": "sha512-4R2ALj71J6EAHVCKNnHHCKL7wcosMsv3gcMXbMTE+Wpzo3khEhM0Tej+I1qmMbVmGXVlRb//4+rjE4gff6FvQw==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/compiler": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.1.0.tgz", - "integrity": "sha512-QHw/JSeTXHiJQ2Ih0EtU7FGsYcOr+0hwZhqwSW3EEn8TtUgA3DS5lXeiDV66f+3DdvNZFPmgiZIvun3ypxn1HA==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.0.4.tgz", + "integrity": "sha512-Smf8FKSjkqd522ZCdXjSnVv1lYA0b21AN3WC5L1mwtRwyl/VacqCA/YEklLneDGgI2FdSIC9+bzSQIV+CCVftA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/compiler-cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.1.0.tgz", - "integrity": "sha512-xZ8mVPmPporSTtvNA+cbFJQymLzuWfMX6HDDgztZ2eZ5WcQJYloRN4CcYMEzDhCxfV1Zw9Tfc2l14jZD8osi6g==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-11.0.4.tgz", + "integrity": "sha512-FV010O6GAldRe5sr+qegHe7oLinTylES70NX+0PIp44/W4tPx75Zvop+FVT90I4xPcvFvteLemy8nFAnMK+x5g==", "dev": true, "requires": { + "@babel/core": "^7.8.6", + "@babel/types": "^7.8.6", "canonical-path": "1.0.0", "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", @@ -367,9 +389,54 @@ "semver": "^6.3.0", "source-map": "^0.6.1", "sourcemap-codec": "^1.4.8", - "yargs": "15.3.0" + "tslib": "^2.0.0", + "yargs": "^16.1.1" }, "dependencies": { + "@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -381,73 +448,134 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true } } }, "@angular/core": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.1.0.tgz", - "integrity": "sha512-RVlyegdIAij0P1wLY5ObIdsBAzvmHkHfElnmfiNKhaDftP6U/3zRtaKDu0bq0jvn1WCQ8zXxFQ8AWyKZwyFS+w==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.0.4.tgz", + "integrity": "sha512-860cTMjdCHcvEsHOsTzpg5rThxwVgtnY4yT0SgboWiphrlzX+aNoyN/cCJHxWhmOTRlrl6/+hkeRq95E2BZkKw==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/forms": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.1.0.tgz", - "integrity": "sha512-5GC8HQlPChPV+168zLlm4yj4syA6N9ChSKV0tmzj1zIfMcub1UAOaB9IYaXRHQsjPFh9OuQXwmkzScyAfhEVjA==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.0.4.tgz", + "integrity": "sha512-Fhfc4buwMZk0WumDvl/X7XBnOKFeTRTJrwKdi8LlhY6o1Og8H4e/f69u9iDJCF3YjU4qC6yGtPp9YpSVCPP7Ew==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/language-service": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.1.0.tgz", - "integrity": "sha512-2f8ECoXrj40oS1rtIfi+F8T4WPzundcZDs8WMFNBuWYbk14v1S9sTgMEmZyePHGkPjt6IfYiLJKJCvVgrt1nxQ==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-11.0.4.tgz", + "integrity": "sha512-KtQxVSlZi3SwZEN4E56KHkNTFEYa3FPZfLJFm6WD1dSobFyMwJgvztO08GWSaT4S0ht0NNRD2IRt0XzBYuZkag==", "dev": true }, "@angular/localize": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-9.1.0.tgz", - "integrity": "sha512-llM46gLyxW6jD0VQcLSGLMB0nZ0FIWAp1W2BMWbUspmcSCEcLmdhSWKPpZLFt/vGtpvGYSY/LDeScaHSRmAe+g==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-11.0.4.tgz", + "integrity": "sha512-r0dWvwFvEqQJ/H94rr548S092Hq53RqyMANuFD09CpkWXS6yAWBfArB6mW77PARnicEC0zkbi5qMGACjtSyNtA==", "requires": { "@babel/core": "7.8.3", "glob": "7.1.2", - "yargs": "15.3.0" + "yargs": "^16.1.1" } }, "@angular/material": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.0.tgz", - "integrity": "sha512-KKzEIVh6/m56m+Ao8p4PK0SyEr0574l3VP2swj1qPag3u+FYgemmXCGTaChrKdDsez+zeTCPXImBGXzE6NQ80Q==" + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-11.0.2.tgz", + "integrity": "sha512-qmLxrIcbbowXrE0+ZiGA/RXfaZTtVKtgLchn7GvI+R5DZ79g5IicTPxayzQavLJSTESX19JTjlByRSGiXJstgA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/platform-browser": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.0.tgz", - "integrity": "sha512-OsS/blUjl8ranmDaRADjFAmvnlmwbT6WNU7dVov7FhV0rqesbwaOJ5bR0LSYHYpej7Jaa6oYk0v0XWkaH9LTFg==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.0.4.tgz", + "integrity": "sha512-+uUCKJgspSghJ3R6Fk0XHA0tolbaRBi8JFS2cY+hi9s27WKB88peGvtsK6RCOPJONY6JdOuhpcZqRN8dKfPi7w==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/platform-browser-dynamic": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.0.tgz", - "integrity": "sha512-sMtz/poQ3TYaWZzWjrn9apKUZ/WKql2MYCWbpax7pql3GgC9OoTslc7ZEe7/d3ynfFE/CQqWBBOuWGD71Z0LMQ==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.0.4.tgz", + "integrity": "sha512-ZOWTZaFfZSHhMy7a0RIxipiZoiobHWrGlq8/YaMrIgzUb9Fv518FeFCCI68BP0/GuyxX74MJmzv4ZgQctKKxXw==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@angular/router": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.0.tgz", - "integrity": "sha512-cExO1nPnoPFiUJWZ28hTHozPLFoCmqr3xqcM57We0hhKE0esdrO+gRWKRH0EJERukLbU8coPKVhA8daGUpASiQ==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.0.4.tgz", + "integrity": "sha512-B0sqv8zMM6j88+udEZzO8wKBw61pHgWZmLopnAqA65rRPrAvMsvAHUnYqX6w5pYqhJQxCVLVeKM+0QlQh1+WnA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.10.4" } }, "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", - "dev": true, - "requires": { - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", + "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", + "dev": true }, "@babel/core": { "version": "7.8.3", @@ -479,13 +607,12 @@ } }, "@babel/generator": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", - "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", "requires": { - "@babel/types": "^7.9.0", + "@babel/types": "^7.12.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" }, "dependencies": { @@ -497,327 +624,416 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", + "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", + "@babel/compat-data": "^7.12.5", + "@babel/helper-validator-option": "^7.12.1", + "browserslist": "^4.14.5", "semver": "^5.5.0" } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", + "@babel/helper-create-class-features-plugin": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", + "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "regexpu-core": "^4.7.1" + }, + "dependencies": { + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + } } }, "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } } }, "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", "dev": true, "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.1" } }, "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.7" } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.5" } }, "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } } }, "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz", + "integrity": "sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.7" } }, "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "dev": true, - "requires": { - "lodash": "^4.17.13" - } - }, "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/types": "^7.12.1" } }, "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", + "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" } }, "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/helper-validator-option": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", + "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==", + "dev": true }, "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz", + "integrity": "sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", + "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "@babel/plugin-proposal-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", + "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz", - "integrity": "sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", + "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "@babel/plugin-proposal-private-methods": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -829,6 +1045,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -838,6 +1063,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -847,6 +1081,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -857,12 +1100,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -893,392 +1136,417 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", + "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", - "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", - "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", + "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", + "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", "dev": true, "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/compat-data": "^7.12.1", + "@babel/helper-compilation-targets": "^7.12.1", + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.1", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.0", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.7", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.1", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.0", - "browserslist": "^4.9.1", + "@babel/types": "^7.12.1", "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", "semver": "^5.5.0" } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1289,48 +1557,62 @@ } }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", + "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" } }, "@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz", + "integrity": "sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + } } }, "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz", + "integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + } } }, "@electron/get": { @@ -1370,38 +1652,16 @@ "dev": true }, "@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz", - "integrity": "sha512-TAdNkeGB5Fe4Og+ZkAr1Kvn9by2sfL44IAHFtxlh1BA1XJ5cLpO9iSNki5opWESv3l3vSHsZ9BNKuqFKbEbFaA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", "dev": true, "requires": { "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.1", - "loader-utils": "^1.4.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", "merge-source-map": "^1.1.0", - "schema-utils": "^2.6.4" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } + "schema-utils": "^2.7.0" } }, "@ngneat/content-loader": { @@ -1420,107 +1680,90 @@ } }, "@ngtools/webpack": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.0.tgz", - "integrity": "sha512-kQ+1N/F+5tuUXiiaoqJwhcOIM0I93EEvF3xwpTLRm91wl2i8R1261LvsD/uQPrgLrZNGR6eFhFF1Izn2PnIjQA==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.0.4.tgz", + "integrity": "sha512-MAV7inQmsMISTnDcXwyRX5oJZx8F7K/tZRLJciQwkM0DqZyq8fI9KDRwBcmYeQ+J0mSJV9LUVdExmpulpkywqw==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.0", - "enhanced-resolve": "4.1.1", - "rxjs": "6.5.4", - "webpack-sources": "1.4.3" + "@angular-devkit/core": "11.0.4", + "enhanced-resolve": "5.3.1", + "webpack-sources": "2.0.1" } }, - "@npmcli/ci-detect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/ci-detect/-/ci-detect-1.2.0.tgz", - "integrity": "sha512-JtktVH7ASBVIWsQTFlFpeOzhBJskvoBCTfeeRhhZy7ybATcUvwiwotZ8j5rkqUUyB69lIy/AvboiiiGBjYBKBA==", - "dev": true - }, - "@npmcli/git": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.0.1.tgz", - "integrity": "sha512-hVatexiBtx71F01Ars38Hr5AFUGmJgHAfQtRlO5fJlnAawRGSXwEFgjB5i3XdUUmElZU/RXy7fefN02dZKxgPw==", + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", "dev": true, "requires": { - "@npmcli/promise-spawn": "^1.1.0", - "mkdirp": "^1.0.3", - "npm-pick-manifest": "^6.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "unique-filename": "^1.1.1", - "which": "^2.0.2" + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" }, "dependencies": { "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, - "@npmcli/installed-package-contents": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.5.tgz", - "integrity": "sha512-aKIwguaaqb6ViwSOFytniGvLPb9SMCUm39TgM3SfUo7n0TxUMbwoXfpwyvQ4blm10lzbAwTsvjr7QZ85LvTi4A==", - "dev": true, - "requires": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1", - "read-package-json-fast": "^1.1.1", - "readdir-scoped-modules": "^1.1.0" - } - }, - "@npmcli/promise-spawn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.1.0.tgz", - "integrity": "sha512-FwbuYN9KXBkloLeIR3xRgI8dyOdfK/KzaJlChszNuwmUXD1lHXfLlSeo4n4KrKt2udIK9K9/TzlnyCA3ubM2fA==", - "dev": true, - "requires": { - "infer-owner": "^1.0.4" - } - }, "@schematics/angular": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.1.0.tgz", - "integrity": "sha512-qkehaITQ1S1udfnnBY5CXGWnk1iVFI8cZayjLUlRfD5w+6v9if3VIuqPssX96MqvkbjyRu1N214+ieaawzLmuA==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-11.0.4.tgz", + "integrity": "sha512-LwBD9TIoLy9XqqInJvlN4BHtPyJExyeorNiOp6rXb/wafuDbvZ+9kY9GWZTY1auVo5PNKqErfxr74ydA3FFb9g==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0" + "@angular-devkit/core": "11.0.4", + "@angular-devkit/schematics": "11.0.4", + "jsonc-parser": "2.3.1" } }, "@schematics/update": { - "version": "0.901.0", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.901.0.tgz", - "integrity": "sha512-u2VESL1dgOSGZK/wcWEz0WcCU/yv764zhzCQerCwUtbV1CISSSDZ6x+prVYDXOdxWBGtDos2MbCF3GEJJI1T+w==", + "version": "0.1100.4", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.1100.4.tgz", + "integrity": "sha512-YwFtgxCQQkYC89IC7dfshyGr0roE6bpp5HgpQLdS/AOjHeZKo7/SPdM0W4ddB+Fml1Fo6v4eFG/Ia9oR7qNv1A==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.0", - "@angular-devkit/schematics": "9.1.0", + "@angular-devkit/core": "11.0.4", + "@angular-devkit/schematics": "11.0.4", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "npm-package-arg": "^8.0.0", - "pacote": "11.1.4", - "rxjs": "6.5.4", - "semver": "7.1.3", + "pacote": "9.5.12", + "semver": "7.3.2", "semver-intersect": "1.4.0" }, "dependencies": { "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } @@ -1540,29 +1783,12 @@ "defer-to-connect": "^1.0.1" } }, - "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", - "dev": true - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, "@types/core-js": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/@types/core-js/-/core-js-2.5.3.tgz", "integrity": "sha512-F9RHpjuPSit4dCCRXgi7XcqA01DAjy9QY+v9yICoxXsjXD9cgQpyZyL2eSZnTkBGXGaQnea8waZOZTogLDB+rA==", "dev": true }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, "@types/file-saver": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz", @@ -1570,20 +1796,25 @@ "dev": true }, "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, "requires": { - "@types/events": "*", "@types/minimatch": "*", "@types/node": "*" } }, "@types/jasmine": { - "version": "2.5.45", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.45.tgz", - "integrity": "sha1-WJKKYh0BTOarWcWpxBBx9zKLDKk=", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.2.tgz", + "integrity": "sha512-AzfesNFLvOs6Q1mHzIsVJXSeUnqVh4ZHG8ngygKJfbkcSLwzrBVm/LKa+mR8KrOfnWtUL47112gde1MC0IXqpQ==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, "@types/minimatch": { @@ -1598,16 +1829,22 @@ "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, "@types/selenium-webdriver": { - "version": "2.53.45", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.45.tgz", - "integrity": "sha512-/DJGXtMuklLQef49qDsmjQofzaGU6D5To2xJZRLBNZO4GLAi30IjxLy6hHRQdycXP+IZKKXqJhYyNJ53SglJ8w==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", + "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", "dev": true }, "@types/source-list-map": { @@ -1617,9 +1854,9 @@ "dev": true }, "@types/webpack-sources": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", - "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.8.tgz", + "integrity": "sha512-JHB2/xZlXOjzjBB6fMOpH1eQAfsrpqVVIbneE0Rok16WXwFaznaI5vfg75U5WgGJm7V9W1c4xeRQDjX/zwvghA==", "dev": true, "requires": { "@types/node": "*", @@ -1635,179 +1872,186 @@ } } }, + "@videogular/ngx-videogular": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@videogular/ngx-videogular/-/ngx-videogular-2.1.0.tgz", + "integrity": "sha512-YVTutwUc5j1JN7x2FGlX4KR8t7E3h8bqDg1H9oqxk80nsjqIC/opY8zlKsKpULs0ZaSmTfsjKrzVAu6WdBXIzg==", + "requires": { + "tslib": "^1.11.1 || ^2.0.0" + } + }, "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", "dev": true }, "@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.8.5" + "@webassemblyjs/wast-printer": "1.9.0" } }, "@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", "dev": true }, "@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "^0.0.3" + "@webassemblyjs/ast": "1.9.0" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" } }, "@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" } }, "@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, "@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" } }, "@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, "@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", "@xtuc/long": "4.2.2" } }, @@ -1829,6 +2073,22 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -1840,15 +2100,25 @@ } }, "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, + "adjust-sourcemap-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", + "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + } + }, "adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, "after": { @@ -1858,29 +2128,27 @@ "dev": true }, "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "requires": { - "debug": "4" + "es6-promisify": "^5.0.0" } }, "agentkeepalive": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.0.tgz", - "integrity": "sha512-CW/n1wxF8RpEuuiq6Vbn9S8m0VSYDMnZESqaJ6F2cWN9fY8rei2qaxweIaRgq+ek8TqfoFIsUjaGNKGGEHElSg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "dev": true, "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", "humanize-ms": "^1.2.1" } }, "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "requires": { "clean-stack": "^2.0.0", @@ -1888,9 +2156,9 @@ } }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1905,9 +2173,9 @@ "dev": true }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, "alphanum-sort": { @@ -1917,9 +2185,9 @@ "dev": true }, "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-escapes": { @@ -1961,20 +2229,11 @@ } }, "app-root-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", - "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", "dev": true }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -2000,6 +2259,12 @@ "commander": "^2.11.0" } }, + "arity-n": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", + "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=", + "dev": true + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -2024,20 +2289,11 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true }, "array-uniq": { "version": "1.0.3", @@ -2052,9 +2308,9 @@ "dev": true }, "arraybuffer.slice": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", - "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, "arrify": { @@ -2063,12 +2319,6 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2079,14 +2329,23 @@ } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "assert": { @@ -2168,18 +2427,18 @@ "dev": true }, "autoprefixer": { - "version": "9.7.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", - "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", "dev": true, "requires": { - "browserslist": "^4.8.3", - "caniuse-lite": "^1.0.30001020", - "chalk": "^2.4.2", + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.26", - "postcss-value-parser": "^4.0.2" + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" } }, "aws-sign2": { @@ -2189,9 +2448,9 @@ "dev": true }, "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, "axobject-query": { @@ -2203,105 +2462,17 @@ "ast-types-flow": "0.0.7" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, "babel-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", - "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", "dev": true, "requires": { - "find-cache-dir": "^2.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "pify": "^4.0.1" + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" }, "dependencies": { "find-cache-dir": { @@ -2337,121 +2508,15 @@ } } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "requires": { "object.assign": "^4.1.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -2519,21 +2584,21 @@ } }, "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", "dev": true }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true }, "batch": { @@ -2567,31 +2632,21 @@ "dev": true }, "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.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", "dev": true }, "blocking-proxy": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-0.0.5.tgz", - "integrity": "sha1-RikF4Nz76pcPQao3Ij3anAexkSs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -2604,9 +2659,9 @@ "dev": true }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "body-parser": { @@ -2647,12 +2702,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true } } }, @@ -2745,28 +2794,49 @@ } }, "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", "dev": true, "requires": { - "bn.js": "^4.1.0", + "bn.js": "^5.0.0", "randombytes": "^2.0.1" } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "dev": true, "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "browserify-zlib": { @@ -2779,15 +2849,25 @@ } }, "browserslist": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz", - "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.15.0.tgz", + "integrity": "sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001038", - "electron-to-chromium": "^1.3.390", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "caniuse-lite": "^1.0.30001164", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.612", + "escalade": "^3.1.1", + "node-releases": "^1.1.67" + } + }, + "browserstack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.0.tgz", + "integrity": "sha512-HJDJ0TSlmkwnt9RZ+v5gFpa1XZTBYTj0ywvLwJ3241J7vMw2jAsGNVhKHtmCOyg+VxeLZyaibO9UL71AsUeDIw==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" } }, "buffer": { @@ -2801,34 +2881,12 @@ "isarray": "^1.0.0" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2847,6 +2905,12 @@ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -2866,27 +2930,27 @@ "dev": true }, "cacache": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", - "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", "dev": true, "requires": { - "chownr": "^1.1.2", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", + "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", + "p-map": "^4.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", + "rimraf": "^3.0.2", "ssri": "^8.0.0", - "tar": "^6.0.1", + "tar": "^6.0.2", "unique-filename": "^1.1.1" }, "dependencies": { @@ -2905,19 +2969,10 @@ } }, "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } } } }, @@ -2976,6 +3031,16 @@ } } }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -3007,9 +3072,10 @@ "dev": true }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true }, "caniuse-api": { "version": "3.0.0", @@ -3024,9 +3090,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001038", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001038.tgz", - "integrity": "sha512-zii9quPo96XfOiRD4TrfYGs+QsGZpb2cGiMAzPjtf/hpFgB6zCPZgJb7I1+EATeMw/o+lG8FyRAnI+CWStHcaQ==", + "version": "1.0.30001165", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz", + "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==", "dev": true }, "canonical-path": { @@ -3058,9 +3124,9 @@ "dev": true }, "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", "dev": true, "requires": { "anymatch": "~3.1.1", @@ -3070,24 +3136,13 @@ "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - }, - "dependencies": { - "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==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } + "readdirp": "~3.5.0" } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, "chrome-trace-event": { @@ -3097,6 +3152,14 @@ "dev": true, "requires": { "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "cipher-base": { @@ -3154,31 +3217,31 @@ } }, "cli-spinners": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", - "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", + "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", "dev": true }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "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": "^6.2.0" + "wrap-ansi": "^7.0.0" } }, "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "clone-deep": { @@ -3212,29 +3275,40 @@ "q": "^1.1.2" } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "codelyzer": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.2.tgz", - "integrity": "sha512-jB4FZ1Sx7kZhvZVdf+N2BaKTdrrNZOL0Bj10RRfrhHrb3zEvXjJvvq298JPMJAiyiCS/v4zs1QlGU0ip7xGqeA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.1.tgz", + "integrity": "sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g==", "dev": true, "requires": { - "app-root-path": "^2.2.1", + "@angular/compiler": "9.0.0", + "@angular/core": "9.0.0", + "app-root-path": "^3.0.0", "aria-query": "^3.0.0", "axobject-query": "2.0.2", "css-selector-tokenizer": "^0.7.1", "cssauron": "^1.4.0", "damerau-levenshtein": "^1.0.4", + "rxjs": "^6.5.3", "semver-dsl": "^1.0.1", "source-map": "^0.5.7", - "sprintf-js": "^1.1.2" + "sprintf-js": "^1.1.2", + "tslib": "^1.10.0", + "zone.js": "~0.10.3" }, "dependencies": { + "@angular/compiler": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", + "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", + "dev": true + }, + "@angular/core": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", + "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3246,6 +3320,12 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -3260,13 +3340,13 @@ } }, "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", "dev": true, "requires": { "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-string": "^1.5.4" } }, "color-convert": { @@ -3283,29 +3363,26 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "dev": true, "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", "dev": true }, - "combine-lists": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", - "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", - "dev": true, - "requires": { - "lodash": "^4.5.0" - } + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true }, "combined-stream": { "version": "1.0.8", @@ -3346,6 +3423,15 @@ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", "dev": true }, + "compose-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", + "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=", + "dev": true, + "requires": { + "arity-n": "^1.0.4" + } + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -3543,120 +3629,58 @@ "dev": true }, "copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz", + "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==", "dev": true, "requires": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", + "cacache": "^15.0.5", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "webpack-log": "^2.0.0" + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "webpack-sources": "^1.4.3" }, "dependencies": { - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" + "yocto-queue": "^0.1.0" } }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "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 + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" } } } @@ -3667,12 +3691,12 @@ "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" }, "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", + "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", "dev": true, "requires": { - "browserslist": "^4.8.3", + "browserslist": "^4.15.0", "semver": "7.0.0" }, "dependencies": { @@ -3703,13 +3727,21 @@ } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "create-hash": { @@ -3807,6 +3839,37 @@ "timsort": "^0.3.0" } }, + "css-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", + "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^2.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.3", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.1", + "semver": "^7.3.2" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "css-parse": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", @@ -3835,14 +3898,13 @@ "dev": true }, "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", "dev": true, "requires": { "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" + "fastparse": "^1.1.2" } }, "css-tree": { @@ -3864,9 +3926,9 @@ } }, "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", "dev": true }, "cssauron": { @@ -3962,28 +4024,28 @@ "dev": true }, "csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", "dev": true, "requires": { - "css-tree": "1.0.0-alpha.39" + "css-tree": "^1.1.2" }, "dependencies": { "css-tree": { - "version": "1.0.0-alpha.39", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", - "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", + "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", "dev": true, "requires": { - "mdn-data": "2.0.6", + "mdn-data": "2.0.14", "source-map": "^0.6.1" } }, "mdn-data": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", - "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, "source-map": { @@ -4006,6 +4068,16 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", @@ -4021,6 +4093,12 @@ "assert-plus": "^1.0.0" } }, + "date-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -4029,16 +4107,11 @@ "ms": "^2.1.1" } }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decode-uri-component": { "version": "0.2.0", @@ -4079,15 +4152,6 @@ "ip-regex": "^2.1.0" } }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -4095,14 +4159,6 @@ "dev": true, "requires": { "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - } } }, "defer-to-connect": { @@ -4176,6 +4232,15 @@ "rimraf": "^2.6.3" }, "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, "globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", @@ -4264,31 +4329,12 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -4310,15 +4356,23 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "path-type": "^3.0.0" + "path-type": "^4.0.0" } }, "dns-equal": { @@ -4369,9 +4423,9 @@ }, "dependencies": { "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", "dev": true } } @@ -4399,9 +4453,9 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "requires": { "is-obj": "^2.0.0" @@ -4453,15 +4507,15 @@ } }, "electron-to-chromium": { - "version": "1.3.393", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.393.tgz", - "integrity": "sha512-Ko3/VdhZAaMaJBLBFqEJ+M1qMiBI8sJfPY/hSJvDrkB3Do8LJsL9tmXy4w7o9nPXif/jFaZGSlXTQWU8XVsYtg==", + "version": "1.3.621", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.621.tgz", + "integrity": "sha512-FeIuBzArONbAmKmZIsZIFGu/Gc9AVGlVeVbhCq+G2YIl6QkT0TDn2HKN/FMf1btXEB9kEmIuQf3/lBTVAbmFOg==", "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -4471,6 +4525,14 @@ "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "emoji-regex": { @@ -4491,13 +4553,23 @@ "dev": true }, "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, - "optional": true, "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, "end-of-stream": { @@ -4510,144 +4582,119 @@ } }, "engine.io": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", - "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", + "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", "dev": true, "requires": { - "accepts": "1.3.3", - "base64id": "1.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", "cookie": "0.3.1", - "debug": "2.3.3", - "engine.io-parser": "1.3.2", - "ws": "1.1.2" + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" }, "dependencies": { - "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", - "dev": true, - "requires": { - "mime-types": "~2.1.11", - "negotiator": "0.6.1" - } - }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", "dev": true }, - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, "ws": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", - "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", - "dev": true, - "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" - } + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "dev": true } } }, "engine.io-client": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", - "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=", + "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==", "dev": true, "requires": { - "component-emitter": "1.2.1", + "component-emitter": "~1.3.0", "component-inherit": "0.0.3", - "debug": "2.3.3", - "engine.io-parser": "1.3.2", + "debug": "~3.1.0", + "engine.io-parser": "~2.2.0", "has-cors": "1.1.0", "indexof": "0.0.1", - "parsejson": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "1.1.2", - "xmlhttprequest-ssl": "1.5.3", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" }, "dependencies": { - "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": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "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": "0.7.2" + "ms": "2.0.0" } }, "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "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": "1.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", - "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", + "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": { - "options": ">=0.0.5", - "ultron": "1.0.x" + "async-limiter": "~1.0.0" } } } }, "engine.io-parser": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", - "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", + "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", "dev": true, "requires": { "after": "0.8.2", - "arraybuffer.slice": "0.0.6", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary": "0.1.7", - "wtf-8": "1.0.0" + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.4", + "blob": "0.0.5", + "has-binary2": "~1.0.2" } }, "enhanced-resolve": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", - "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.1.tgz", + "integrity": "sha512-G1XD3MRGrGfNcf6Hg0LVZG7GIKcYkbfHa5QMxt1HDUTdYoXH0JR1xXyg+MaKLF73E9A27uWNVxvFivNRYeUB6w==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "ent": { @@ -4657,9 +4704,9 @@ "dev": true }, "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "dev": true }, "env-paths": { @@ -4693,22 +4740,23 @@ } }, "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -4722,6 +4770,17 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -4729,6 +4788,47 @@ "dev": true, "optional": true }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4757,12 +4857,20 @@ "dev": true }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -4784,15 +4892,15 @@ "dev": true }, "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "eventsource": { @@ -4835,56 +4943,6 @@ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, - "expand-braces": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", - "dev": true, - "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" - }, - "dependencies": { - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", - "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", - "dev": true, - "requires": { - "expand-range": "^0.1.0" - } - }, - "expand-range": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", - "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", - "dev": true, - "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" - } - }, - "is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", - "dev": true - }, - "repeat-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", - "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", - "dev": true - } - } - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -4935,57 +4993,6 @@ } } }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -5044,11 +5051,22 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + } + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", "dev": true } } @@ -5192,9 +5210,23 @@ "dev": true }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -5207,6 +5239,15 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fastq": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", + "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -5241,13 +5282,26 @@ } }, "file-loader": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", - "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", "dev": true, "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } } }, "file-saver": { @@ -5255,29 +5309,6 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, "filesize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", @@ -5335,15 +5366,49 @@ "pkg-dir": "^4.1.0" }, "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "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==", "dev": true, "requires": { "semver": "^6.0.0" } }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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==", + "dev": true + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -5362,12 +5427,12 @@ } }, "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^3.0.0" } }, "fingerprintjs2": { @@ -5375,6 +5440,12 @@ "resolved": "https://registry.npmjs.org/fingerprintjs2/-/fingerprintjs2-2.1.0.tgz", "integrity": "sha512-H1k/ESTD2rJ3liupyqWBPjZC+LKfCGixQzz/NDN4dkgbmG1bVFyMOh7luKSkVDoyfhgvRm62pviNMPI+eJTZcQ==" }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -5386,24 +5457,10 @@ } }, "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", - "dev": true, - "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "dev": true }, "for-in": { "version": "1.0.2", @@ -5411,15 +5468,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -5468,15 +5516,6 @@ "readable-stream": "^2.0.0" } }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, "fs-extra": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", @@ -5515,9 +5554,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.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "dev": true, "optional": true }, @@ -5527,16 +5566,33 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "genfun": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "dev": true + }, "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "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-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -5574,61 +5630,13 @@ "path-is-absolute": "^1.0.0" } }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "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==", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, "global-agent": { @@ -5692,25 +5700,17 @@ } }, "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" } }, "got": { @@ -5744,26 +5744,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "handlebars": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", - "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "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 - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -5771,12 +5751,12 @@ "dev": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, @@ -5806,19 +5786,19 @@ } } }, - "has-binary": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", - "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, "requires": { - "isarray": "0.0.1" + "isarray": "2.0.1" }, "dependencies": { "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", "dev": true } } @@ -5893,13 +5873,33 @@ } }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "hash.js": { @@ -5930,12 +5930,12 @@ } }, "hosted-git-info": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", - "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", "dev": true, "requires": { - "lru-cache": "^5.1.1" + "lru-cache": "^6.0.0" } }, "hpack.js": { @@ -5969,9 +5969,15 @@ "dev": true }, "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, "http-cache-semantics": { @@ -6007,16 +6013,10 @@ } } }, - "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", - "dev": true - }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { "eventemitter3": "^4.0.0", @@ -6025,14 +6025,30 @@ } }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "http-proxy-middleware": { @@ -6045,2674 +6061,6 @@ "is-glob": "^4.0.0", "lodash": "^4.17.11", "micromatch": "^3.1.10" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dev": true, - "requires": { - "import-from": "^2.1.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "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==", - "dev": true - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "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==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", - "dev": true - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "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==" - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "requires": { - "is-path-inside": "^2.1.0" - } - }, - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "requires": { - "path-is-inside": "^1.0.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-retina": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz", - "integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "dev": true, - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "requires": { - "buffer-alloc": "^1.2.0" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - } - } - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", - "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "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==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jasmine": { - "version": "2.99.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.99.0.tgz", - "integrity": "sha1-jKctEC5jm4Z8ZImFbg4YqceqQrc=", - "dev": true, - "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.99.0" - }, - "dependencies": { - "jasmine-core": { - "version": "2.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", - "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", - "dev": true - } - } - }, - "jasmine-core": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.6.4.tgz", - "integrity": "sha1-3skmzQqfoof7bbXHVfpIfnTOysU=", - "dev": true - }, - "jasmine-spec-reporter": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.1.1.tgz", - "integrity": "sha1-Wm1Yq11hvqcwn7wnkjlRF1axtYg=", - "dev": true, - "requires": { - "colors": "1.1.2" - } - }, - "jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true - }, - "jest-worker": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz", - "integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^7.0.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==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.2.0.tgz", - "integrity": "sha512-2tLgY7LRNZ9Hd6gmCuBG5/OjRHQpSgJQqJoYyLLOhUgn8LdOYrjaZLcxkWnDads+AD/haWWioPNziXQcgvQJ/g==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, - "json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "karma": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", - "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", - "dev": true, - "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "chokidar": "^1.4.1", - "colors": "^1.1.0", - "combine-lists": "^1.0.0", - "connect": "^3.6.0", - "core-js": "^2.2.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "expand-braces": "^0.1.1", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^3.8.0", - "log4js": "^0.6.31", - "mime": "^1.3.4", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "1.7.3", - "source-map": "^0.5.3", - "tmp": "0.0.31", - "useragent": "^2.1.12" - }, - "dependencies": { - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "karma-chrome-launcher": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz", - "integrity": "sha1-IWh5xorATY1RQOmWGboEtZr9Rs8=", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "karma-cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/karma-cli/-/karma-cli-1.0.1.tgz", - "integrity": "sha1-rmw8WKMTodALRRZMRVubhs4X+WA=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "karma-coverage-istanbul-reporter": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.3.tgz", - "integrity": "sha1-O13/RmT6W41RlrmInj9hwforgNk=", - "dev": true, - "requires": { - "istanbul-api": "^1.3.1", - "minimatch": "^3.0.4" - } - }, - "karma-jasmine": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", - "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", - "dev": true - }, - "karma-jasmine-html-reporter": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", - "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", - "dev": true, - "requires": { - "karma-jasmine": "^1.0.2" - } - }, - "karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", - "dev": true, - "requires": { - "source-map-support": "^0.5.5" - } - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "less": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz", - "integrity": "sha512-tlWX341RECuTOvoDIvtFqXsKj072hm3+9ymRBe76/mD6O5ZZecnlAOVDlWAleF2+aohFrxNidXhv2773f6kY7g==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0", - "tslib": "^1.10.0" - }, - "dependencies": { - "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, - "optional": true - } - } - }, - "less-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", - "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^4.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "requires": { - "leven": "^3.1.0" - } - }, - "license-webpack-plugin": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.4.tgz", - "integrity": "sha512-1Xq72fmPbTg5KofXs+yI5L4QqPFjQ6mZxoeI6D7gfiEDOtaEIk6PGrdLaej90bpDqKNHNxlQ/MW4tMAL6xMPJQ==", - "dev": true, - "requires": { - "@types/webpack-sources": "^0.1.5", - "webpack-sources": "^1.2.0" - } - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "log4js": { - "version": "0.6.38", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", - "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", - "dev": true, - "requires": { - "readable-stream": "~1.0.2", - "semver": "~4.3.3" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "make-fetch-happen": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.4.tgz", - "integrity": "sha512-hIFoqGq1db0QMiy/Atr/pI1Rs4rDV+ZdGSey2SQyF3KK3u1z4aj9mS5UdNnZkdQpA+H3pGn0J3KlEwsi2x4EqA==", - "dev": true, - "requires": { - "agentkeepalive": "^4.1.0", - "cacache": "^15.0.0", - "http-cache-semantics": "^4.0.4", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.1.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^5.0.0", - "ssri": "^8.0.0" - }, - "dependencies": { - "cacache": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", - "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", - "dev": true, - "requires": { - "chownr": "^1.1.2", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "unique-filename": "^1.1.1" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - } - } - }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "matcher": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz", - "integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==", - "dev": true, - "optional": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "optional": true - } - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "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 - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" }, "dependencies": { "braces": { @@ -8787,6 +6135,27 @@ } } }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -8799,6 +6168,1697 @@ } } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "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==", + "dev": true + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arguments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "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==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-retina": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz", + "integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "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==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.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==", + "dev": true + }, + "make-dir": { + "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==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "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 + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.2.tgz", + "integrity": "sha512-6gP1LbVgJ+d7PKksQBc2H0oDGNRQI3gKUsWlswKaQ2fif9X5gzhQcgM5+kiJGCQVurOG09jqNhk7payggyp5+g==", + "dev": true, + "requires": { + "colors": "1.4.0" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "jest-worker": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", + "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.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==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", + "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "karma": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/karma/-/karma-5.0.9.tgz", + "integrity": "sha512-dUA5z7Lo7G4FRSe1ZAXqOINEEWxmCjDBbfRBmU/wYlSMwxUQJP/tEEP90yJt3Uqo03s9rCgVnxtlfq+uDhxSPg==", + "dev": true, + "requires": { + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.0.0", + "colors": "^1.4.0", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "flatted": "^2.0.2", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.6", + "lodash": "^4.17.15", + "log4js": "^6.2.1", + "mime": "^2.4.5", + "minimatch": "^3.0.4", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^2.3.0", + "source-map": "^0.6.1", + "tmp": "0.2.1", + "ua-parser-js": "0.7.21", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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==", + "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 + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "karma-cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/karma-cli/-/karma-cli-1.0.1.tgz", + "integrity": "sha1-rmw8WKMTodALRRZMRVubhs4X+WA=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", + "integrity": "sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==", + "dev": true, + "requires": { + "jasmine-core": "^3.6.0" + }, + "dependencies": { + "jasmine-core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", + "dev": true + } + } + }, + "karma-jasmine-html-reporter": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.4.tgz", + "integrity": "sha512-PtilRLno5O6wH3lDihRnz0Ba8oSn0YUJqKjjux1peoYGwo0AQqrWRbdWk/RLzcGlb+onTyXAnHl6M+Hu3UxG/Q==", + "dev": true + }, + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "dev": true + }, + "less": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/less/-/less-3.12.2.tgz", + "integrity": "sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q==", + "dev": true, + "requires": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "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, + "optional": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "less-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-7.0.2.tgz", + "integrity": "sha512-7MKlgjnkCf63E3Lv6w2FvAEgLMx3d/tNBExITcanAq7ys5U8VPWT3F6xcRjYmdNfkoQ9udoVFb1r2azSiTnD6w==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "license-webpack-plugin": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.3.1.tgz", + "integrity": "sha512-yhqTmlYIEpZWA122lf6E0G8+rkn0AzoQ1OpzUKKs/lXUqG1plmGnwmkuuPlfggzJR5y6DLOdot/Tv00CC51CeQ==", + "dev": true, + "requires": { + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" + }, + "dependencies": { + "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 + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log4js": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "dev": true, + "requires": { + "date-format": "^3.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.1", + "rfdc": "^1.1.4", + "streamroller": "^2.2.4" + } + }, + "loglevel": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "make-fetch-happen": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matcher": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz", + "integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==", + "dev": true, + "optional": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "optional": true + } + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -8807,6 +7867,14 @@ "requires": { "bn.js": "^4.0.0", "brorand": "^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==", + "dev": true + } } }, "mime": { @@ -8816,18 +7884,18 @@ "dev": true }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "dev": true }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "mimic-fn": { @@ -8843,58 +7911,41 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", - "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.2.1.tgz", + "integrity": "sha512-G3yw7/TQaPfkuiR73MDcyiqhyP8SnbmLhUbpC76H+wtQxA6wfKhMCQOCb6wnPK0dQbjORAeOILQqEesg4/wF7A==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", "webpack-sources": "^1.1.0" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "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 + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" } } } @@ -8925,9 +7976,9 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", "dev": true, "requires": { "yallist": "^4.0.0" @@ -8942,19 +7993,6 @@ "minipass": "^3.0.0" } }, - "minipass-fetch": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.2.1.tgz", - "integrity": "sha512-ssHt0dkljEDaKmTgQ04DQgx2ag6G2gMPxA5hpcsoeTbfDgRf2fC2gNSRc6kISjD7ckCpHwwQvXxuTBK8402fXg==", - "dev": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-pipeline": "^1.2.2", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, "minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -8964,38 +8002,19 @@ "minipass": "^3.0.0" } }, - "minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, "minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "requires": { "minipass": "^3.0.0" } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -9140,6 +8159,13 @@ "to-regex": "^3.0.1" } }, + "native-request": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.8.tgz", + "integrity": "sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag==", + "dev": true, + "optional": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -9152,6 +8178,12 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, "ng-lazyload-image": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-7.1.0.tgz", @@ -9171,21 +8203,27 @@ "resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-9.0.1.tgz", "integrity": "sha512-xtUUjGMr9c8wwSfA4Cyy0iZMPLnBOg9i32A3tHOPfEivRrn9evULvxriCM45Qz6HpuuqA7vZGxGZZTCUIj/h3A==" }, - "ngx-videogular": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ngx-videogular/-/ngx-videogular-9.0.1.tgz", - "integrity": "sha512-LqaDs1b55mS6+5d2ns5b8dURpepd1B85jEVjisCMBKo+U3SZvLN1vvTQl6g9QcdZfS64vBAud76nIKwhV77Gsg==" - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch-npm": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", + "integrity": "sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-libs-browser": { @@ -9228,9 +8266,9 @@ } }, "node-releases": { - "version": "1.1.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", - "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==", + "version": "1.1.67", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", + "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==", "dev": true }, "normalize-package-data": { @@ -9310,10 +8348,13 @@ }, "dependencies": { "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -9324,56 +8365,42 @@ "dev": true }, "npm-package-arg": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.0.1.tgz", - "integrity": "sha512-/h5Fm6a/exByzFSTm7jAyHbgOqErl9qSNJDQF32Si/ZzgwT2TERVxRxn3Jurw1wflgyVVAxnFR4fRHPM7y1ClQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.0.tgz", + "integrity": "sha512-/ep6QDxBkm9HvOhOg0heitSd7JHA1U7y1qhhlRlteYYAi9Pdb/ZV7FW5aHpkrpM8+P+4p/jjR8zCyKPBMBjSig==", "dev": true, "requires": { - "hosted-git-info": "^3.0.2", + "hosted-git-info": "^3.0.6", "semver": "^7.0.0", "validate-npm-package-name": "^3.0.0" }, "dependencies": { "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true - } - } - }, - "npm-packlist": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.1.1.tgz", - "integrity": "sha512-95TSDvGwujIhqfSpIiRRLodEF+y6mJMopuZdahoGzqtRDFZXGav46S0p6ngeWaiAkb5R72w6eVARhzej0HvZeQ==", - "dev": true, - "requires": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "lru-cache": "^6.0.0" } } } }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "npm-pick-manifest": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.0.0.tgz", - "integrity": "sha512-PdJpXMvjqt4nftNEDpCgjBUF8yI3Q3MyuAmVB9nemnnCg32F4BPL/JFBfdj8DubgHCYUFQhtLWmBPvdsFtjWMg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.0.tgz", + "integrity": "sha512-ygs4k6f54ZxJXrzT0x34NybRlLeZ4+6nECAIbr2i0foTnijtS1TJiyzpqtuUAJOps/hO0tNDr8fRV5g+BtRlTw==", "dev": true, "requires": { "npm-install-checks": "^4.0.0", @@ -9382,27 +8409,70 @@ }, "dependencies": { "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, "npm-registry-fetch": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-8.0.0.tgz", - "integrity": "sha512-975WwLvZjX97y9UWWQ8nAyr7bw02s9xKPHqvEm5T900LQsB1HXb8Gb9ebYtCBLSX+K8gSOrO5KS/9yV/naLZmQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz", + "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==", "dev": true, "requires": { - "@npmcli/ci-detect": "^1.0.0", + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", "lru-cache": "^5.1.1", - "make-fetch-happen": "^8.0.2", - "minipass": "^3.0.0", - "minipass-fetch": "^1.1.2", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0" + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "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==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "npm-run-path": { @@ -9423,24 +8493,12 @@ "boolbase": "~1.0.0" } }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -9491,16 +8549,20 @@ } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", "dev": true }, "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } }, "object-keys": { "version": "1.1.1", @@ -9518,35 +8580,26 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", + "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "es-abstract": "^1.18.0-next.1" } }, "object.pick": { @@ -9559,14 +8612,14 @@ } }, "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" } }, @@ -9600,18 +8653,18 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, "open": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz", - "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", "dev": true, "requires": { "is-docker": "^2.0.0", @@ -9635,60 +8688,35 @@ } } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - } - } - }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", - "dev": true - }, "ora": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.3.tgz", - "integrity": "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz", + "integrity": "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==", "dev": true, "requires": { - "chalk": "^3.0.0", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", + "cli-spinners": "^2.4.0", "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", + "log-symbols": "^4.0.0", "mute-stream": "0.0.8", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -9717,9 +8745,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -9742,16 +8770,11 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true }, "os-tmpdir": { "version": "1.0.2", @@ -9759,50 +8782,50 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.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==", "dev": true }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "^2.0.0" } }, "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" @@ -9820,62 +8843,83 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "pacote": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.1.4.tgz", - "integrity": "sha512-eUGJvSSpWFZKn3z8gig/HgnBmUl6gIWByIIaHzSyEr3tOWX0w8tFEADXtpu8HGv5E0ShCeTP6enRq8iHKCHSvw==", + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", + "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "dev": true, "requires": { - "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.5", - "@npmcli/promise-spawn": "^1.1.0", - "cacache": "^15.0.0", - "chownr": "^1.1.4", - "fs-minipass": "^2.1.0", + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "minipass": "^3.0.1", - "minipass-fetch": "^1.2.1", - "mkdirp": "^1.0.3", - "npm-package-arg": "^8.0.1", - "npm-packlist": "^2.1.0", - "npm-pick-manifest": "^6.0.0", - "npm-registry-fetch": "^8.0.0", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", "promise-inflight": "^1.0.1", "promise-retry": "^1.1.1", - "read-package-json-fast": "^1.1.3", - "rimraf": "^2.7.1", - "semver": "^7.1.3", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "which": "^2.0.2" + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" }, "dependencies": { "cacache": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", - "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", "dev": true, "requires": { - "chownr": "^1.1.2", - "fs-minipass": "^2.0.0", + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", "glob": "^7.1.4", - "infer-owner": "^1.0.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^8.0.0", - "tar": "^6.0.1", - "unique-filename": "^1.1.1" + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" } }, "glob": { @@ -9892,12 +8936,63 @@ "path-is-absolute": "^1.0.0" } }, - "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "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==", "dev": true }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -9907,29 +9002,41 @@ "glob": "^7.1.3" } }, - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -9950,49 +9057,36 @@ "readable-stream": "^2.1.5" } }, - "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + } + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" } }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -10009,13 +9103,39 @@ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "optional": true }, - "parsejson": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", - "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", "dev": true, "requires": { - "better-assert": "~1.0.0" + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } + }, + "parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } } }, "parseqs": { @@ -10061,9 +9181,10 @@ "dev": true }, "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==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -10094,26 +9215,15 @@ "dev": true }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -10169,123 +9279,45 @@ "dev": true, "requires": { "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } } }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", "dev": true, "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } + "ts-pnp": "^1.1.6" } }, "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.5" }, "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==", "dev": true, "requires": { "ms": "^2.1.1" } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } } } }, @@ -10296,9 +9328,9 @@ "dev": true }, "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10324,9 +9356,9 @@ } }, "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", "dev": true, "requires": { "postcss": "^7.0.27", @@ -10429,57 +9461,78 @@ } } }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - } - }, "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.0.4.tgz", + "integrity": "sha512-pntA9zIR14drQo84yGTjQJg1m7T0DkXR4vXYHBngiRZdJtEeCrojL6lOpqUanMzG375lIJbT4Yug85zC/AJWGw==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" + "cosmiconfig": "^7.0.0", + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { - "minimist": "^1.2.0" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" } }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "import-fresh": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" } } } @@ -10616,6 +9669,47 @@ } } }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, "postcss-normalize-charset": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", @@ -10831,14 +9925,15 @@ } }, "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", "dev": true, "requires": { "cssesc": "^3.0.0", "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" } }, "postcss-svgo": { @@ -10873,27 +9968,9 @@ } }, "postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, "process": { @@ -10914,16 +9991,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "requires": { - "asap": "~2.0.3" - } - }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -10955,35 +10022,38 @@ "dev": true, "optional": true }, - "protractor": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.1.2.tgz", - "integrity": "sha1-myIXQXCaTGLVzVPGqt1UpxE36V8=", + "protoduck": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "protractor": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", + "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", "dev": true, "requires": { - "@types/node": "^6.0.46", "@types/q": "^0.0.32", - "@types/selenium-webdriver": "~2.53.39", - "blocking-proxy": "0.0.5", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", "chalk": "^1.1.3", "glob": "^7.0.3", - "jasmine": "^2.5.3", + "jasmine": "2.8.0", "jasminewd2": "^2.1.0", - "optimist": "~0.6.0", "q": "1.4.1", - "saucelabs": "~1.3.0", - "selenium-webdriver": "3.0.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", "source-map-support": "~0.4.0", - "webdriver-js-extender": "^1.0.0", - "webdriver-manager": "^12.0.6" + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.1.7", + "yargs": "^15.3.1" }, "dependencies": { - "@types/node": { - "version": "6.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.9.tgz", - "integrity": "sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w==", - "dev": true - }, "@types/q": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", @@ -11002,6 +10072,21 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -11015,6 +10100,49 @@ "supports-color": "^2.0.0" } }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.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==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", @@ -11030,6 +10158,16 @@ "rimraf": "^2.2.8" } }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "globby": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", @@ -11068,6 +10206,30 @@ "path-is-inside": "^1.0.1" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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==", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -11153,6 +10315,78 @@ "semver": "^5.3.0", "xml2js": "^0.4.17" } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "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==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -11172,12 +10406,6 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -11196,6 +10424,14 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "pump": { @@ -11249,21 +10485,11 @@ "dev": true }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "dev": true }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -11277,30 +10503,11 @@ "dev": true }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11347,33 +10554,24 @@ } }, "raw-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz", - "integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", "dev": true, "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.5.0" + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } @@ -11395,40 +10593,6 @@ } } }, - "read-package-json": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", - "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", - "dev": true, - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "read-package-json-fast": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-1.1.3.tgz", - "integrity": "sha512-MmFqiyfCXV2Dmm4jH24DEGhxdkUDFivJQj4oPZQPOKywxR7HWBE6WnMWDAapfFHi3wm1b+mhR+XHlUH0CL8axg==", - "dev": true, - "requires": { - "json-parse-even-better-errors": "^2.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "read-package-tree": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", - "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", - "dev": true, - "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -11444,25 +10608,13 @@ "util-deprecate": "~1.0.1" } }, - "readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, "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==", "dev": true, "requires": { - "picomatch": "^2.0.7" + "picomatch": "^2.2.1" } }, "reflect-metadata": { @@ -11487,28 +10639,18 @@ } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" + "@babel/runtime": "^7.8.4" } }, "regex-not": { @@ -11521,6 +10663,12 @@ "safe-regex": "^1.1.0" } }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, "regexp.prototype.flags": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", @@ -11529,20 +10677,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" - } - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "regjsgen": { @@ -11586,15 +10741,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -11621,6 +10767,14 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } } }, "require-directory": { @@ -11631,7 +10785,8 @@ "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "requires-port": { "version": "1.0.0", @@ -11668,6 +10823,84 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "resolve-url-loader": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", + "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", + "dev": true, + "requires": { + "adjust-sourcemap-loader": "3.0.0", + "camelcase": "5.3.1", + "compose-function": "3.0.3", + "convert-source-map": "1.7.0", + "es6-iterator": "2.0.3", + "loader-utils": "1.2.3", + "postcss": "7.0.21", + "rework": "1.0.1", + "rework-visit": "1.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "postcss": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", + "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "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 + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -11699,6 +10932,42 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rework": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", + "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", + "dev": true, + "requires": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + } + } + }, + "rework-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", + "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=", + "dev": true + }, + "rfdc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", + "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "dev": true + }, "rgb-regex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", @@ -11771,22 +11040,25 @@ } }, "rollup": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.1.0.tgz", - "integrity": "sha512-gfE1455AEazVVTJoeQtcOq/U6GSxwoj4XPSWVsuWmgIxj7sBQNLDOSA82PbdMe+cP8ql8fR1jogPFe8Wg8g4SQ==", + "version": "2.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz", + "integrity": "sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw==", "dev": true, "requires": { "fsevents": "~2.1.2" } }, "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true }, "run-queue": { "version": "1.0.3", @@ -11798,11 +11070,18 @@ } }, "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "requires": { "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } } }, "rxjs-compat": { @@ -11840,106 +11119,62 @@ } }, "sass": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.3.tgz", - "integrity": "sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.27.0.tgz", + "integrity": "sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==", "dev": true, "requires": { "chokidar": ">=2.0.0 <4.0.0" } }, "sass-loader": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", - "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.5.tgz", + "integrity": "sha512-2LqoNPtKkZq/XbXNQ4C64GFEleSEHKv6NPSI+bMC/l+jpEXGJhiRYkAQToO24MR7NU4JRY2RpLpJ/gjo2Uf13w==", "dev": true, "requires": { - "clone-deep": "^4.0.1", - "loader-utils": "^1.2.3", - "neo-async": "^2.6.1", - "schema-utils": "^2.6.1", - "semver": "^6.3.0" + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, "saucelabs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.3.0.tgz", - "integrity": "sha1-0kDoAJ33+ocwbsRXimm6O1xCT+4=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", "dev": true, "requires": { - "https-proxy-agent": "^1.0.0" - }, - "dependencies": { - "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", - "dev": true, - "requires": { - "extend": "~3.0.0", - "semver": "~5.0.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", - "dev": true, - "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } + "https-proxy-agent": "^2.2.1" } }, "sax": { @@ -11949,13 +11184,14 @@ "dev": true }, "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "select-hose": { @@ -11965,12 +11201,12 @@ "dev": true }, "selenium-webdriver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz", - "integrity": "sha1-ot6l2kqX9mcuiefKcnbO+jZRR6c=", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", "dev": true, "requires": { - "adm-zip": "^0.4.7", + "jszip": "^3.1.3", "rimraf": "^2.5.4", "tmp": "0.0.30", "xml2js": "^0.4.17" @@ -12011,12 +11247,12 @@ } }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.9.0" + "node-forge": "^0.10.0" } }, "semver": { @@ -12115,10 +11351,13 @@ } }, "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } }, "serve-index": { "version": "1.9.1", @@ -12191,7 +11430,14 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true }, "set-value": { "version": "2.0.1", @@ -12286,9 +11532,9 @@ } }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "smart-buffer": { @@ -12426,88 +11672,111 @@ } }, "socket.io": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", - "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", "dev": true, "requires": { - "debug": "2.3.3", - "engine.io": "1.8.3", - "has-binary": "0.1.7", - "object-assign": "4.1.0", - "socket.io-adapter": "0.5.0", - "socket.io-client": "1.7.3", - "socket.io-parser": "2.3.1" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - }, - "object-assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", - "dev": true - } + "debug": "~4.1.0", + "engine.io": "~3.4.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" } }, "socket.io-adapter": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", - "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", - "dev": true, - "requires": { - "debug": "2.3.3", - "socket.io-parser": "2.3.1" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "dev": true }, "socket.io-client": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", - "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=", + "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==", "dev": true, "requires": { "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "2.3.3", - "engine.io-client": "1.8.3", - "has-binary": "0.1.7", + "debug": "~4.1.0", + "engine.io-client": "~3.4.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", - "socket.io-parser": "2.3.1", + "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 + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "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==", + "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" + } + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, "dependencies": { "component-emitter": { "version": "1.2.1", @@ -12515,78 +11784,23 @@ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } - }, - "socket.io-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", - "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", - "dev": true, - "requires": { - "component-emitter": "1.1.2", - "debug": "2.2.0", - "isarray": "0.0.1", - "json3": "3.3.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", - "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", - "dev": true - }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - } - }, "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", "dev": true } } }, "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", "dev": true, "requires": { "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" } }, "sockjs-client": { @@ -12604,9 +11818,9 @@ }, "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==", "dev": true, "requires": { "ms": "^2.1.1" @@ -12634,23 +11848,24 @@ } }, "socks-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", - "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "dev": true, "requires": { - "agent-base": "6", - "debug": "4", - "socks": "^2.3.3" - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } } }, "source-list-map": { @@ -12665,34 +11880,44 @@ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" }, "source-map-loader": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", - "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.2.tgz", + "integrity": "sha512-bjf6eSENOYBX4JZDfl9vVLNsGAQ6Uz90fLmOazcmMcyDYOBFsGxPNn83jXezWLY9bJsVAo1ObztxPcV8HAbjVA==", "dev": true, "requires": { - "async": "^2.5.0", - "loader-utils": "^1.1.0" + "abab": "^2.0.5", + "iconv-lite": "^0.6.2", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "source-map": "^0.6.1", + "whatwg-mimetype": "^2.3.0" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", "dev": true, "requires": { - "minimist": "^1.2.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } + }, + "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 } } }, @@ -12710,9 +11935,9 @@ } }, "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -12739,9 +11964,9 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -12749,15 +11974,15 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -12765,15 +11990,15 @@ } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", "dev": true }, "spdy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", - "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "requires": { "debug": "^4.1.0", @@ -12811,9 +12036,9 @@ } }, "speed-measure-webpack-plugin": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz", - "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.3.tgz", + "integrity": "sha512-2ljD4Ch/rz2zG3HsLsnPfp23osuPBS0qPuz9sGpkNXTN1Ic4M+W9xB8l8rS8ob2cO4b1L+WTJw/0AJwWYVgcxQ==", "dev": true, "requires": { "chalk": "^2.0.1" @@ -12932,11 +12157,35 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true + "streamroller": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "dev": true, + "requires": { + "date-format": "^2.1.0", + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } }, "string-width": { "version": "4.2.0", @@ -12949,45 +12198,23 @@ } }, "string.prototype.trimend": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", - "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" } }, "string.prototype.trimstart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", - "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" } }, "string_decoder": { @@ -13007,15 +12234,6 @@ "ansi-regex": "^5.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -13029,33 +12247,24 @@ "dev": true }, "style-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", - "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", "dev": true, "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.6.4" + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } @@ -13085,18 +12294,18 @@ } }, "stylus": { - "version": "0.54.7", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.7.tgz", - "integrity": "sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug==", + "version": "0.54.8", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", + "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", "dev": true, "requires": { "css-parse": "~2.0.0", "debug": "~3.1.0", - "glob": "^7.1.3", - "mkdirp": "~0.5.x", + "glob": "^7.1.6", + "mkdirp": "~1.0.4", "safer-buffer": "^2.1.2", "sax": "~1.2.4", - "semver": "^6.0.0", + "semver": "^6.3.0", "source-map": "^0.7.3" }, "dependencies": { @@ -13123,6 +12332,12 @@ "path-is-absolute": "^1.0.0" } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -13138,34 +12353,27 @@ } }, "stylus-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", - "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-4.3.1.tgz", + "integrity": "sha512-apDYJEM5ZpOAWbWInWcsbtI8gHNr/XYVcSY/tWqOUPt7M5tqhtwXVsAkgyiVjhuvw2Yrjq474a9H+g4d047Ebw==", "dev": true, "requires": { - "loader-utils": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "when": "~3.6.x" + "fast-glob": "^3.2.4", + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^3.0.0" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } @@ -13208,117 +12416,86 @@ "util.promisify": "~1.0.0" } }, + "symbol-observable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", + "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", + "dev": true + }, "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", "dev": true }, "tar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", - "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", "dev": true, "requires": { - "chownr": "^1.1.3", + "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", + "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, "dependencies": { "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true } } }, "terser": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", - "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.7.tgz", + "integrity": "sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w==", "dev": true, "requires": { "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "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 - } + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" } }, "terser-webpack-plugin": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", - "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", "dev": true, "requires": { - "cacache": "^13.0.1", - "find-cache-dir": "^3.2.0", - "jest-worker": "^25.1.0", - "p-limit": "^2.2.2", - "schema-utils": "^2.6.4", - "serialize-javascript": "^2.1.2", + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.5.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", "source-map": "^0.6.1", - "terser": "^4.4.3", + "terser": "^5.3.4", "webpack-sources": "^1.4.3" }, "dependencies": { - "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", - "unique-filename": "^1.1.1" + "yocto-queue": "^0.1.0" } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, "source-map": { @@ -13327,18 +12504,24 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "minipass": "^3.1.1" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" } } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -13362,9 +12545,9 @@ "dev": true }, "timers-browserify": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", - "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" @@ -13471,12 +12654,6 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, "truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -13566,6 +12743,12 @@ } } }, + "ts-pnp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", + "dev": true + }, "tsconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", @@ -13585,25 +12768,43 @@ } }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" }, "tslint": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.3.2.tgz", - "integrity": "sha1-5WRZ+wlacwfxA7hAUhdPXju+9u0=", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "colors": "^1.1.2", - "diff": "^3.2.0", + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", "glob": "^7.1.1", - "optimist": "~0.6.0", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.6.0", - "tsutils": "^2.0.0" + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "tsutils": { @@ -13613,6 +12814,14 @@ "dev": true, "requires": { "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "tty-browserify": { @@ -13643,6 +12852,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, "type-fest": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", @@ -13666,34 +12881,14 @@ "dev": true }, "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==" + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==" }, - "uglify-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", - "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "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, - "optional": true - } - } - }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "ua-parser-js": { + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", "dev": true }, "unicode-canonical-property-names-ecmascript": { @@ -13767,25 +12962,14 @@ } }, "universal-analytics": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", - "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.23.tgz", + "integrity": "sha512-lgMIH7XBI6OgYn1woDEmxhGdj8yDefMKg7GkWdeATAlQZFrMrNyxSkpDzY57iY0/6fdlzTbBV03OawvvzG+q7A==", "dev": true, "requires": { - "debug": "^3.0.0", - "request": "^2.88.0", + "debug": "^4.1.1", + "request": "^2.88.2", "uuid": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } } }, "universalify": { @@ -13853,9 +13037,9 @@ "dev": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" } @@ -13923,34 +13107,6 @@ "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", "dev": true }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - }, - "dependencies": { - "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==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, "utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", @@ -13980,15 +13136,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "util-promisify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", - "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, "util.promisify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", @@ -13999,6 +13146,27 @@ "es-abstract": "^1.17.2", "has-symbols": "^1.0.1", "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "utils-merge": { @@ -14077,14 +13245,25 @@ "dev": true }, "watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dev": true, "requires": { - "chokidar": "^2.1.8", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" }, "dependencies": { "anymatch": { @@ -14092,6 +13271,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, + "optional": true, "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" @@ -14102,6 +13282,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, + "optional": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -14112,13 +13293,15 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -14130,6 +13313,18 @@ "snapdragon-node": "^2.0.1", "split-string": "^3.0.2", "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "chokidar": { @@ -14137,6 +13332,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, + "optional": true, "requires": { "anymatch": "^2.0.0", "async-each": "^1.0.1", @@ -14152,575 +13348,61 @@ "upath": "^1.1.1" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "is-extglob": "^2.1.0" } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "optional": true } } }, @@ -14729,6 +13411,7 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, + "optional": true, "requires": { "binary-extensions": "^1.0.0" } @@ -14738,17 +13421,43 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, + "optional": true, "requires": { - "is-buffer": "^1.1.5" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "readdirp": { @@ -14756,6 +13465,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", @@ -14767,6 +13477,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -14798,122 +13509,75 @@ "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==" }, "webdriver-js-extender": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-1.0.0.tgz", - "integrity": "sha1-gcUzqeM9W/tZe05j4s2yW1R3dRU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", "dev": true, "requires": { - "@types/selenium-webdriver": "^2.53.35", - "selenium-webdriver": "^2.53.2" - }, - "dependencies": { - "adm-zip": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", - "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "sax": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", - "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=", - "dev": true - }, - "selenium-webdriver": { - "version": "2.53.3", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz", - "integrity": "sha1-0p/1qVff8aG0ncRXdW5OS/vc4IU=", - "dev": true, - "requires": { - "adm-zip": "0.4.4", - "rimraf": "^2.2.8", - "tmp": "0.0.24", - "ws": "^1.0.1", - "xml2js": "0.4.4" - } - }, - "tmp": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", - "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=", - "dev": true - }, - "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", - "dev": true, - "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" - } - }, - "xml2js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.4.tgz", - "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", - "dev": true, - "requires": { - "sax": "0.6.x", - "xmlbuilder": ">=1.0.0" - } - } + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" } }, "webpack": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", - "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/wasm-edit": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.1", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", + "enhanced-resolve": "^4.3.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", "loader-utils": "^1.2.3", "memory-fs": "^0.4.1", "micromatch": "^3.1.10", - "mkdirp": "^0.5.1", + "mkdirp": "^0.5.3", "neo-async": "^2.6.1", "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", + "watchpack": "^1.7.4", "webpack-sources": "^1.4.1" }, "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -14937,6 +13601,58 @@ "y18n": "^4.0.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -14962,6 +13678,26 @@ "path-is-absolute": "^1.0.0" } }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -14988,14 +13724,34 @@ "json5": "^1.0.1" } }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "yallist": "^3.0.2" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "rimraf": { @@ -15018,6 +13774,15 @@ "ajv-keywords": "^3.1.0" } }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -15033,22 +13798,71 @@ "figgy-pudding": "^3.5.1" } }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -15065,28 +13879,18 @@ "webpack-log": "^2.0.0" }, "dependencies": { - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", "dev": true } } }, "webpack-dev-server": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", - "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", "dev": true, "requires": { "ansi-html": "0.0.7", @@ -15097,31 +13901,31 @@ "debug": "^4.1.1", "del": "^4.1.1", "express": "^4.17.1", - "html-entities": "^1.2.1", + "html-entities": "^1.3.1", "http-proxy-middleware": "0.19.1", "import-local": "^2.0.0", "internal-ip": "^4.3.0", "ip": "^1.1.5", "is-absolute-url": "^3.0.3", "killable": "^1.0.1", - "loglevel": "^1.6.6", + "loglevel": "^1.6.8", "opn": "^5.5.0", "p-retry": "^3.0.1", - "portfinder": "^1.0.25", + "portfinder": "^1.0.26", "schema-utils": "^1.0.0", "selfsigned": "^1.10.7", "semver": "^6.3.0", "serve-index": "^1.9.1", - "sockjs": "0.3.19", + "sockjs": "0.3.20", "sockjs-client": "1.4.0", - "spdy": "^4.0.1", + "spdy": "^4.0.2", "strip-ansi": "^3.0.1", "supports-color": "^6.1.0", "url": "^0.11.0", "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", "ws": "^6.2.1", - "yargs": "12.0.5" + "yargs": "^13.3.2" }, "dependencies": { "ansi-regex": { @@ -15173,8 +13977,25 @@ "snapdragon-node": "^2.0.1", "split-string": "^3.0.2", "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -15196,41 +14017,38 @@ } }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "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==", "dev": true }, "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==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true }, "fill-range": { "version": "4.0.0", @@ -15242,573 +14060,49 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" }, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { + "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "is-extendable": "^0.1.0" } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "optional": true } } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } }, "is-absolute-url": { "version": "3.0.3", @@ -15838,42 +14132,40 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -15885,12 +14177,6 @@ "readable-stream": "^2.0.2" } }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -15909,28 +14195,29 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { + "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "strip-ansi": "^5.1.0" }, "dependencies": { "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==", "dev": true }, "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==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -15964,61 +14251,61 @@ } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^4.1.0" } } } }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" } }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -16035,25 +14322,34 @@ "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + } } }, "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.2.0.tgz", + "integrity": "sha512-QBglJBg5+lItm3/Lopv8KDDK01+hjdg2azEwi/4vKJ8ZmGPdtJsTpjtNNOW3a4WiqzXdCATtTudOZJngE7RKkA==", "dev": true, "requires": { - "lodash": "^4.17.15" + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" } }, "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.0.1.tgz", + "integrity": "sha512-A9oYz7ANQBK5EN19rUXbvNgfdfZf5U2gP0769OXsj9CvYkCR6OHOsd6OKyEy4H38GGxpsQPKIL83NC64QY6Xmw==", "dev": true, "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -16065,35 +14361,51 @@ } }, "webpack-subresource-integrity": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.4.0.tgz", - "integrity": "sha512-GB1kB/LwAWC3CxwcedGhMkxGpNZxSheCe1q+KJP1bakuieAdX/rGHEcf5zsEzhKXpqsGqokgsDoD9dIkr61VDQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.5.1.tgz", + "integrity": "sha512-uekbQ93PZ9e7BFB8Hl9cFIVYQyQqiXp2ExKk9Zv+qZfH/zHXHrCFAfw1VW0+NqWbTWrs/HnuDrto3+tiPXh//Q==", "dev": true, "requires": { "webpack-sources": "^1.3.0" + }, + "dependencies": { + "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 + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } } }, "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", "dev": true, "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", - "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "when": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", - "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", "dev": true }, "which": { @@ -16108,12 +14420,13 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, "worker-farm": { @@ -16126,9 +14439,9 @@ } }, "worker-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-4.0.2.tgz", - "integrity": "sha512-V+1zSZMOOKk+uBzKyNIODLQLsx59zSIOaI75J1EMS0iR1qy+KQR3y/pQ3T0vIhvPfDFapGRMsoMvQNEL3okqSA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-5.0.0.tgz", + "integrity": "sha512-AXMUstURCxDD6yGam2r4E34aJg6kW85IiaeX72hi+I1cxyaMUtrvVY6sbfpGKAj5e7f68Acl62BjQF5aOOx2IQ==", "dev": true, "requires": { "loader-utils": "^1.1.0" @@ -16157,9 +14470,9 @@ } }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "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", @@ -16167,11 +14480,10 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -16204,12 +14516,6 @@ "async-limiter": "~1.0.0" } }, - "wtf-8": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", - "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", - "dev": true - }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", @@ -16227,9 +14533,9 @@ "dev": true }, "xmlhttprequest-ssl": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", - "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", "dev": true }, "xtend": { @@ -16239,9 +14545,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" }, "yallist": { "version": "4.0.0", @@ -16249,32 +14555,30 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, "yargs": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz", - "integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.0" + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" }, "yauzl": { "version": "2.10.0", @@ -16298,6 +14602,12 @@ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", "dev": true }, + "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==", + "dev": true + }, "zone.js": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz", diff --git a/package.json b/package.json index 14aa13e..e34f8a8 100644 --- a/package.json +++ b/package.json @@ -18,19 +18,20 @@ }, "private": true, "dependencies": { - "@angular-devkit/core": "^9.0.6", - "@angular/animations": "^9.1.0", - "@angular/cdk": "^9.2.0", - "@angular/common": "^9.1.0", - "@angular/compiler": "^9.1.0", - "@angular/core": "^9.0.7", - "@angular/forms": "^9.1.0", - "@angular/localize": "^9.1.0", - "@angular/material": "^9.2.0", - "@angular/platform-browser": "^9.1.0", - "@angular/platform-browser-dynamic": "^9.1.0", - "@angular/router": "^9.1.0", + "@angular-devkit/core": "^11.0.4", + "@angular/animations": "^11.0.4", + "@angular/cdk": "^11.0.2", + "@angular/common": "^11.0.4", + "@angular/compiler": "^11.0.4", + "@angular/core": "^11.0.4", + "@angular/forms": "^11.0.4", + "@angular/localize": "^11.0.4", + "@angular/material": "^11.0.2", + "@angular/platform-browser": "^11.0.4", + "@angular/platform-browser-dynamic": "^11.0.4", + "@angular/router": "^11.0.4", "@ngneat/content-loader": "^5.0.0", + "@videogular/ngx-videogular": "^2.1.0", "core-js": "^2.4.1", "file-saver": "^2.0.2", "filesize": "^6.1.0", @@ -39,35 +40,34 @@ "ng-lazyload-image": "^7.0.1", "ngx-avatar": "^4.0.0", "ngx-file-drop": "^9.0.1", - "ngx-videogular": "^9.0.1", - "rxjs": "^6.5.3", + "rxjs": "^6.6.3", "rxjs-compat": "^6.0.0-rc.0", - "tslib": "^1.10.0", - "typescript": "~3.7.5", + "tslib": "^2.0.0", + "typescript": "~4.0.5", "web-animations-js": "^2.3.2", "zone.js": "~0.10.2" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.901.0", - "@angular/cli": "^9.0.7", - "@angular/compiler-cli": "^9.0.7", - "@angular/language-service": "^9.0.7", + "@angular-devkit/build-angular": "^0.1100.4", + "@angular/cli": "^11.0.4", + "@angular/compiler-cli": "^11.0.4", + "@angular/language-service": "^11.0.4", "@types/core-js": "^2.5.2", "@types/file-saver": "^2.0.1", - "@types/jasmine": "2.5.45", + "@types/jasmine": "~3.6.0", "@types/node": "^12.11.1", - "codelyzer": "^5.1.2", + "codelyzer": "^6.0.0", "electron": "^8.0.1", - "jasmine-core": "~2.6.2", - "jasmine-spec-reporter": "~4.1.0", - "karma": "~1.7.0", - "karma-chrome-launcher": "~2.1.1", + "jasmine-core": "~3.6.0", + "jasmine-spec-reporter": "~5.0.0", + "karma": "~5.0.0", + "karma-chrome-launcher": "~3.1.0", "karma-cli": "~1.0.1", - "karma-coverage-istanbul-reporter": "^1.2.1", - "karma-jasmine": "~1.1.0", - "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.1.2", + "karma-coverage-istanbul-reporter": "~3.0.2", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "^1.5.0", + "protractor": "~7.0.0", "ts-node": "~3.0.4", - "tslint": "~5.3.2" + "tslint": "~6.1.0" } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5a18750..523a893 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -19,7 +19,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true })], + imports: [RouterModule.forRoot(routes, { useHash: true, relativeLinkResolution: 'legacy' })], exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 7d2799c..cfb2117 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,9 +1,9 @@ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, waitForAsync } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ AppComponent @@ -11,19 +11,19 @@ describe('AppComponent', () => { }).compileComponents(); })); - it('should create the app', async(() => { + it('should create the app', waitForAsync(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); - it(`should have as title 'app'`, async(() => { + it(`should have as title 'app'`, waitForAsync(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('app'); })); - it('should render title in a h1 tag', async(() => { + it('should render title in a h1 tag', waitForAsync(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 72c2658..056ee7b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,7 +39,10 @@ import { RouterModule } from '@angular/router'; import { AppRoutingModule } from './app-routing.module'; import { MainComponent } from './main/main.component'; import { PlayerComponent } from './player/player.component'; -import { VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule } from 'ngx-videogular'; +import { VgControlsModule } from '@videogular/ngx-videogular/controls'; +import { VgBufferingModule } from '@videogular/ngx-videogular/buffering'; +import { VgOverlayPlayModule } from '@videogular/ngx-videogular/overlay-play'; +import { VgCoreModule } from '@videogular/ngx-videogular/core'; import { InputDialogComponent } from './input-dialog/input-dialog.component'; import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image'; import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component'; diff --git a/src/app/components/custom-playlists/custom-playlists.component.spec.ts b/src/app/components/custom-playlists/custom-playlists.component.spec.ts index 5e9348b..fbc8b11 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.spec.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { CustomPlaylistsComponent } from './custom-playlists.component'; @@ -6,7 +6,7 @@ describe('CustomPlaylistsComponent', () => { let component: CustomPlaylistsComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ CustomPlaylistsComponent ] }) diff --git a/src/app/components/downloads/downloads.component.spec.ts b/src/app/components/downloads/downloads.component.spec.ts index e7a1fa6..6d1811a 100644 --- a/src/app/components/downloads/downloads.component.spec.ts +++ b/src/app/components/downloads/downloads.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { DownloadsComponent } from './downloads.component'; @@ -6,7 +6,7 @@ describe('DownloadsComponent', () => { let component: DownloadsComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ DownloadsComponent ] }) diff --git a/src/app/components/login/login.component.spec.ts b/src/app/components/login/login.component.spec.ts index d6d85a8..eb0c71a 100644 --- a/src/app/components/login/login.component.spec.ts +++ b/src/app/components/login/login.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { LoginComponent } from './login.component'; @@ -6,7 +6,7 @@ describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ LoginComponent ] }) diff --git a/src/app/components/logs-viewer/logs-viewer.component.spec.ts b/src/app/components/logs-viewer/logs-viewer.component.spec.ts index ad64462..7c271e3 100644 --- a/src/app/components/logs-viewer/logs-viewer.component.spec.ts +++ b/src/app/components/logs-viewer/logs-viewer.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { LogsViewerComponent } from './logs-viewer.component'; @@ -6,7 +6,7 @@ describe('LogsViewerComponent', () => { let component: LogsViewerComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ LogsViewerComponent ] }) diff --git a/src/app/components/manage-role/manage-role.component.spec.ts b/src/app/components/manage-role/manage-role.component.spec.ts index 2e9579e..14ff0a6 100644 --- a/src/app/components/manage-role/manage-role.component.spec.ts +++ b/src/app/components/manage-role/manage-role.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ManageRoleComponent } from './manage-role.component'; @@ -6,7 +6,7 @@ describe('ManageRoleComponent', () => { let component: ManageRoleComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ManageRoleComponent ] }) diff --git a/src/app/components/manage-user/manage-user.component.spec.ts b/src/app/components/manage-user/manage-user.component.spec.ts index f8fe3a7..5f65c64 100644 --- a/src/app/components/manage-user/manage-user.component.spec.ts +++ b/src/app/components/manage-user/manage-user.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ManageUserComponent } from './manage-user.component'; @@ -6,7 +6,7 @@ describe('ManageUserComponent', () => { let component: ManageUserComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ManageUserComponent ] }) diff --git a/src/app/components/modify-users/modify-users.component.spec.ts b/src/app/components/modify-users/modify-users.component.spec.ts index e5e8ef8..67bdc13 100644 --- a/src/app/components/modify-users/modify-users.component.spec.ts +++ b/src/app/components/modify-users/modify-users.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ModifyUsersComponent } from './modify-users.component'; @@ -6,7 +6,7 @@ describe('ModifyUsersComponent', () => { let component: ModifyUsersComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ModifyUsersComponent ] }) diff --git a/src/app/components/recent-videos/recent-videos.component.spec.ts b/src/app/components/recent-videos/recent-videos.component.spec.ts index 4869c76..6031dbe 100644 --- a/src/app/components/recent-videos/recent-videos.component.spec.ts +++ b/src/app/components/recent-videos/recent-videos.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { RecentVideosComponent } from './recent-videos.component'; @@ -6,7 +6,7 @@ describe('RecentVideosComponent', () => { let component: RecentVideosComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ RecentVideosComponent ] }) diff --git a/src/app/components/twitch-chat/twitch-chat.component.spec.ts b/src/app/components/twitch-chat/twitch-chat.component.spec.ts index eafdece..f860811 100644 --- a/src/app/components/twitch-chat/twitch-chat.component.spec.ts +++ b/src/app/components/twitch-chat/twitch-chat.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { TwitchChatComponent } from './twitch-chat.component'; @@ -6,7 +6,7 @@ describe('TwitchChatComponent', () => { let component: TwitchChatComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ TwitchChatComponent ] }) diff --git a/src/app/components/unified-file-card/unified-file-card.component.spec.ts b/src/app/components/unified-file-card/unified-file-card.component.spec.ts index 4482471..fb96080 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.spec.ts +++ b/src/app/components/unified-file-card/unified-file-card.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { UnifiedFileCardComponent } from './unified-file-card.component'; @@ -6,7 +6,7 @@ describe('UnifiedFileCardComponent', () => { let component: UnifiedFileCardComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ UnifiedFileCardComponent ] }) diff --git a/src/app/create-playlist/create-playlist.component.spec.ts b/src/app/create-playlist/create-playlist.component.spec.ts index 862e64b..3378cba 100644 --- a/src/app/create-playlist/create-playlist.component.spec.ts +++ b/src/app/create-playlist/create-playlist.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { CreatePlaylistComponent } from './create-playlist.component'; @@ -6,7 +6,7 @@ describe('CreatePlaylistComponent', () => { let component: CreatePlaylistComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ CreatePlaylistComponent ] }) diff --git a/src/app/dialogs/about-dialog/about-dialog.component.spec.ts b/src/app/dialogs/about-dialog/about-dialog.component.spec.ts index 7fc637d..8ee906c 100644 --- a/src/app/dialogs/about-dialog/about-dialog.component.spec.ts +++ b/src/app/dialogs/about-dialog/about-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { AboutDialogComponent } from './about-dialog.component'; @@ -6,7 +6,7 @@ describe('AboutDialogComponent', () => { let component: AboutDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ AboutDialogComponent ] }) diff --git a/src/app/dialogs/add-user-dialog/add-user-dialog.component.spec.ts b/src/app/dialogs/add-user-dialog/add-user-dialog.component.spec.ts index 2eee5ca..e9e1fcc 100644 --- a/src/app/dialogs/add-user-dialog/add-user-dialog.component.spec.ts +++ b/src/app/dialogs/add-user-dialog/add-user-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { AddUserDialogComponent } from './add-user-dialog.component'; @@ -6,7 +6,7 @@ describe('AddUserDialogComponent', () => { let component: AddUserDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ AddUserDialogComponent ] }) diff --git a/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts index 5b67052..63b7a78 100644 --- a/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts +++ b/src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ArgModifierDialogComponent } from './arg-modifier-dialog.component'; @@ -6,7 +6,7 @@ describe('ArgModifierDialogComponent', () => { let component: ArgModifierDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ArgModifierDialogComponent ] }) diff --git a/src/app/dialogs/confirm-dialog/confirm-dialog.component.spec.ts b/src/app/dialogs/confirm-dialog/confirm-dialog.component.spec.ts index ccea433..384fdf3 100644 --- a/src/app/dialogs/confirm-dialog/confirm-dialog.component.spec.ts +++ b/src/app/dialogs/confirm-dialog/confirm-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ConfirmDialogComponent } from './confirm-dialog.component'; @@ -6,7 +6,7 @@ describe('ConfirmDialogComponent', () => { let component: ConfirmDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ConfirmDialogComponent ] }) diff --git a/src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.spec.ts b/src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.spec.ts index 710d01b..0a28817 100644 --- a/src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.spec.ts +++ b/src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { CookiesUploaderDialogComponent } from './cookies-uploader-dialog.component'; @@ -6,7 +6,7 @@ describe('CookiesUploaderDialogComponent', () => { let component: CookiesUploaderDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ CookiesUploaderDialogComponent ] }) diff --git a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts index 71d64a9..a9b32eb 100644 --- a/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts +++ b/src/app/dialogs/edit-category-dialog/edit-category-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { EditCategoryDialogComponent } from './edit-category-dialog.component'; @@ -6,7 +6,7 @@ describe('EditCategoryDialogComponent', () => { let component: EditCategoryDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ EditCategoryDialogComponent ] }) diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.spec.ts b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.spec.ts index abb5a71..8a48dd8 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.spec.ts +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { EditSubscriptionDialogComponent } from './edit-subscription-dialog.component'; @@ -6,7 +6,7 @@ describe('EditSubscriptionDialogComponent', () => { let component: EditSubscriptionDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ EditSubscriptionDialogComponent ] }) diff --git a/src/app/dialogs/modify-playlist/modify-playlist.component.spec.ts b/src/app/dialogs/modify-playlist/modify-playlist.component.spec.ts index 13cc64f..b724b87 100644 --- a/src/app/dialogs/modify-playlist/modify-playlist.component.spec.ts +++ b/src/app/dialogs/modify-playlist/modify-playlist.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ModifyPlaylistComponent } from './modify-playlist.component'; @@ -6,7 +6,7 @@ describe('ModifyPlaylistComponent', () => { let component: ModifyPlaylistComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ModifyPlaylistComponent ] }) diff --git a/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts index cc37170..b4a7d65 100644 --- a/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts +++ b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SetDefaultAdminDialogComponent } from './set-default-admin-dialog.component'; @@ -6,7 +6,7 @@ describe('SetDefaultAdminDialogComponent', () => { let component: SetDefaultAdminDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SetDefaultAdminDialogComponent ] }) diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.spec.ts b/src/app/dialogs/share-media-dialog/share-media-dialog.component.spec.ts index fddbc1a..5960552 100644 --- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.spec.ts +++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ShareMediaDialogComponent } from './share-media-dialog.component'; @@ -6,7 +6,7 @@ describe('ShareMediaDialogComponent', () => { let component: ShareMediaDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ShareMediaDialogComponent ] }) diff --git a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.spec.ts b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.spec.ts index 94d1fe1..10b9d61 100644 --- a/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.spec.ts +++ b/src/app/dialogs/subscribe-dialog/subscribe-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SubscribeDialogComponent } from './subscribe-dialog.component'; @@ -6,7 +6,7 @@ describe('SubscribeDialogComponent', () => { let component: SubscribeDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SubscribeDialogComponent ] }) diff --git a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.spec.ts b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.spec.ts index 45fa822..15648f5 100644 --- a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.spec.ts +++ b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SubscriptionInfoDialogComponent } from './subscription-info-dialog.component'; @@ -6,7 +6,7 @@ describe('SubscriptionInfoDialogComponent', () => { let component: SubscriptionInfoDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SubscriptionInfoDialogComponent ] }) diff --git a/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.spec.ts b/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.spec.ts index 60ce881..17b8390 100644 --- a/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.spec.ts +++ b/src/app/dialogs/update-progress-dialog/update-progress-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { UpdateProgressDialogComponent } from './update-progress-dialog.component'; @@ -6,7 +6,7 @@ describe('UpdateProgressDialogComponent', () => { let component: UpdateProgressDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ UpdateProgressDialogComponent ] }) diff --git a/src/app/dialogs/user-profile-dialog/user-profile-dialog.component.spec.ts b/src/app/dialogs/user-profile-dialog/user-profile-dialog.component.spec.ts index 364e28b..6e02703 100644 --- a/src/app/dialogs/user-profile-dialog/user-profile-dialog.component.spec.ts +++ b/src/app/dialogs/user-profile-dialog/user-profile-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { UserProfileDialogComponent } from './user-profile-dialog.component'; @@ -6,7 +6,7 @@ describe('UserProfileDialogComponent', () => { let component: UserProfileDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ UserProfileDialogComponent ] }) diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts b/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts index 126ea43..050f037 100644 --- a/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { VideoInfoDialogComponent } from './video-info-dialog.component'; @@ -6,7 +6,7 @@ describe('VideoInfoDialogComponent', () => { let component: VideoInfoDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ VideoInfoDialogComponent ] }) diff --git a/src/app/download-item/download-item.component.spec.ts b/src/app/download-item/download-item.component.spec.ts index 1e731c4..7baea1e 100644 --- a/src/app/download-item/download-item.component.spec.ts +++ b/src/app/download-item/download-item.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { DownloadItemComponent } from './download-item.component'; @@ -6,7 +6,7 @@ describe('DownloadItemComponent', () => { let component: DownloadItemComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ DownloadItemComponent ] }) diff --git a/src/app/file-card/file-card.component.spec.ts b/src/app/file-card/file-card.component.spec.ts index 722eee2..0e940d9 100644 --- a/src/app/file-card/file-card.component.spec.ts +++ b/src/app/file-card/file-card.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { FileCardComponent } from './file-card.component'; @@ -6,7 +6,7 @@ describe('FileCardComponent', () => { let component: FileCardComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ FileCardComponent ] }) diff --git a/src/app/input-dialog/input-dialog.component.spec.ts b/src/app/input-dialog/input-dialog.component.spec.ts index 17691ab..d0ffc68 100644 --- a/src/app/input-dialog/input-dialog.component.spec.ts +++ b/src/app/input-dialog/input-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { InputDialogComponent } from './input-dialog.component'; @@ -6,7 +6,7 @@ describe('InputDialogComponent', () => { let component: InputDialogComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ InputDialogComponent ] }) diff --git a/src/app/main/main.component.spec.ts b/src/app/main/main.component.spec.ts index 0878044..cd81c2d 100644 --- a/src/app/main/main.component.spec.ts +++ b/src/app/main/main.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MainComponent } from './main.component'; @@ -6,7 +6,7 @@ describe('MainComponent', () => { let component: MainComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ MainComponent ] }) diff --git a/src/app/player/player.component.spec.ts b/src/app/player/player.component.spec.ts index d08e5be..be22911 100644 --- a/src/app/player/player.component.spec.ts +++ b/src/app/player/player.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { PlayerComponent } from './player.component'; @@ -6,7 +6,7 @@ describe('PlayerComponent', () => { let component: PlayerComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ PlayerComponent ] }) diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 0ce60ab..fe8ff5a 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, HostListener, EventEmitter, OnDestroy, AfterViewInit, ViewChild } from '@angular/core'; -import { VgAPI } from 'ngx-videogular'; +import { VgApiService } from '@videogular/ngx-videogular/core'; import { PostsService } from 'app/posts.services'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; @@ -31,7 +31,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { currentIndex = 0; currentItem: IMedia = null; - api: VgAPI; + api: VgApiService; api_ready = false; // params @@ -272,7 +272,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.original_playlist = JSON.stringify(this.playlist); } - onPlayerReady(api: VgAPI) { + onPlayerReady(api: VgApiService) { this.api = api; this.api_ready = true; diff --git a/src/app/settings/settings.component.spec.ts b/src/app/settings/settings.component.spec.ts index 91588f3..1df2bd0 100644 --- a/src/app/settings/settings.component.spec.ts +++ b/src/app/settings/settings.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SettingsComponent } from './settings.component'; @@ -6,7 +6,7 @@ describe('SettingsComponent', () => { let component: SettingsComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SettingsComponent ] }) diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts index ccb2763..42facb5 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SubscriptionFileCardComponent } from './subscription-file-card.component'; @@ -6,7 +6,7 @@ describe('SubscriptionFileCardComponent', () => { let component: SubscriptionFileCardComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SubscriptionFileCardComponent ] }) diff --git a/src/app/subscription/subscription/subscription.component.spec.ts b/src/app/subscription/subscription/subscription.component.spec.ts index a4a2203..8cf00c3 100644 --- a/src/app/subscription/subscription/subscription.component.spec.ts +++ b/src/app/subscription/subscription/subscription.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SubscriptionComponent } from './subscription.component'; @@ -6,7 +6,7 @@ describe('SubscriptionComponent', () => { let component: SubscriptionComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SubscriptionComponent ] }) diff --git a/src/app/subscriptions/subscriptions.component.spec.ts b/src/app/subscriptions/subscriptions.component.spec.ts index 205dcf4..14204f7 100644 --- a/src/app/subscriptions/subscriptions.component.spec.ts +++ b/src/app/subscriptions/subscriptions.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SubscriptionsComponent } from './subscriptions.component'; @@ -6,7 +6,7 @@ describe('SubscriptionsComponent', () => { let component: SubscriptionsComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ SubscriptionsComponent ] }) diff --git a/src/app/updater/updater.component.spec.ts b/src/app/updater/updater.component.spec.ts index 62ae61d..40c783a 100644 --- a/src/app/updater/updater.component.spec.ts +++ b/src/app/updater/updater.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { UpdaterComponent } from './updater.component'; @@ -6,7 +6,7 @@ describe('UpdaterComponent', () => { let component: UpdaterComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ UpdaterComponent ] }) diff --git a/tsconfig.json b/tsconfig.json index 1c0a9ed..46666c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,6 @@ "es2016", "dom" ], - "module": "esnext" + "module": "es2020" } } \ No newline at end of file diff --git a/tslint.json b/tslint.json index 02efbe6..a9c831d 100644 --- a/tslint.json +++ b/tslint.json @@ -13,6 +13,9 @@ "curly": true, "eofline": true, "forin": true, + "deprecation": { + "severity": "warning" + }, "import-blacklist": [ true ], @@ -62,7 +65,6 @@ "no-trailing-whitespace": true, "no-unnecessary-initializer": true, "no-unused-expression": true, - "no-use-before-declare": true, "no-var-keyword": true, "object-literal-sort-keys": false, "one-line": [ From ff1bb8dee192e833775a0b80abf62bb3ff792d52 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 10 Dec 2020 19:34:15 -0500 Subject: [PATCH 046/250] Reduced the max number of ghost cards to 10 for performance reasons --- src/app/components/recent-videos/recent-videos.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 5c2503d..a0667c7 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -59,7 +59,8 @@ export class RecentVideosComponent implements OnInit { constructor(public postsService: PostsService, private router: Router) { // get cached file count if (localStorage.getItem('cached_file_count')) { - this.cached_file_count = +localStorage.getItem('cached_file_count'); + this.cached_file_count = +localStorage.getItem('cached_file_count') <= 10 ? +localStorage.getItem('cached_file_count') : 10; + this.loading_files = Array(this.cached_file_count).fill(0); } } From 9de403245b2261cf0bf97a1059216131a2aa7388 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 10 Dec 2020 21:04:53 -0500 Subject: [PATCH 047/250] Twitch chat now supports subscriptions - refactored code to be cleaner and more modularized Updated scrolling on twitch chat to actually scroll to the bottom with new messages Fast forwarding in videos with a twitch chat is now faster and provides a smoother transition --- backend/app.js | 66 ++++--------------- backend/subscriptions.js | 12 +++- backend/twitch.js | 57 +++++++++++++++- .../twitch-chat/twitch-chat.component.html | 3 +- .../twitch-chat/twitch-chat.component.ts | 31 +++++---- src/app/player/player.component.html | 2 +- src/app/player/player.component.ts | 8 +-- src/app/posts.services.ts | 8 +-- 8 files changed, 107 insertions(+), 80 deletions(-) diff --git a/backend/app.js b/backend/app.js index 0cdd414..823ea46 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1192,7 +1192,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { && config.getConfigItem('ytdl_use_twitch_api') && config.getConfigItem('ytdl_twitch_auto_download_chat')) { let vodId = url.split('twitch.tv/videos/')[1]; vodId = vodId.split('?')[0]; - downloadTwitchChatByVODID(vodId, file_name, type, options.user); + twitch_api.downloadTwitchChatByVODID(vodId, file_name, type, options.user); } // renames file if necessary due to bug @@ -1768,42 +1768,6 @@ function removeFileExtension(filename) { return filename_parts.join('.'); } -async function getTwitchChatByFileID(id, type, user_uid, uuid) { - let file_path = null; - - if (user_uid) { - file_path = path.join('users', user_uid, type, id + '.twitch_chat.json'); - } else { - file_path = path.join(type, id + '.twitch_chat.json'); - } - - var chat_file = null; - if (fs.existsSync(file_path)) { - chat_file = fs.readJSONSync(file_path); - } - - return chat_file; -} - -async function downloadTwitchChatByVODID(vodId, id, type, user_uid) { - const twitch_api_key = config_api.getConfigItem('ytdl_twitch_api_key'); - const chat = await twitch_api.getCommentsForVOD(twitch_api_key, vodId); - - // save file if needec params are included - if (id && type) { - let file_path = null; - if (user_uid) { - file_path = path.join('users', user_uid, type, id + '.twitch_chat.json'); - } else { - file_path = path.join(type, id + '.twitch_chat.json'); - } - - if (chat) fs.writeJSONSync(file_path, chat); - } - - return chat; -} - app.use(function(req, res, next) { res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization"); res.header("Access-Control-Allow-Origin", getOrigin()); @@ -2038,7 +2002,7 @@ app.post('/api/getFile', optionalJwt, function (req, res) { } // check if chat exists for twitch videos - if (file['url'].includes('twitch.tv')) file['chat_exists'] = fs.existsSync(file['path'].substring(0, file['path'].length - 4) + '.twitch_chat.json'); + if (file && file['url'].includes('twitch.tv')) file['chat_exists'] = fs.existsSync(file['path'].substring(0, file['path'].length - 4) + '.twitch_chat.json'); if (file) { res.send({ @@ -2109,11 +2073,12 @@ app.post('/api/getFullTwitchChat', optionalJwt, async (req, res) => { var id = req.body.id; var type = req.body.type; var uuid = req.body.uuid; + var sub = req.body.sub; var user_uid = null; if (req.isAuthenticated()) user_uid = req.user.uid; - const chat_file = await getTwitchChatByFileID(id, type, user_uid, uuid); + const chat_file = await twitch_api.getTwitchChatByFileID(id, type, user_uid, uuid, sub); res.send({ chat: chat_file @@ -2125,28 +2090,19 @@ app.post('/api/downloadTwitchChatByVODID', optionalJwt, async (req, res) => { var type = req.body.type; var vodId = req.body.vodId; var uuid = req.body.uuid; + var sub = req.body.sub; var user_uid = null; if (req.isAuthenticated()) user_uid = req.user.uid; // check if file already exists. if so, send that instead - const file_exists_check = await getTwitchChatByFileID(id, type, user_uid, uuid); + const file_exists_check = await twitch_api.getTwitchChatByFileID(id, type, user_uid, uuid, sub); if (file_exists_check) { res.send({chat: file_exists_check}); return; } - const full_chat = await downloadTwitchChatByVODID(vodId); - - let file_path = null; - - if (user_uid) { - file_path = path.join('users', req.user.uid, type, id + '.twitch_chat.json'); - } else { - file_path = path.join(type, id + '.twitch_chat.json'); - } - - if (full_chat) fs.writeJSONSync(file_path, full_chat); + const full_chat = await twitch_api.downloadTwitchChatByVODID(vodId, id, type, user_uid, sub); res.send({ chat: full_chat @@ -2435,9 +2391,15 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => { var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); parsed_files.push(file_obj); } + } else { + // loop through files for extra processing + for (let i = 0; i < parsed_files.length; i++) { + const file = parsed_files[i]; + // check if chat exists for twitch videos + if (file && file['url'].includes('twitch.tv')) file['chat_exists'] = fs.existsSync(file['path'].substring(0, file['path'].length - 4) + '.twitch_chat.json'); + } } - res.send({ subscription: subscription, files: parsed_files diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 4bd071b..9ab7542 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -6,7 +6,8 @@ var path = require('path'); var youtubedl = require('youtube-dl'); const config_api = require('./config'); -var utils = require('./utils') +const twitch_api = require('./twitch'); +var utils = require('./utils'); const debugMode = process.env.YTDL_MODE === 'debug'; @@ -418,6 +419,15 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_ sub_db.get('videos').push(output_json).write(); } else { db_api.registerFileDB(path.basename(output_json['_filename']), sub.type, multiUserMode, sub); + const url = output_json['webpage_url']; + if (sub.type === 'video' && url.includes('twitch.tv/videos/') && url.split('twitch.tv/videos/').length > 1 + && config_api.getConfigItem('ytdl_use_twitch_api') && config_api.getConfigItem('ytdl_twitch_auto_download_chat')) { + const file_name = path.basename(output_json['_filename']); + const id = file_name.substring(0, file_name.length-4); + let vodId = url.split('twitch.tv/videos/')[1]; + vodId = vodId.split('?')[0]; + twitch_api.downloadTwitchChatByVODID(vodId, id, sub.type, multiUserMode.user, sub); + } } } diff --git a/backend/twitch.js b/backend/twitch.js index fb05fbb..2a231e9 100644 --- a/backend/twitch.js +++ b/backend/twitch.js @@ -1,5 +1,8 @@ var moment = require('moment'); var Axios = require('axios'); +var fs = require('fs-extra') +var path = require('path'); +const config_api = require('./config'); async function getCommentsForVOD(clientID, vodId) { let url = `https://api.twitch.tv/v5/videos/${vodId}/comments?content_offset_seconds=0`, @@ -68,6 +71,58 @@ async function getCommentsForVOD(clientID, vodId) { return comments; } +async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) { + let file_path = null; + + if (user_uid) { + if (sub) { + file_path = path.join('users', user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json'); + } else { + file_path = path.join('users', user_uid, type, id + '.twitch_chat.json'); + } + } else { + if (sub) { + file_path = path.join('subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json'); + } else { + file_path = path.join(type, id + '.twitch_chat.json'); + } + } + + var chat_file = null; + if (fs.existsSync(file_path)) { + chat_file = fs.readJSONSync(file_path); + } + + return chat_file; +} + +async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub) { + const twitch_api_key = config_api.getConfigItem('ytdl_twitch_api_key'); + const chat = await getCommentsForVOD(twitch_api_key, vodId); + + // save file if needed params are included + let file_path = null; + if (user_uid) { + if (sub) { + file_path = path.join('users', user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json'); + } else { + file_path = path.join('users', user_uid, type, id + '.twitch_chat.json'); + } + } else { + if (sub) { + file_path = path.join('subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json'); + } else { + file_path = path.join(type, id + '.twitch_chat.json'); + } + } + + if (chat) fs.writeJSONSync(file_path, chat); + + return chat; +} + module.exports = { - getCommentsForVOD: getCommentsForVOD + getCommentsForVOD: getCommentsForVOD, + getTwitchChatByFileID: getTwitchChatByFileID, + downloadTwitchChatByVODID: downloadTwitchChatByVODID } \ No newline at end of file diff --git a/src/app/components/twitch-chat/twitch-chat.component.html b/src/app/components/twitch-chat/twitch-chat.component.html index 634cac6..43fbac0 100644 --- a/src/app/components/twitch-chat/twitch-chat.component.html +++ b/src/app/components/twitch-chat/twitch-chat.component.html @@ -1,7 +1,8 @@
Twitch Chat
-
+
{{chat.timestamp_str}} - {{chat.name}}: {{chat.message}} + {{last ? scrollToBottom() : ''}}
diff --git a/src/app/components/twitch-chat/twitch-chat.component.ts b/src/app/components/twitch-chat/twitch-chat.component.ts index eace952..d2e0aa0 100644 --- a/src/app/components/twitch-chat/twitch-chat.component.ts +++ b/src/app/components/twitch-chat/twitch-chat.component.ts @@ -21,9 +21,11 @@ export class TwitchChatComponent implements OnInit, AfterViewInit { scrollContainer = null; @Input() db_file = null; + @Input() sub = null; @Input() current_timestamp = null; @ViewChild('scrollContainer') scrollRef: ElementRef; + @ViewChildren('chat') chat: QueryList; constructor(private postsService: PostsService) { } @@ -35,22 +37,31 @@ export class TwitchChatComponent implements OnInit, AfterViewInit { } private isUserNearBottom(): boolean { - const threshold = 300; + const threshold = 150; const position = this.scrollContainer.scrollTop + this.scrollContainer.offsetHeight; const height = this.scrollContainer.scrollHeight; return position > height - threshold; } - scrollToBottom = () => { - this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight; + scrollToBottom = (force_scroll) => { + if (force_scroll || this.isUserNearBottom()) { + this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight; + } } addNewChatMessages() { + const next_chat_index = this.getIndexOfNextChat(); if (!this.scrollContainer) { this.scrollContainer = this.scrollRef.nativeElement; } if (this.current_chat_index === null) { - this.current_chat_index = this.getIndexOfNextChat(); + this.current_chat_index = next_chat_index; + } + + if (Math.abs(next_chat_index - this.current_chat_index) > 25) { + this.visible_chat = []; + this.current_chat_index = next_chat_index - 25; + setTimeout(() => this.scrollToBottom(true), 100); } const latest_chat_timestamp = this.visible_chat.length ? this.visible_chat[this.visible_chat.length - 1]['timestamp'] : 0; @@ -59,9 +70,6 @@ export class TwitchChatComponent implements OnInit, AfterViewInit { if (this.full_chat[i]['timestamp'] >= latest_chat_timestamp && this.full_chat[i]['timestamp'] <= this.current_timestamp) { this.visible_chat.push(this.full_chat[i]); this.current_chat_index = i; - if (this.isUserNearBottom()) { - this.scrollToBottom(); - } } else if (this.full_chat[i]['timestamp'] > this.current_timestamp) { break; } @@ -74,7 +82,7 @@ export class TwitchChatComponent implements OnInit, AfterViewInit { } getFullChat() { - this.postsService.getFullTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', null).subscribe(res => { + this.postsService.getFullTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', null, this.sub).subscribe(res => { this.chat_response_received = true; if (res['chat']) { this.initializeChatCheck(res['chat']); @@ -82,11 +90,6 @@ export class TwitchChatComponent implements OnInit, AfterViewInit { }); } - renewChat() { - this.visible_chat = []; - this.current_chat_index = this.getIndexOfNextChat(); - } - downloadTwitchChat() { this.downloading_chat = true; let vodId = this.db_file.url.split('videos/').length > 1 && this.db_file.url.split('videos/')[1]; @@ -94,7 +97,7 @@ export class TwitchChatComponent implements OnInit, AfterViewInit { if (!vodId) { this.postsService.openSnackBar('VOD url for this video is not supported. VOD ID must be after "twitch.tv/videos/"'); } - this.postsService.downloadTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', vodId, null).subscribe(res => { + this.postsService.downloadTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', vodId, null, this.sub).subscribe(res => { if (res['chat']) { this.initializeChatCheck(res['chat']); } else { diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 6774ace..53fad9b 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -15,7 +15,7 @@
- + diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index fe8ff5a..412a583 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -39,6 +39,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { type: string; id = null; // used for playlists (not subscription) uid = null; // used for non-subscription files (audio, video, playlist) + subscription = null; subscriptionName = null; subPlaylist = null; uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video @@ -180,6 +181,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { getSubscription() { this.postsService.getSubscription(null, this.subscriptionName).subscribe(res => { const subscription = res['subscription']; + this.subscription = subscription; if (this.fileNames) { subscription.videos.forEach(video => { if (video['id'] === this.fileNames[0]) { @@ -276,12 +278,6 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.api = api; this.api_ready = true; - this.api.subscriptions.seeked.subscribe(data => { - if (this.twitchChat) { - this.twitchChat.renewChat(); - } - }); - // checks if volume has been previously set. if so, use that as default if (localStorage.getItem('player_volume')) { this.api.volume = parseFloat(localStorage.getItem('player_volume')); diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index da479fb..c3934c7 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -234,12 +234,12 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions); } - getFullTwitchChat(id, type, uuid = null) { - return this.http.post(this.path + 'getFullTwitchChat', {id: id, type: type, uuid: uuid}, this.httpOptions); + getFullTwitchChat(id, type, uuid = null, sub = null) { + return this.http.post(this.path + 'getFullTwitchChat', {id: id, type: type, uuid: uuid, sub: sub}, this.httpOptions); } - downloadTwitchChat(id, type, vodId, uuid = null) { - return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid}, this.httpOptions); + downloadTwitchChat(id, type, vodId, uuid = null, sub = null) { + 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, From 4f693d4eda5d28230d1112d96def19cda198abfc Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 14 Dec 2020 18:19:50 -0500 Subject: [PATCH 048/250] Added description to player component and simplified the database by un-splitting videos and playlists by type --- backend/app.js | 150 +++++++++--------- backend/authentication/auth.js | 66 +++----- backend/db.js | 20 +-- backend/utils.js | 3 +- src/app/app.module.ts | 8 +- .../see-more/see-more.component.html | 11 ++ .../see-more/see-more.component.scss | 7 + .../see-more/see-more.component.spec.ts | 25 +++ .../components/see-more/see-more.component.ts | 60 +++++++ src/app/player/player.component.html | 30 ++-- 10 files changed, 235 insertions(+), 145 deletions(-) create mode 100644 src/app/components/see-more/see-more.component.html create mode 100644 src/app/components/see-more/see-more.component.scss create mode 100644 src/app/components/see-more/see-more.component.spec.ts create mode 100644 src/app/components/see-more/see-more.component.ts diff --git a/backend/app.js b/backend/app.js index 148e94c..b2d6446 100644 --- a/backend/app.js +++ b/backend/app.js @@ -13,7 +13,7 @@ var express = require("express"); var bodyParser = require("body-parser"); var archiver = require('archiver'); var unzipper = require('unzipper'); -var db_api = require('./db') +var db_api = require('./db'); var utils = require('./utils') var mergeFiles = require('merge-files'); const low = require('lowdb') @@ -87,14 +87,8 @@ categories_api.initialize(db, users_db, logger, db_api); // Set some defaults db.defaults( { - playlists: { - audio: [], - video: [] - }, - files: { - audio: [], - video: [] - }, + playlists: [], + files: [], configWriteFlag: false, downloads: {}, subscriptions: [], @@ -218,10 +212,12 @@ async function checkMigrations() { // 4.1->4.2 migration - const add_description_migration_complete = false; // db.get('add_description_migration_complete').value(); + const add_description_migration_complete = db.get('add_description_migration_complete').value(); if (!add_description_migration_complete) { logger.info('Beginning migration: 4.1->4.2+') - const success = await addMetadataPropertyToDB('description'); + let success = await simplifyDBFileStructure(); + success = success && await addMetadataPropertyToDB('view_count'); + success = success && await addMetadataPropertyToDB('description'); if (success) { logger.info('4.1->4.2+ migration complete!'); } else { logger.error('Migration failed: 4.1->4.2+'); } } @@ -229,6 +225,7 @@ async function checkMigrations() { return true; } +/* async function runFilesToDBMigration() { try { let mp3s = await getMp3s(); @@ -260,6 +257,37 @@ async function runFilesToDBMigration() { return false; } } +*/ + +async function simplifyDBFileStructure() { + let users = users_db.get('users').value(); + for (let i = 0; i < users.length; i++) { + const user = users[i]; + if (user['files']['video'] !== undefined && user['files']['audio'] !== undefined) { + const user_files = user['files']['video'].concat(user['files']['audio']); + const user_db_path = users_db.get('users').find({uid: user['uid']}); + user_db_path.assign({files: user_files}).write(); + } + if (user['playlists']['video'] !== undefined && user['playlists']['audio'] !== undefined) { + const user_playlists = user['playlists']['video'].concat(user['playlists']['audio']); + const user_db_path = users_db.get('users').find({uid: user['uid']}); + user_db_path.assign({playlists: user_playlists}).write(); + } + } + + if (db.get('files.video').value() !== undefined && db.get('files.audio').value() !== undefined) { + const files = db.get('files.video').value().concat(db.get('files.audio')); + db.assign({files: files}).write(); + } + + if (db.get('playlists.video').value() !== undefined && db.get('playlists.audio').value() !== undefined) { + const playlists = db.get('playlists.video').value().concat(db.get('playlists.audio')); + db.assign({playlists: playlists}).write(); + } + + + return true; +} async function addMetadataPropertyToDB(property_key) { try { @@ -592,6 +620,9 @@ async function loadConfig() { // creates archive path if missing await fs.ensureDir(archivePath); + // check migrations + await checkMigrations(); + // now this is done here due to youtube-dl's repo takedown await startYoutubeDL(); @@ -606,9 +637,6 @@ async function loadConfig() { db_api.importUnregisteredFiles(); - // check migrations - await checkMigrations(); - // load in previous downloads downloads = db.get('downloads').value(); @@ -1994,7 +2022,7 @@ async function addThumbnails(files) { // gets all download mp3s app.get('/api/getMp3s', optionalJwt, async function(req, res) { - var mp3s = db.get('files.audio').value(); // getMp3s(); + var mp3s = db.get('files').chain().find({isAudio: true}).value(); // getMp3s(); var playlists = db.get('playlists.audio').value(); const is_authenticated = req.isAuthenticated(); if (is_authenticated) { @@ -2020,8 +2048,8 @@ app.get('/api/getMp3s', optionalJwt, async function(req, res) { // gets all download mp4s app.get('/api/getMp4s', optionalJwt, async function(req, res) { - var mp4s = db.get('files.video').value(); // getMp4s(); - var playlists = db.get('playlists.video').value(); + var mp4s = db.get('files').chain().find({isAudio: false}).value(); // getMp4s(); + var playlists = db.get('playlists').value(); const is_authenticated = req.isAuthenticated(); if (is_authenticated) { @@ -2052,21 +2080,11 @@ app.post('/api/getFile', optionalJwt, function (req, res) { var file = null; if (req.isAuthenticated()) { - file = auth_api.getUserVideo(req.user.uid, uid, type); + file = auth_api.getUserVideo(req.user.uid, uid); } else if (uuid) { - file = auth_api.getUserVideo(uuid, uid, type, true); + file = auth_api.getUserVideo(uuid, uid, true); } else { - if (!type) { - file = db.get('files.audio').find({uid: uid}).value(); - if (!file) { - file = db.get('files.video').find({uid: uid}).value(); - if (file) type = 'video'; - } else { - type = 'audio'; - } - } - - if (!file && type) file = db.get(`files.${type}`).find({uid: uid}).value(); + file = db.get('files').find({uid: uid}).value(); } // check if chat exists for twitch videos @@ -2086,32 +2104,20 @@ app.post('/api/getFile', optionalJwt, function (req, res) { app.post('/api/getAllFiles', optionalJwt, async function (req, res) { // these are returned - let files = []; - let playlists = []; - let subscription_files = []; + let files = null; + let playlists = null; - let videos = null; - let audios = null; - let audio_playlists = null; - let video_playlists = null; let subscriptions = config_api.getConfigItem('ytdl_allow_subscriptions') ? (subscriptions_api.getAllSubscriptions(req.isAuthenticated() ? req.user.uid : null)) : []; // get basic info depending on multi-user mode being enabled if (req.isAuthenticated()) { - videos = auth_api.getUserVideos(req.user.uid, 'video'); - audios = auth_api.getUserVideos(req.user.uid, 'audio'); - audio_playlists = auth_api.getUserPlaylists(req.user.uid, 'audio'); - video_playlists = auth_api.getUserPlaylists(req.user.uid, 'video'); + files = auth_api.getUserVideos(req.user.uid); + playlists = auth_api.getUserPlaylists(req.user.uid); } else { - videos = db.get('files.audio').value(); - audios = db.get('files.video').value(); - audio_playlists = db.get('playlists.audio').value(); - video_playlists = db.get('playlists.video').value(); + files = db.get('files').value(); + playlists = db.get('playlists').value(); } - files = videos.concat(audios); - playlists = video_playlists.concat(audio_playlists); - // loop through subscriptions and add videos for (let i = 0; i < subscriptions.length; i++) { sub = subscriptions[i]; @@ -2187,14 +2193,13 @@ app.post('/api/downloadTwitchChatByVODID', optionalJwt, async (req, res) => { // video sharing app.post('/api/enableSharing', optionalJwt, function(req, res) { - var type = req.body.type; var uid = req.body.uid; var is_playlist = req.body.is_playlist; let success = false; // multi-user mode if (req.isAuthenticated()) { // if multi user mode, use this method instead - success = auth_api.changeSharingMode(req.user.uid, uid, type, is_playlist, true); + success = auth_api.changeSharingMode(req.user.uid, uid, is_playlist, true); res.send({success: success}); return; } @@ -2203,12 +2208,12 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) { try { success = true; if (!is_playlist && type !== 'subscription') { - db.get(`files.${type}`) + db.get(`files`) .find({uid: uid}) .assign({sharingEnabled: true}) .write(); } else if (is_playlist) { - db.get(`playlists.${type}`) + db.get(`playlists`) .find({id: uid}) .assign({sharingEnabled: true}) .write(); @@ -2237,7 +2242,7 @@ app.post('/api/disableSharing', optionalJwt, function(req, res) { // multi-user mode if (req.isAuthenticated()) { // if multi user mode, use this method instead - success = auth_api.changeSharingMode(req.user.uid, uid, type, is_playlist, false); + success = auth_api.changeSharingMode(req.user.uid, uid, is_playlist, false); res.send({success: success}); return; } @@ -2246,12 +2251,12 @@ app.post('/api/disableSharing', optionalJwt, function(req, res) { try { success = true; if (!is_playlist && type !== 'subscription') { - db.get(`files.${type}`) + db.get(`files`) .find({uid: uid}) .assign({sharingEnabled: false}) .write(); } else if (is_playlist) { - db.get(`playlists.${type}`) + db.get(`playlists`) .find({id: uid}) .assign({sharingEnabled: false}) .write(); @@ -2544,7 +2549,7 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => { if (req.isAuthenticated()) { auth_api.addPlaylist(req.user.uid, new_playlist, type); } else { - db.get(`playlists.${type}`) + db.get(`playlists`) .push(new_playlist) .write(); } @@ -2558,26 +2563,15 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => { app.post('/api/getPlaylist', optionalJwt, async (req, res) => { let playlistID = req.body.playlistID; - let type = req.body.type; let uuid = req.body.uuid; let playlist = null; if (req.isAuthenticated()) { - playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID, type); + playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID); type = playlist.type; } else { - if (!type) { - playlist = db.get('playlists.audio').find({id: playlistID}).value(); - if (!playlist) { - playlist = db.get('playlists.video').find({id: playlistID}).value(); - if (playlist) type = 'video'; - } else { - type = 'audio'; - } - } - - if (!playlist) playlist = db.get(`playlists.${type}`).find({id: playlistID}).value(); + playlist = db.get(`playlists`).find({id: playlistID}).value(); } res.send({ @@ -2590,14 +2584,13 @@ 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 type = req.body.type; let success = false; try { if (req.isAuthenticated()) { - auth_api.updatePlaylistFiles(req.user.uid, playlistID, fileNames, type); + auth_api.updatePlaylistFiles(req.user.uid, playlistID, fileNames); } else { - db.get(`playlists.${type}`) + db.get(`playlists`) .find({id: playlistID}) .assign({fileNames: fileNames}) .write(); @@ -2623,15 +2616,14 @@ app.post('/api/updatePlaylist', optionalJwt, async (req, res) => { app.post('/api/deletePlaylist', optionalJwt, async (req, res) => { let playlistID = req.body.playlistID; - let type = req.body.type; let success = null; try { if (req.isAuthenticated()) { - auth_api.removePlaylist(req.user.uid, playlistID, type); + auth_api.removePlaylist(req.user.uid, playlistID); } else { // removes playlist from playlists - db.get(`playlists.${type}`) + db.get(`playlists`) .remove({id: playlistID}) .write(); } @@ -2653,23 +2645,23 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { var blacklistMode = req.body.blacklistMode; if (req.isAuthenticated()) { - let success = auth_api.deleteUserFile(req.user.uid, uid, type, blacklistMode); + let success = auth_api.deleteUserFile(req.user.uid, uid, blacklistMode); res.send(success); return; } - var file_obj = db.get(`files.${type}`).find({uid: uid}).value(); + 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.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode); - db.get('files.video').remove({uid: uid}).write(); + db.get('files').remove({uid: uid}).write(); // wasDeleted = true; res.send(wasDeleted); } else if (video_obj) { - db.get('files.video').remove({uid: uid}).write(); + db.get('files').remove({uid: uid}).write(); wasDeleted = true; res.send(wasDeleted); } else { diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index 9fdc7e7..3c77edd 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -281,24 +281,13 @@ exports.adminExists = function() { // video stuff -exports.getUserVideos = function(user_uid, type) { +exports.getUserVideos = function(user_uid) { const user = users_db.get('users').find({uid: user_uid}).value(); - return user['files'][type]; + return user['files']; } -exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false) { - let file = null; - if (!type) { - file = users_db.get('users').find({uid: user_uid}).get(`files.audio`).find({uid: file_uid}).value(); - if (!file) { - file = users_db.get('users').find({uid: user_uid}).get(`files.video`).find({uid: file_uid}).value(); - if (file) type = 'video'; - } else { - type = 'audio'; - } - } - - if (!file && type) file = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value(); +exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) { + let file = users_db.get('users').find({uid: user_uid}).get(`files`).find({uid: file_uid}).value(); // prevent unauthorized users from accessing the file info if (file && !file['sharingEnabled'] && requireSharing) file = null; @@ -306,38 +295,28 @@ exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false return file; } -exports.addPlaylist = function(user_uid, new_playlist, type) { - users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).push(new_playlist).write(); +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, type) { - users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).assign({fileNames: new_filenames}); +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; } -exports.removePlaylist = function(user_uid, playlistID, type) { - users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).remove({id: playlistID}).write(); +exports.removePlaylist = function(user_uid, playlistID) { + users_db.get('users').find({uid: user_uid}).get(`playlists`).remove({id: playlistID}).write(); return true; } -exports.getUserPlaylists = function(user_uid, type) { +exports.getUserPlaylists = function(user_uid) { const user = users_db.get('users').find({uid: user_uid}).value(); - return user['playlists'][type]; + return user['playlists']; } -exports.getUserPlaylist = function(user_uid, playlistID, type, requireSharing = false) { - let playlist = null; - if (!type) { - playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.audio`).find({id: playlistID}).value(); - if (!playlist) { - playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.video`).find({id: playlistID}).value(); - if (playlist) type = 'video'; - } else { - type = 'audio'; - } - } - if (!playlist) playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).value(); +exports.getUserPlaylist = function(user_uid, playlistID, requireSharing = false) { + let playlist = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}).value(); // prevent unauthorized users from accessing the file info if (requireSharing && !playlist['sharingEnabled']) playlist = null; @@ -345,21 +324,22 @@ exports.getUserPlaylist = function(user_uid, playlistID, type, requireSharing = return playlist; } -exports.registerUserFile = function(user_uid, file_object, type) { - users_db.get('users').find({uid: user_uid}).get(`files.${type}`) +exports.registerUserFile = function(user_uid, file_object) { + users_db.get('users').find({uid: user_uid}).get(`files`) .remove({ path: file_object['path'] }).write(); - users_db.get('users').find({uid: user_uid}).get(`files.${type}`) + users_db.get('users').find({uid: user_uid}).get(`files`) .push(file_object) .write(); } -exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode = false) { +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.${type}`).find({uid: file_uid}).value(); + 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'; @@ -375,7 +355,7 @@ exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode } const full_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext); - users_db.get('users').find({uid: user_uid}).get(`files.${type}`) + users_db.get('users').find({uid: user_uid}).get(`files`) .remove({ uid: file_uid }).write(); @@ -424,11 +404,11 @@ exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode return success; } -exports.changeSharingMode = function(user_uid, file_uid, type, is_playlist, enabled) { +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}); if (user_db_obj.value()) { - const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({id: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid}); + const file_db_obj = is_playlist ? user_db_obj.get(`playlists`).find({id: file_uid}) : user_db_obj.get(`files`).find({uid: file_uid}); if (file_db_obj.value()) { success = true; file_db_obj.assign({sharingEnabled: enabled}).write(); diff --git a/backend/db.js b/backend/db.js index 9436715..f95b43f 100644 --- a/backend/db.js +++ b/backend/db.js @@ -32,9 +32,9 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null, custo if (!sub) { if (multiUserMode) { const user_uid = multiUserMode.user; - db_path = users_db.get('users').find({uid: user_uid}).get(`files.${type}`); + db_path = users_db.get('users').find({uid: user_uid}).get(`files`); } else { - db_path = db.get(`files.${type}`) + db_path = db.get(`files`); } } else { if (multiUserMode) { @@ -94,18 +94,18 @@ function generateFileObject(id, type, customPath = null, sub = null) { var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = type === 'audio'; - var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date); + var description = jsonobj.description; + var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date, description); return file_obj; } function updatePlaylist(playlist, user_uid) { let playlistID = playlist.id; - let type = playlist.type; let db_loc = null; if (user_uid) { - db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}); + db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}); } else { - db_loc = db.get(`playlists.${type}`).find({id: playlistID}); + db_loc = db.get(`playlists`).find({id: playlistID}); } db_loc.assign(playlist).write(); return true; @@ -132,14 +132,14 @@ function getFileDirectoriesAndDBs() { // add user's audio dir to check list dirs_to_check.push({ basePath: path.join(usersFileFolder, user.uid, 'audio'), - dbPath: users_db.get('users').find({uid: user.uid}).get('files.audio'), + dbPath: users_db.get('users').find({uid: user.uid}).get('files'), type: 'audio' }); // add user's video dir to check list dirs_to_check.push({ basePath: path.join(usersFileFolder, user.uid, 'video'), - dbPath: users_db.get('users').find({uid: user.uid}).get('files.video'), + dbPath: users_db.get('users').find({uid: user.uid}).get('files'), type: 'video' }); } @@ -153,14 +153,14 @@ function getFileDirectoriesAndDBs() { // add audio dir to check list dirs_to_check.push({ basePath: audioFolderPath, - dbPath: db.get('files.audio'), + dbPath: db.get('files'), type: 'audio' }); // add video dir to check list dirs_to_check.push({ basePath: videoFolderPath, - dbPath: db.get('files.video'), + dbPath: db.get('files'), type: 'video' }); } diff --git a/backend/utils.js b/backend/utils.js index 33b877a..988a112 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -189,7 +189,7 @@ async function recFindByExt(base,ext,files,result) // objects -function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) { +function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description) { this.id = id; this.title = title; this.thumbnailURL = thumbnailURL; @@ -200,6 +200,7 @@ function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, p this.size = size; this.path = path; this.upload_date = upload_date; + this.description = description; } module.exports = { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 72c2658..3f9a119 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -81,6 +81,7 @@ import { EditSubscriptionDialogComponent } from './dialogs/edit-subscription-dia import { CustomPlaylistsComponent } from './components/custom-playlists/custom-playlists.component'; import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit-category-dialog.component'; import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component'; +import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component'; registerLocaleData(es, 'es'); @@ -107,6 +108,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible VideoInfoDialogComponent, ArgModifierDialogComponent, HighlightPipe, + LinkifyPipe, UpdaterComponent, UpdateProgressDialogComponent, ShareMediaDialogComponent, @@ -127,7 +129,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible EditSubscriptionDialogComponent, CustomPlaylistsComponent, EditCategoryDialogComponent, - TwitchChatComponent + TwitchChatComponent, + SeeMoreComponent ], imports: [ CommonModule, @@ -188,7 +191,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible PostsService ], exports: [ - HighlightPipe + HighlightPipe, + LinkifyPipe ], bootstrap: [AppComponent] }) diff --git a/src/app/components/see-more/see-more.component.html b/src/app/components/see-more/see-more.component.html new file mode 100644 index 0000000..297d820 --- /dev/null +++ b/src/app/components/see-more/see-more.component.html @@ -0,0 +1,11 @@ + + + + + See more. + + + See less. + + + \ No newline at end of file diff --git a/src/app/components/see-more/see-more.component.scss b/src/app/components/see-more/see-more.component.scss new file mode 100644 index 0000000..1843c11 --- /dev/null +++ b/src/app/components/see-more/see-more.component.scss @@ -0,0 +1,7 @@ +.text { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + word-wrap: break-word; + } \ No newline at end of file diff --git a/src/app/components/see-more/see-more.component.spec.ts b/src/app/components/see-more/see-more.component.spec.ts new file mode 100644 index 0000000..608bee6 --- /dev/null +++ b/src/app/components/see-more/see-more.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SeeMoreComponent } from './see-more.component'; + +describe('SeeMoreComponent', () => { + let component: SeeMoreComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SeeMoreComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SeeMoreComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/see-more/see-more.component.ts b/src/app/components/see-more/see-more.component.ts new file mode 100644 index 0000000..8573cb9 --- /dev/null +++ b/src/app/components/see-more/see-more.component.ts @@ -0,0 +1,60 @@ +import { Component, Input, OnInit, Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Pipe({ name: 'linkify' }) +export class LinkifyPipe implements PipeTransform { + + constructor(private _domSanitizer: DomSanitizer) {} + + transform(value: any, args?: any): any { + return this._domSanitizer.bypassSecurityTrustHtml(this.stylize(value)); + } + + // Modify this method according to your custom logic + private stylize(text: string): string { + let stylizedText: string = ''; + if (text && text.length > 0) { + for (let line of text.split("\n")) { + for (let t of line.split(" ")) { + if (t.startsWith("http") && t.length>7) { + stylizedText += `${t} `; + } + else + stylizedText += t + " "; + } + stylizedText += '
'; + } + return stylizedText; + } + else return text; + } + +} + +@Component({ + selector: 'app-see-more', + templateUrl: './see-more.component.html', + providers: [LinkifyPipe], + styleUrls: ['./see-more.component.scss'] +}) +export class SeeMoreComponent implements OnInit { + + see_more_active = false; + + @Input() text = ''; + @Input() line_limit = 2; + + constructor() { } + + ngOnInit(): void { + } + + toggleSeeMore() { + this.see_more_active = !this.see_more_active; + } + + parseText() { + return this.text.replace(/(http.*?\s)/, "$1") + } + +} diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 73ea9eb..a328561 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -8,14 +8,28 @@
-
+
-
- {{file_obj['local_play_count'] ? file_obj['local_play_count'] : 0}} views +
+ {{db_file['local_view_count'] ? db_file['local_view_count'] : 0}} views
-
- +
+ +

+ +

+
+ +

+ No description available. +

+
+
+
+
+ +
@@ -25,15 +39,11 @@ {{playlist_item.label}}
- + - -
- -
From da3bd2600f45896653ad4b4daafa8abbf8df0364 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 15 Dec 2020 00:42:24 -0500 Subject: [PATCH 049/250] Fixed bug where sharing didn't work for some videos View count now increments on each play unless the video is shared --- backend/app.js | 23 ++++++++++++++++++++++- backend/db.js | 20 +++++++++++++++++++- src/app/player/player.component.html | 2 +- src/app/player/player.component.ts | 8 ++++++++ src/app/posts.services.ts | 4 ++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/backend/app.js b/backend/app.js index 849f9ed..93149d6 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2233,6 +2233,27 @@ app.post('/api/disableSharing', optionalJwt, function(req, res) { }); }); +app.post('/api/incrementViewCount', optionalJwt, async (req, res) => { + let file_uid = req.body.file_uid; + let sub_id = req.body.sub_id; + let uuid = req.body.uuid; + + if (!uuid && req.isAuthenticated()) { + uuid = req.user.uid; + } + + const file_obj = await db_api.getVideo(file_uid, uuid, sub_id); + + const current_view_count = file_obj && file_obj['local_view_count'] ? file_obj['local_view_count'] : 0; + const new_view_count = current_view_count + 1; + + await db_api.setVideoProperty(file_uid, {local_view_count: new_view_count}, uuid, sub_id); + + res.send({ + success: true + }); +}); + // categories app.post('/api/getAllCategories', optionalJwt, async (req, res) => { @@ -2759,7 +2780,7 @@ app.get('/api/stream/:id', optionalJwt, (req, res) => { 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) : null; + 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']) { diff --git a/backend/db.js b/backend/db.js index f95b43f..e609a30 100644 --- a/backend/db.js +++ b/backend/db.js @@ -205,10 +205,28 @@ async function importUnregisteredFiles() { } +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'); + return sub_db_path.find({uid: file_uid}).value(); +} + +async function setVideoProperty(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}); + if (!(file_db_path.value())) { + logger.error(`Failed to find file with uid ${file_uid}`); + } + sub_db_path.find({uid: file_uid}).assign(assignment_obj).write(); +} + module.exports = { initialize: initialize, registerFileDB: registerFileDB, updatePlaylist: updatePlaylist, getFileDirectoriesAndDBs: getFileDirectoriesAndDBs, - importUnregisteredFiles: importUnregisteredFiles + importUnregisteredFiles: importUnregisteredFiles, + getVideo: getVideo, + setVideoProperty: setVideoProperty } diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 5eb1e71..e96156b 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -12,7 +12,7 @@
- {{db_file['local_view_count'] ? db_file['local_view_count'] : 0}} views + {{db_file['local_view_count'] ? db_file['local_view_count']+1 : 1}} views
diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 412a583..c2aa1c2 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -160,6 +160,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { this.openSnackBar('Failed to get file information from the server.', 'Dismiss'); return; } + this.postsService.incrementViewCount(this.db_file['uid'], null, this.uuid).subscribe(res => {}, err => { + 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 @@ -186,6 +190,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { 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(); } diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index c3934c7..77acefc 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -286,6 +286,10 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'enableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions); } + incrementViewCount(file_uid, sub_id, uuid) { + return this.http.post(this.path + 'incrementViewCount', {file_uid: file_uid, sub_id: sub_id, uuid: uuid}, this.httpOptions); + } + disableSharing(uid, type, is_playlist) { return this.http.post(this.path + 'disableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions); } From e39e8f3dba8f719ee3fdab89f0cb2d71ceaed629 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 15 Dec 2020 01:11:15 -0500 Subject: [PATCH 050/250] Home page paginator no longer disappears for empty pages Paginator length fixed Updated styling on paginator Added new text if videos are not present on the home page (due to filter or no downloads in general) --- .../components/recent-videos/recent-videos.component.html | 5 ++++- .../components/recent-videos/recent-videos.component.scss | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index f530163..ec4616c 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -34,6 +34,9 @@
+
+ No videos found. +
@@ -43,7 +46,7 @@
- diff --git a/src/app/components/recent-videos/recent-videos.component.scss b/src/app/components/recent-videos/recent-videos.component.scss index 5530225..49023ec 100644 --- a/src/app/components/recent-videos/recent-videos.component.scss +++ b/src/app/components/recent-videos/recent-videos.component.scss @@ -47,6 +47,10 @@ top: 10px; } +.paginator { + margin-top: 5px; +} + .my-videos-title { text-align: center; position: relative; From 43b0c2fb9e1b0c119510db2eb9f60071ae583082 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 15 Dec 2020 17:19:15 -0500 Subject: [PATCH 051/250] Fixed bug that prevented subscription videos from being shown in the subscription page --- .../subscription-file-card.component.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) 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 c63a6f3..e500a7d 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 @@ -15,9 +15,6 @@ export class SubscriptionFileCardComponent implements OnInit { image_errored = false; image_loaded = false; - scrollSubject; - scrollAndLoad; - formattedDuration = null; @Input() file; @@ -27,13 +24,7 @@ export class SubscriptionFileCardComponent implements OnInit { @Output() goToFileEmit = new EventEmitter(); @Output() reloadSubscription = new EventEmitter(); - constructor(private snackBar: MatSnackBar, private postsService: PostsService, private dialog: MatDialog) { - this.scrollSubject = new Subject(); - this.scrollAndLoad = Observable.merge( - Observable.fromEvent(window, 'scroll'), - this.scrollSubject - ); - } + constructor(private snackBar: MatSnackBar, private postsService: PostsService, private dialog: MatDialog) {} ngOnInit() { if (this.file.duration) { @@ -45,10 +36,6 @@ export class SubscriptionFileCardComponent implements OnInit { this.image_errored = true; } - onHoverResponse() { - this.scrollSubject.next(); - } - imageLoaded(loaded) { this.image_loaded = true; } From ff8886d2e0d5d4a79eda95d8c468fadae02a140e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 15 Dec 2020 17:20:04 -0500 Subject: [PATCH 052/250] Simplified rxjs imports on the home page and potentially removed an erroneous error --- src/app/main/main.component.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index e6cfe48..013d0f3 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -1,19 +1,11 @@ import { Component, OnInit, ElementRef, ViewChild, ViewChildren, QueryList } from '@angular/core'; import {PostsService} from '../posts.services'; import {FileCardComponent} from '../file-card/file-card.component'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import {FormControl, Validators} from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { saveAs } from 'file-saver'; -import 'rxjs/add/observable/of'; -import 'rxjs/add/operator/mapTo'; -import 'rxjs/add/operator/toPromise'; -import 'rxjs/add/observable/fromEvent' -import 'rxjs/add/operator/filter' -import 'rxjs/add/operator/debounceTime' -import 'rxjs/add/operator/do' -import 'rxjs/add/operator/switch' import { YoutubeSearchService, Result } from '../youtube-search.service'; import { Router, ActivatedRoute } from '@angular/router'; import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component'; From 29b8dc227c33ddb980e1ea10b25ab23f5ba13536 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 15 Dec 2020 20:04:02 -0500 Subject: [PATCH 053/250] Updated location/style of the share and download icons on the player --- src/app/player/player.component.css | 6 ++---- src/app/player/player.component.html | 27 ++++++++++++--------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/app/player/player.component.css b/src/app/player/player.component.css index 42247ef..202b80d 100644 --- a/src/app/player/player.component.css +++ b/src/app/player/player.component.css @@ -37,10 +37,8 @@ } .spinner { - width: 50px; - height: 50px; - bottom: 3px; - left: 3px; + bottom: 1px; + left: 2px; position: absolute; } diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index e96156b..6aaa7b0 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -14,7 +14,7 @@
{{db_file['local_view_count'] ? db_file['local_view_count']+1 : 1}} views
-
+

@@ -26,10 +26,17 @@

-
-
- -
+
+ + + + + + + + + +
@@ -52,16 +59,6 @@
- -
- - - -
-
- - -
From e3374c573a5305d7ccec9fb65f5cccf731e1c226 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 15 Dec 2020 20:16:53 -0500 Subject: [PATCH 054/250] Args incompatible with video mode and audio-only mode will now get removed --- backend/app.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/app.js b/backend/app.js index 823ea46..d2c4378 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1505,6 +1505,10 @@ async function generateArgs(url, type, options) { } } + + // filter out incompatible args + downloadConfig = filterArgs(downloadConfig, is_audio); + logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`); return downloadConfig; } @@ -1535,6 +1539,13 @@ async function getVideoInfoByURL(url, args = [], download = null) { }); } +function filterArgs(args, isAudio) { + const video_only_args = ['--add-metadata', '--embed-subs', '--xattrs']; + const audio_only_args = ['-x', '--extract-audio', '--embed-thumbnail']; + const args_to_remove = isAudio ? video_only_args : audio_only_args; + return args.filter(x => !args_to_remove.includes(x)); +} + // currently only works for single urls async function getUrlInfos(urls) { let startDate = Date.now(); From 8058b743eb8ca8206c8e4ed07191cdcad3a89bb5 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 18 Dec 2020 18:31:23 -0500 Subject: [PATCH 055/250] Added support for redownloading fresh uploads, which will eventually be hidden behind an opt-in setting --- backend/app.js | 66 +------------ backend/db.js | 2 +- backend/subscriptions.js | 196 ++++++++++++++++++++++++++------------- backend/utils.js | 15 ++- 4 files changed, 145 insertions(+), 134 deletions(-) diff --git a/backend/app.js b/backend/app.js index 93149d6..6476136 100644 --- a/backend/app.js +++ b/backend/app.js @@ -218,6 +218,8 @@ async function checkMigrations() { let success = await simplifyDBFileStructure(); success = success && await addMetadataPropertyToDB('view_count'); success = success && await addMetadataPropertyToDB('description'); + success = success && await addMetadataPropertyToDB('height'); + success = success && await addMetadataPropertyToDB('abr'); if (success) { logger.info('4.1->4.2+ migration complete!'); } else { logger.error('Migration failed: 4.1->4.2+'); } } @@ -225,7 +227,6 @@ async function checkMigrations() { return true; } -/* async function runFilesToDBMigration() { try { let mp3s = await getMp3s(); @@ -257,7 +258,6 @@ async function runFilesToDBMigration() { return false; } } -*/ async function simplifyDBFileStructure() { let users = users_db.get('users').value(); @@ -760,64 +760,6 @@ function generateEnvVarConfigItem(key) { return {key: key, value: process['env'][key]}; } -async function getMp3s() { - let mp3s = []; - var files = await utils.recFindByExt(audioFolderPath, 'mp3'); // fs.readdirSync(audioFolderPath); - for (let i = 0; i < files.length; i++) { - let file = files[i]; - var file_path = file.substring(audioFolderPath.length, file.length); - - var stats = await fs.stat(file); - - var id = file_path.substring(0, file_path.length-4); - var jsonobj = await utils.getJSONMp3(id, audioFolderPath); - if (!jsonobj) continue; - var title = jsonobj.title; - var url = jsonobj.webpage_url; - var uploader = jsonobj.uploader; - var upload_date = jsonobj.upload_date; - upload_date = upload_date ? `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}` : null; - - var size = stats.size; - - var thumbnail = jsonobj.thumbnail; - var duration = jsonobj.duration; - var isaudio = true; - var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); - mp3s.push(file_obj); - } - return mp3s; -} - -async function getMp4s(relative_path = true) { - let mp4s = []; - var files = await utils.recFindByExt(videoFolderPath, 'mp4'); - for (let i = 0; i < files.length; i++) { - let file = files[i]; - var file_path = file.substring(videoFolderPath.length, file.length); - - var stats = fs.statSync(file); - - var id = file_path.substring(0, file_path.length-4); - var jsonobj = await utils.getJSONMp4(id, videoFolderPath); - if (!jsonobj) continue; - var title = jsonobj.title; - var url = jsonobj.webpage_url; - var uploader = jsonobj.uploader; - var upload_date = jsonobj.upload_date; - upload_date = upload_date ? `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}` : null; - var thumbnail = jsonobj.thumbnail; - var duration = jsonobj.duration; - - var size = stats.size; - - var isaudio = false; - var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); - mp4s.push(file_obj); - } - return mp4s; -} - function getThumbnailMp3(name) { var obj = utils.getJSONMp3(name, audioFolderPath); @@ -2446,7 +2388,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => { var size = stats.size; var isaudio = false; - var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); + var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date, jsonobj.description, jsonobj.view_count, jsonobj.height, jsonobj.abr); parsed_files.push(file_obj); } } else { @@ -2468,7 +2410,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => { if (subscription.videos) { for (let i = 0; i < subscription.videos.length; i++) { const video = subscription.videos[i]; - parsed_files.push(new utils.File(video.title, video.title, video.thumbnail, false, video.duration, video.url, video.uploader, video.size, null, null, video.upload_date)); + parsed_files.push(new utils.File(video.title, video.title, video.thumbnail, false, video.duration, video.url, video.uploader, video.size, null, null, video.upload_date, video.view_count, video.height, video.abr)); } } res.send({ diff --git a/backend/db.js b/backend/db.js index e609a30..b7298d0 100644 --- a/backend/db.js +++ b/backend/db.js @@ -95,7 +95,7 @@ function generateFileObject(id, type, customPath = null, sub = null) { var duration = jsonobj.duration; var isaudio = type === 'audio'; var description = jsonobj.description; - var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date, description); + var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date, description, jsonobj.view_count, jsonobj.height, jsonobj.abr); return file_obj; } diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 9ab7542..7ce2a96 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -273,10 +273,7 @@ async function getVideosForSub(sub, user_uid = null) { else basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); - const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); - - let appendedBasePath = null - appendedBasePath = getAppendedBasePath(sub, basePath); + let appendedBasePath = getAppendedBasePath(sub, basePath); let multiUserMode = null; if (user_uid) { @@ -286,14 +283,87 @@ async function getVideosForSub(sub, user_uid = null) { } } - const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4' + const downloadConfig = await generateArgsForSubscription(sub, user_uid); + + // get videos + logger.verbose('Subscription: getting videos for subscription ' + sub.name); + + return new Promise(resolve => { + youtubedl.exec(sub.url, downloadConfig, {}, async function(err, output) { + logger.verbose('Subscription: finished check for ' + sub.name); + if (err && !output) { + logger.error(err.stderr ? err.stderr : err.message); + if (err.stderr.includes('This video is unavailable')) { + logger.info('An error was encountered with at least one video, backup method will be used.') + try { + const outputs = err.stdout.split(/\r\n|\r|\n/); + for (let i = 0; i < outputs.length; i++) { + const output = JSON.parse(outputs[i]); + handleOutputJSON(sub, sub_db, output, i === 0, multiUserMode) + if (err.stderr.includes(output['id']) && archive_path) { + // we found a video that errored! add it to the archive to prevent future errors + if (sub.archive) { + archive_dir = sub.archive; + archive_path = path.join(archive_dir, 'archive.txt') + fs.appendFileSync(archive_path, output['id']); + } + } + } + } catch(e) { + logger.error('Backup method failed. See error below:'); + logger.error(e); + } + } + resolve(false); + } else if (output) { + if (output.length === 0 || (output.length === 1 && output[0] === '')) { + logger.verbose('No additional videos to download for ' + sub.name); + resolve(true); + } + for (let i = 0; i < output.length; i++) { + let output_json = null; + try { + output_json = JSON.parse(output[i]); + } catch(e) { + output_json = null; + } + if (!output_json) { + continue; + } + + const reset_videos = i === 0; + handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos); + await setFreshUploads(sub, user_uid); + checkVideosForFreshUploads(sub, user_uid); + } + resolve(true); + } + }); + }, err => { + logger.error(err); + }); +} + +async function generateArgsForSubscription(sub, user_uid, redownload = false, desired_path = null) { + // get basePath + let basePath = null; + if (user_uid) + basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions'); + else + basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); + + const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); + + let appendedBasePath = getAppendedBasePath(sub, basePath); let fullOutput = `${appendedBasePath}/%(title)s.%(ext)s`; - if (sub.custom_output) { + if (desired_path) { + fullOutput = `${desired_path}.%(ext)s`; + } else if (sub.custom_output) { fullOutput = `${appendedBasePath}/${sub.custom_output}.%(ext)s`; } - let downloadConfig = ['-o', fullOutput, '-ciw', '--write-info-json', '--print-json']; + let downloadConfig = ['-o', fullOutput, !redownload ? '-ciw' : '-ci', '--write-info-json', '--print-json']; let qualityPath = null; if (sub.type && sub.type === 'audio') { @@ -320,7 +390,7 @@ async function getVideosForSub(sub, user_uid = null) { let archive_dir = null; let archive_path = null; - if (useArchive) { + if (useArchive && !redownload) { if (sub.archive) { archive_dir = sub.archive; archive_path = path.join(archive_dir, 'archive.txt') @@ -350,60 +420,7 @@ async function getVideosForSub(sub, user_uid = null) { downloadConfig.push('--write-thumbnail'); } - // get videos - logger.verbose('Subscription: getting videos for subscription ' + sub.name); - - return new Promise(resolve => { - youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) { - logger.verbose('Subscription: finished check for ' + sub.name); - if (err && !output) { - logger.error(err.stderr ? err.stderr : err.message); - if (err.stderr.includes('This video is unavailable')) { - logger.info('An error was encountered with at least one video, backup method will be used.') - try { - const outputs = err.stdout.split(/\r\n|\r|\n/); - for (let i = 0; i < outputs.length; i++) { - const output = JSON.parse(outputs[i]); - handleOutputJSON(sub, sub_db, output, i === 0, multiUserMode) - if (err.stderr.includes(output['id']) && archive_path) { - // we found a video that errored! add it to the archive to prevent future errors - fs.appendFileSync(archive_path, output['id']); - } - } - } catch(e) { - logger.error('Backup method failed. See error below:'); - logger.error(e); - } - } - resolve(false); - } else if (output) { - if (output.length === 0 || (output.length === 1 && output[0] === '')) { - logger.verbose('No additional videos to download for ' + sub.name); - resolve(true); - } - for (let i = 0; i < output.length; i++) { - let output_json = null; - try { - output_json = JSON.parse(output[i]); - } catch(e) { - output_json = null; - } - if (!output_json) { - continue; - } - - const reset_videos = i === 0; - handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos); - - // TODO: Potentially store downloaded files in db? - - } - resolve(true); - } - }); - }, err => { - logger.error(err); - }); + return downloadConfig; } function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_videos = false) { @@ -418,6 +435,14 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_ // add to db sub_db.get('videos').push(output_json).write(); } else { + path_object = path.parse(output_json['_filename']); + const path_string = path.format(path_object); + + if (sub_db.get('videos').find({path: path_string}).value()) { + // file already exists in DB, return early to avoid reseting the download date + return; + } + db_api.registerFileDB(path.basename(output_json['_filename']), sub.type, multiUserMode, sub); const url = output_json['webpage_url']; if (sub.type === 'video' && url.includes('twitch.tv/videos/') && url.split('twitch.tv/videos/').length > 1 @@ -468,6 +493,53 @@ function subExists(subID, user_uid = null) { return !!db.get('subscriptions').find({id: subID}).value(); } +async function setFreshUploads(sub, user_uid) { + const current_date = new Date().toISOString().split('T')[0].replace(/-/g, ''); + sub.videos.forEach(async video => { + if (current_date === video['upload_date'].replace(/-/g, '')) { + // set upload as fresh + const video_uid = video['uid']; + await db_api.setVideoProperty(video_uid, {'fresh_upload': true}, user_uid, sub['id']); + } + }); +} + +async function checkVideosForFreshUploads(sub, user_uid) { + const current_date = new Date().toISOString().split('T')[0].replace(/-/g, ''); + sub.videos.forEach(async video => { + if (video['fresh_upload'] && current_date > video['upload_date'].replace(/-/g, '')) { + checkVideoIfBetterExists(video, sub, user_uid) + } + }); +} + +async function checkVideoIfBetterExists(file_obj, sub, user_uid) { + const new_path = file_obj['path'].substring(0, file_obj['path'].length - 4); + const downloadConfig = generateArgsForSubscription(sub, user_uid, true, new_path); + logger.verbose(`Checking if a better version of the fresh upload ${file_obj['id']} exists.`); + // simulate a download to verify that a better version exists + youtubedl.getInfo(file_obj['url'], downloadConfig, (err, output) => { + if (err) { + // video is not available anymore for whatever reason + } else if (output) { + console.log(output); + const metric_to_compare = sub.type === 'audio' ? 'abr' : 'height'; + if (output[metric_to_compare] > file_obj[metric_to_compare]) { + // download new video as the simulated one is better + youtubedl.exec(file_obj['url'], downloadConfig, async (err, output) => { + if (err) { + logger.verbose(`Failed to download better version of video ${file_obj['id']}`); + } else if (output) { + logger.verbose(`Successfully upgraded video ${file_obj['id']}'s ${metric_to_compare} from ${file_obj[metric_to_compare]} to ${output[metric_to_compare]}`); + await db_api.setVideoProperty(file_obj['uid'], {[metric_to_compare]: output[metric_to_compare]}, user_uid, sub['id']); + } + }); + } + } + }); + await db_api.setVideoProperty(file_obj['uid'], {'fresh_upload': false}, user_uid, sub['id']); +} + // helper functions function getAppendedBasePath(sub, base_path) { diff --git a/backend/utils.js b/backend/utils.js index 988a112..b18ed6a 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -41,18 +41,12 @@ async function getDownloadedFilesByType(basePath, type, full_metadata = false) { files.push(jsonobj); continue; } - var title = jsonobj.title; - var url = jsonobj.webpage_url; - var uploader = jsonobj.uploader; var upload_date = jsonobj.upload_date; upload_date = upload_date ? `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}` : null; - var thumbnail = jsonobj.thumbnail; - var duration = jsonobj.duration; - - var size = stats.size; var isaudio = type === 'audio'; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); + var file_obj = new File(id, jsonobj.title, jsonobj.thumbnail, isaudio, jsonobj.duration, jsonobj.webpage_url, jsonobj.uploader, + stats.size, file, upload_date, jsonobj.description, jsonobj.view_count, jsonobj.height, jsonobj.abr); files.push(file_obj); } return files; @@ -189,7 +183,7 @@ async function recFindByExt(base,ext,files,result) // objects -function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description) { +function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) { this.id = id; this.title = title; this.thumbnailURL = thumbnailURL; @@ -201,6 +195,9 @@ function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, p this.path = path; this.upload_date = upload_date; this.description = description; + this.view_count = view_count; + this.height = height; + this.abr = abr; } module.exports = { From cd93313cfc3a26a0cd113466567f55dc4cd47a17 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 18 Dec 2020 18:34:30 -0500 Subject: [PATCH 056/250] Updated Chrome/Firefox extension to 0.4 --- chrome-extension/background.js | 20 -------------- chrome-extension/manifest.json | 9 ++++-- chrome-extension/popup.html | 35 ++++++++++++++++++++++++ chrome-extension/popup.js | 50 ++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 23 deletions(-) delete mode 100644 chrome-extension/background.js create mode 100644 chrome-extension/popup.html create mode 100644 chrome-extension/popup.js diff --git a/chrome-extension/background.js b/chrome-extension/background.js deleted file mode 100644 index 4785a23..0000000 --- a/chrome-extension/background.js +++ /dev/null @@ -1,20 +0,0 @@ -// background.js - -// Called when the user clicks on the browser action. -chrome.browserAction.onClicked.addListener(function(tab) { - // get the frontend_url - chrome.storage.sync.get({ - frontend_url: 'http://localhost', - audio_only: false - }, function(items) { - chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - var activeTab = tabs[0]; - var url = activeTab.url; - if (url.includes('youtube.com')) { - var new_url = items.frontend_url + '/#/home;url=' + encodeURIComponent(url) + ';audioOnly=' + items.audio_only; - chrome.tabs.create({ url: new_url }); - } - }); - }); - -}); \ No newline at end of file diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 1ecf992..f5f4f79 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -1,17 +1,20 @@ { "manifest_version": 2, "name": "YoutubeDL-Material", - "version": "0.3", + "version": "0.4", "description": "The Official Firefox & Chrome Extension of YoutubeDL-Material, an open-source and self-hosted YouTube downloader.", "background": { "scripts": ["background.js"] }, "browser_action": { - "default_icon": "favicon.png" + "default_icon": "favicon.png", + "default_popup": "popup.html", + "default_title": "YoutubeDL-Material" }, "permissions": [ "tabs", - "storage" + "storage", + "contextMenus" ], "options_ui": { "page": "options.html", diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html new file mode 100644 index 0000000..467925c --- /dev/null +++ b/chrome-extension/popup.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/chrome-extension/popup.js b/chrome-extension/popup.js new file mode 100644 index 0000000..9e48758 --- /dev/null +++ b/chrome-extension/popup.js @@ -0,0 +1,50 @@ +function audioOnlyClicked() { + console.log('audio only clicked'); + var audio_only = document.getElementById("audio_only").checked; + + // save state + + chrome.storage.sync.set({ + audio_only: audio_only + }, function() {}); +} + +function downloadVideo() { + var input_url = document.getElementById("url_input").value + // get the frontend_url + chrome.storage.sync.get({ + frontend_url: 'http://localhost', + audio_only: false + }, function(items) { + var download_url = items.frontend_url + '/#/home;url=' + encodeURIComponent(input_url) + ';audioOnly=' + items.audio_only; + chrome.tabs.create({ url: download_url }); + }); +} + +function loadInputs() { + // load audio-only input + chrome.storage.sync.get({ + frontend_url: 'http://localhost', + audio_only: false + }, function(items) { + document.getElementById("audio_only").checked = items.audio_only; + }); + + // load url input + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + var activeTab = tabs[0]; + var current_url = activeTab.url; + console.log(current_url); + if (current_url && current_url.includes('youtube.com')) { + document.getElementById("url_input").value = current_url; + } + }); +} + +document.getElementById('download').addEventListener('click', + downloadVideo); + +document.getElementById('audio_only').addEventListener('click', + audioOnlyClicked); + +document.addEventListener('DOMContentLoaded', loadInputs); From 5f1320501729826bb2ee3b7ccf6523c01f9cb8c9 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 18 Dec 2020 18:36:39 -0500 Subject: [PATCH 057/250] Removed background script declaration from the Chrome/Firefox extension --- chrome-extension/manifest.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index f5f4f79..bf13650 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -3,9 +3,6 @@ "name": "YoutubeDL-Material", "version": "0.4", "description": "The Official Firefox & Chrome Extension of YoutubeDL-Material, an open-source and self-hosted YouTube downloader.", - "background": { - "scripts": ["background.js"] - }, "browser_action": { "default_icon": "favicon.png", "default_popup": "popup.html", From 0fec9d71a0fb9cffef3cae7ae33f4a30c6a52e9b Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 18 Dec 2020 18:41:25 -0500 Subject: [PATCH 058/250] Updated Chrome and Firefox extension zips --- .../youtubedl-material-chrome-extension.zip | Bin 75374 -> 76040 bytes .../youtubedl-material-firefox-extension.zip | Bin 75374 -> 76109 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/chrome-extension/youtubedl-material-chrome-extension.zip b/chrome-extension/youtubedl-material-chrome-extension.zip index 87bb073ab0d26c6fa5f1b1ffb849cb9cab7d73bd..5cbca64eae8fa276ec23bf513632094d05d03d78 100644 GIT binary patch delta 2374 zcmZXV2|SefAIG0(h>WdqL_*9OMpkBQOSP;V*&MNE9P8L>C31`_SB~=h$u(NnG>o-a z6KfL|A(fmtLbk?a$S{_YL@39W9RJ76&Nl4K>ow2sJFnm8{rf)O&+qm9&OQ=O>J!Ej zP6`R{0X9FUEEfpe#Xmp7@bg@7@V`5DZtke=0F$kSHWzj_(PFCGHA1k)XS-y{N#qb$ zvYvc1gu5u+5Zk{?I+H>ijcR+l4+H>xT8knm0}%{J540slgg&5JkN@6A?b9F`7AN4G zEj3Quwy82=iH&G{NnQWjl00)b6r=NMmJ<~@cZ?=3X~Q^KDkI@2I$y+)Rk%>s{gq}^ zQs~=+nB%+B@YCh)^Da4VajTWd?03;Vbw()zsS92MG2srDM>4+1uN95Iaz_%C*QwOT zY=&H~B^#-|gv#T7k`@iKmb)ZW<+~$>IK7)px2B#hN|H@u75KK$u4N28&uDhM)paIl zcE+~L^-f>!vtml(;=#NTvzV{{q>JM~o3vZcIjdLC^}ZY$|8u^A@vG1%$#P6XiUX0W z%BD>6(Xqs%wFN_N%NjRYRa$2C)xE$xU+k{ zy+6vgiEBcgz7lAJ*sNq-G#cyxA)bXhfygup(W6BqyB01f0`?CH+YuNL7#fK43i0zz zv9k_fYk$^&8iZU!9drLMs11vukN7nwc<9gAzq*o=*-gQAkyht4d%|Y0S}sBf2i_UH zBcpq&>y_)8Hr7`~LVXu|Cv7IiOtDO{Db2?R;#20k+X3o#5GUP0;{J_?xIOid)=KJ` zGMwXN>4~5Fap}L@k|P8J=7#QGGh+-7$zAuH)oL}F)e?`j@VH$`&O0&qGztHd$XZn6VSh9W`>+r&&%jw6KiO4t0 z15dP@99|+>Q1g&iMb`m$i){2HIS^1ZGO3oJ49T}+>OOj03YUC-!*f!R&`~StHW}C= zqNdsEQZmz^dk<3~(X>o_RcMx(T=I0r0>NAhZNGbLB$BP?b+q9qF&}g$OfOw-%peA| zUB1<6ieeqHcam=C7tTG8AeE}EB(Hv0$^#aX8D)OZRQe!F%&e5#zI2fHy+orPlc`QRwZQz^pnZnz<)XOs4NgYW8{gaYbxZBbuX>qL zg$&>t(}{3*nV%5Ah3z9O#Ve zWTzam6*Qb0smNYK5l(^#L*U;wPpeb`6`bI^!|m@#0KK^cKtK+NhH&J8(=2eU=YTz> zp9P|QdGRLzw*e8BQg$+3tN~y_7Xioss$4z5<){EO3HDXeC{U>cXH9XVUC`*BX|4=>Xk+DTi%Gz`=sZ- z5lvYYR6r0NY2T=VpPOF4%RBT-M_%&$c$nqIrD4Bj|D|rrYGDEc5OLcT7gDV0~OTKhf47@xcXBG z54g|&$@8iqNP@=*tsLHra8#sqtAX@yaVpI0Pq?7^@C??yed3of&T+=BfXIT delta 1653 zcmeCU#PaS8OMQShGm8iV0|N)c#5J)2oyHApIgAVpSC|+Wc!8oxiOJdNMfs(9DSBDO zxdFc3haCj=vVW3ivr##^>EoP7j#WY|jw1X@shh2H7u}w_OI~;Bm;KhauWSi>zR5cO z-kzD}p{%_j^SRwGW_^74S?9dOY_H^i!^>Q`7rJ)H$!c6)S?|w(#kqH}uhUX4*~p3M zt4g=H6drkBHRFv*=|Zuf*X`%nx4bx2xuv~GGR3$lb{->pe{6z9#(CMuv{#WO%k<@r z)+(rr|H)e`bVe^YUw;3rER95BBX>bx8m)ZWxo9Lw2L11KhV5($#MNB9qApd zE>*iL=l@CFsJ)Iq=HO4()w7*Xu6VJ=C~&cUx9(8^X_2mJM>A&BuFNSketngH+0mbh z52d699DZ4_`c+A;zRZSd8=ej zH9Xa`T-(0%+v4tJe<$#5y{vxz$8TO(%rG#hY%ZM0E6)t1HtKizG6N%O^B&(iPLRN4 z+i*D$y*VzN%?Qj$?_5;^W{CXbl&uF!1^84-&W!{{>UwCT<|gK4rll5_0HZWNuQcec z*I@&Zed0Ck6S&uJGZGOjQ~lBY;6s|Zl5)yR&+3@TQ|6S+K6USI%G+C(8%1W#`8@Z% z`q}S4CA;~}FD+u@O5&Y&A<`~&`r>uzOB;7Q&tJ!RR_I;)F=NRCi`V>MtT*!c=vj7k zda=UOjeN1$aZJ@$r9x#=`(HTj*4>>@zOzxq`bM3aQunXdWF1V zw}6+0FEt$Ixjidvd7783tzP^gGCxK0>Z%XxHdcPQn0n7Vs$OLEd-nxzntv>EeOtx# z-i#&AW9lOwlh^>=*;*VSxk0+uGiTOc-S$A;#ic7P)-Yb@Q-x}8NEc@j;qtAbVXrSh!Eo(Ubd3u^^^wfI^v@~p6AiR?4$|>2b zTG7uc|9yg1-JJY?%NJ3Gx4DTbVKa-C85=+OyrRkA;>C-syY`(_t2yxY(WbvgzOw@( zkPDi(fYGo$MTXIV2^2Him&-98W2|RnvS+}R^HhL&4+IpzBw9Y?f(kP-NH9o6&b^lT z{wgDketzXq{Hu3Y83Vjw#)Fej0Mr~X#liqG-WkLe0OIMuq>)@)tdDH$kAofsXZ~De zgwdOQAbfmJXJPU4hY&UbTm8fSm(`+4v2em%eM&-?w^=k@j>yt@x>KyVO) zD+2tF*TGqWVDZlv9D4eMhyR-)$Mvzv_X zY$#hzl^g778OBYW@VV(7OQM&uO zQUXn{IUv-Gj67LvVz|Ge)Ll09CbD>r5=>)W(dYD_4zi4@v$Q|esqCDMvVSRELeC!C zTk@o$wAQjZt7IINSi8#&Y0_#s<*5>vi~J?rWrgN5w!m%;8ksuui`X#oz^tO$&|eQo zk6!z6G-OvLOS6W(vb-oB8The#0zW=tjp0a5 z>OS6nG39+{E0B4JI+SiMTUY!5tH`42FQgv5gLR*{ZS(U}Yh;ra^_#8U>PyGl``#<3U9%Z@a>?Kc*>@Xr{u4n?E;U?7228t;VxCUQcPzk_kiw+W z{OpErcuzZbluq~ktXHzUALBM=-$6RoG#))O-```%cKZXtr8YkIE9=;P(*7!Pf<^%} zEUhYK>Ce?#(KTOu&!3fhReX9vgV0tj=RHAb64%yk_AHsMH@=T9lV#5nUl-bDC6_#z zwuf<+B3oCg`bj#S zGvyA~JW;g^adju}Lbjo|tgZEi8E04cu@Kb3?CvjwEjhu{-aTc=)DwigEMrdIyOJO&F^Dm zD*bT=i*}vM_S}MHtH?XHMC2ptWnz`?L_d_rmK{WznNgGqj1%le!;$LbDhYiwC(?x% zSAy%y>>2#BWYm=@=QZ9&J!f`qU?ovkg29!R+1np|d#RdBzN~#|ZVx6zs(a|9Y4Wv^ zcX{4Gm7#n?j0g5%aEKB28~>@ywhJCSg50snD7C5l$*iW?YM*9UesmbB_Z{I`?PR6I zA%pnr&>#A@9?8xcxrKctWe?Q?PD#(0kEir z2PmN8tBYUSGVi`|D>u+oFxdO6_ovSOVk|7SVZ~S-8Ngkbk2ssSVjTAUiRdl0gTpaT-wWxG-g}XSqOh3nJB21qfyx4Ez&uK97_3C7aVb6c7 zaZhpJLN=#zhB>l`GkDW}mZf|aF?+T=?Sa9p#Sr!A@ria-L^EhH`J(LVk_^ECgjoRp z+6SW?1*(&vvm@{g1c2l(0T56HVyReK{}C>@)V1A}{*((M16Sj(fYnQBE=7{#=>!0? z#xOt$&=TkY0S5&z&}a~?GfZDqvcu4^*$_!cUPodCp+R-4G6;3*M3lczNHCTXd|I?3 zT+u4$bys~npCjac

;+(Ft+P4BZ8`CaTwzFj%uAA@8eRup@@17N`y*x--7l0>81o zUc0t=OO^Wg#-f1YueyRV=%QqEgkmkTGH{CtMNJ#W19bZiDibAHn`1N zShKKq!dO(a#+9M#VAGn{E2uNb+Mv45fo@C|ziV)wX)gcyquOdWL`jg1FiN3hSe^!> zQyWy%8P;D-V%KR4MkkEdi=;82I(2v($hRc_kl=9W4ifGBKHOT2Fc!u9*RVv-QaKV7 zu`f|JPNO*{1KJOp`cI84a^a54&<~>U|K66O@D!8~zP9XB8I>r0s}gq_2zI}zWssgC WAa8(fT)qcy1cD%M?bL!y7Wf--kls`P delta 1829 zcmX?miRIlHmihp1W)=|!1_lm>iECm5I*l9Hau^vHt}rn$@B&4X5|gvji}FkJQuMNl za|3+64?76#W&b44W}|X+)5kfF9IJ#_97Xt*Qa4-YF1kH;m%Q%MFZ-=;U)d7&e3Nzl zy*)F{Ls@%6=5xDW%=-B7v(9;m*FLdpYlhwGqvfiKnigWK`U#F#9vXK+h zSCwvYDLnGNYQ`Ir(uHC{uiMYDZ+UU5a!Y%WWQuW9>^w&H{@4VIjPtUQX|Eznmg&nK ztyNGL|C6^?=!{-)zWn}KSsJ$l!b(FWgD!1(;%sOan0iI_?1@>qyv3O|-YMI?_8@ zU8;6h&i|9TQF|SK%)y_mt7ki(T=8O!QQ%_zZr!5-(jr~cj%LiLU71s8{Q4^YvZFs0 zA4+w-T9tM4XUpZ=O^jQ*O!nE__0Qa?bb8w58-a`b_@>uBIjr_9@O{bK#E0tt^H#~6 zYIv$=xwd`jx5eGd{!ZZAdRhJakKeqom|nFwm?83yQ?~xf#KHidO3AsAz(`#Wjnv%4yv(%J;u2t#=I50L z-Ss+bAhJ)qrhNkU`fWxcf@P{d+8=yKGgnehdFfdlGkMCKlG&&3-A#FW%W|X0tT~_O zzE?l{{ikF%zxky_Y+Oma^Dac%rA}YGE`4d^j_3L7IL`{bi$7*8d0_FHAB^=zJ|8{H zu1+sjc)F1vhbyb!#uZVg)L9>lC{-~KSbuIXkK0QVco{cFBenqnMc)&tbXsl;7#+7MXqnF zxZazw#Cc48#A6a0pgUWOBP2IS_j=~c`m5U>sN0xUG*2md7uxU96cbcrGlTowX(p?- zqm2jO@=tMTR@ZyQGVfc%8lGjpTxaz8FAxpXe6(c^$3IU`Q;nW_FM*bZZ3~1~GF>?( zn^i0NS>?Y^(5jo0|8Mys%J4QfQ6+3<(K2J>C!bd|8C<-0k#*O;lWH{w-agv&_sDm4 zU<7hO^A<3Mwy%_7bYKF-%=R~OjK>)38JX-EaOFG|VBP}(1u%)054oVij0_SCQjv47 zWxl`42&120c@+QZ-Brc_ZkfX4=U((N4S`pLUor$l5_=jiyo9=Lk*)hW9|s4R#X9 zbqYZ2=Fh;8mROdVoS&yxke7~Z_{U`s|H9}8ijRT*t;g?SX1M7BK+}&$Ffe4H`q=3- z#K$lio0EY)4*2zR|GyiC3=C%b85o4&hAJ?yFfhD`n4YS}sHn~v1~CeqM)o}@@YbpU z1Mg$k^iyh#D)rGzAezu=G))0VX7VcBlVM=E8N|RKfMh=d!@U1sPZi`B6r>jE(DLRnNXG`)g2BjGoEguAm7vyLGr^gs2HyYiEk From 984757743155838881e8462b5ed790d50349b3b6 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 00:24:36 -0500 Subject: [PATCH 059/250] Added setting for redownloading fresh uploads Fixed bug in implementation of fresh upload redownloader --- backend/appdata/default.json | 3 ++- backend/config.js | 3 ++- backend/consts.js | 4 ++++ backend/subscriptions.js | 9 ++++++--- src/app/settings/settings.component.html | 5 ++++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backend/appdata/default.json b/backend/appdata/default.json index 001f515..dca9eee 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -38,7 +38,8 @@ "Subscriptions": { "allow_subscriptions": true, "subscriptions_base_path": "subscriptions/", - "subscriptions_check_interval": "300" + "subscriptions_check_interval": "300", + "redownload_fresh_uploads": false }, "Users": { "base_path": "users/", diff --git a/backend/config.js b/backend/config.js index b2aff63..4790e34 100644 --- a/backend/config.js +++ b/backend/config.js @@ -215,7 +215,8 @@ DEFAULT_CONFIG = { "Subscriptions": { "allow_subscriptions": true, "subscriptions_base_path": "subscriptions/", - "subscriptions_check_interval": "300" + "subscriptions_check_interval": "300", + "redownload_fresh_uploads": false }, "Users": { "base_path": "users/", diff --git a/backend/consts.js b/backend/consts.js index bffe486..64e6e09 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -126,6 +126,10 @@ let CONFIG_ITEMS = { 'key': 'ytdl_subscriptions_check_interval', 'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_check_interval' }, + 'ytdl_subscriptions_redownload_fresh_uploads': { + 'key': 'ytdl_subscriptions_redownload_fresh_uploads', + 'path': 'YoutubeDLMaterial.Subscriptions.redownload_fresh_uploads' + }, // Users 'ytdl_users_base_path': { diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 7ce2a96..396516d 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -333,9 +333,13 @@ async function getVideosForSub(sub, user_uid = null) { const reset_videos = i === 0; handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos); + } + + if (config_api.getConfigItem('ytdl_subscriptions_redownload_fresh_uploads')) { await setFreshUploads(sub, user_uid); checkVideosForFreshUploads(sub, user_uid); } + resolve(true); } }); @@ -403,7 +407,7 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de downloadConfig = ['-f', 'best', '--dump-json']; } - if (sub.timerange) { + if (sub.timerange && !redownload) { downloadConfig.push('--dateafter', sub.timerange); } @@ -515,14 +519,13 @@ async function checkVideosForFreshUploads(sub, user_uid) { async function checkVideoIfBetterExists(file_obj, sub, user_uid) { const new_path = file_obj['path'].substring(0, file_obj['path'].length - 4); - const downloadConfig = generateArgsForSubscription(sub, user_uid, true, new_path); + const downloadConfig = await generateArgsForSubscription(sub, user_uid, true, new_path); logger.verbose(`Checking if a better version of the fresh upload ${file_obj['id']} exists.`); // simulate a download to verify that a better version exists youtubedl.getInfo(file_obj['url'], downloadConfig, (err, output) => { if (err) { // video is not available anymore for whatever reason } else if (output) { - console.log(output); const metric_to_compare = sub.type === 'audio' ? 'abr' : 'height'; if (output[metric_to_compare] > file_obj[metric_to_compare]) { // download new video as the simulated one is better diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 08d4970..bd085b1 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -53,12 +53,15 @@ Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder.

-
+
Unit is seconds, only include numbers.
+
+ Redownload fresh uploads +
From 59c38321fd938c3d3a609c4895c7317f0c157628 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 01:46:19 -0500 Subject: [PATCH 060/250] Fixed bug in file deletion --- backend/app.js | 4 ++-- .../components/recent-videos/recent-videos.component.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/backend/app.js b/backend/app.js index 6476136..d3f9d57 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2570,7 +2570,7 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { var blacklistMode = req.body.blacklistMode; if (req.isAuthenticated()) { - let success = auth_api.deleteUserFile(req.user.uid, uid, blacklistMode); + let success = await auth_api.deleteUserFile(req.user.uid, uid, blacklistMode); res.send(success); return; } @@ -2583,7 +2583,7 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { { wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode); db.get('files').remove({uid: uid}).write(); - // wasDeleted = true; + wasDeleted = true; res.send(wasDeleted); } else if (video_obj) { db.get('files').remove({uid: uid}).write(); diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index a0667c7..e745138 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -127,9 +127,11 @@ export class RecentVideosComponent implements OnInit { this.normal_files_received = false; this.postsService.getAllFiles().subscribe(res => { this.files = res['files']; - this.files.forEach(file => { + for (let i = 0; i < this.files.length; i++) { + const file = this.files[i]; file.duration = typeof file.duration !== 'string' ? file.duration : this.durationStringToNumber(file.duration); - }); + file.index = i; + } this.files.sort(this.sortFiles); if (this.search_mode) { this.filterFiles(this.search_text); @@ -247,7 +249,8 @@ export class RecentVideosComponent implements OnInit { this.postsService.deleteFile(file.uid, file.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => { if (result) { this.postsService.openSnackBar('Delete success!', 'OK.'); - this.files.splice(index, 1); + this.files.splice(file.index, 1); + this.filterByProperty(this.filterProperty['property']); } else { this.postsService.openSnackBar('Delete failed!', 'OK.'); } From eb7661c14a3e7078f7a43944bec316d80f016659 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 02:06:52 -0500 Subject: [PATCH 061/250] Fixed bug in file deletion where file indexes became stale --- src/app/components/recent-videos/recent-videos.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index e745138..2da73f0 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -250,6 +250,7 @@ export class RecentVideosComponent implements OnInit { if (result) { this.postsService.openSnackBar('Delete success!', 'OK.'); this.files.splice(file.index, 1); + for (let i = 0; i < this.files.length; i++) { this.files[i].index = i } this.filterByProperty(this.filterProperty['property']); } else { this.postsService.openSnackBar('Delete failed!', 'OK.'); From 441a470990a7a689897ee0e4a0e10e179293d527 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 03:51:30 -0500 Subject: [PATCH 062/250] Added ability to view playlist in reverse order in the playlist editing dialog --- .../modify-playlist.component.html | 20 ++++++++++++++----- .../modify-playlist.component.scss | 5 ----- .../modify-playlist.component.ts | 15 ++++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/app/dialogs/modify-playlist/modify-playlist.component.html b/src/app/dialogs/modify-playlist/modify-playlist.component.html index d35db91..69f4cad 100644 --- a/src/app/dialogs/modify-playlist/modify-playlist.component.html +++ b/src/app/dialogs/modify-playlist/modify-playlist.component.html @@ -8,14 +8,24 @@
+
+
+ Normal order  + Reverse order  + +
+ +
+ +
+
+ -
{{playlist_item}}
+ +
{{playlist_item}}
- -
- -
+ diff --git a/src/app/dialogs/modify-playlist/modify-playlist.component.scss b/src/app/dialogs/modify-playlist/modify-playlist.component.scss index 84d3c54..0be9a78 100644 --- a/src/app/dialogs/modify-playlist/modify-playlist.component.scss +++ b/src/app/dialogs/modify-playlist/modify-playlist.component.scss @@ -30,11 +30,6 @@ border: none; transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } -.add-content-button { -margin-top: 15px; -margin-bottom: 10px; -} - .remove-item-button { right: 10px; position: absolute; diff --git a/src/app/dialogs/modify-playlist/modify-playlist.component.ts b/src/app/dialogs/modify-playlist/modify-playlist.component.ts index 18db662..414fc92 100644 --- a/src/app/dialogs/modify-playlist/modify-playlist.component.ts +++ b/src/app/dialogs/modify-playlist/modify-playlist.component.ts @@ -15,6 +15,7 @@ export class ModifyPlaylistComponent implements OnInit { available_files = []; all_files = []; playlist_updated = false; + reverse_order = false; constructor(@Inject(MAT_DIALOG_DATA) public data: any, private postsService: PostsService, @@ -26,6 +27,8 @@ export class ModifyPlaylistComponent implements OnInit { this.original_playlist = JSON.parse(JSON.stringify(this.data.playlist)); this.getFiles(); } + + this.reverse_order = localStorage.getItem('default_playlist_order_reversed') === 'true'; } getFiles() { @@ -72,11 +75,23 @@ export class ModifyPlaylistComponent implements OnInit { } removeContent(index) { + if (this.reverse_order) { + index = this.playlist.fileNames.length - 1 - index; + } this.playlist.fileNames.splice(index, 1); this.processFiles(); } + togglePlaylistOrder() { + this.reverse_order = !this.reverse_order; + localStorage.setItem('default_playlist_order_reversed', '' + this.reverse_order); + } + 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; + } moveItemInArray(this.playlist.fileNames, event.previousIndex, event.currentIndex); } From e75b56ad3fe1efa9598f6cfe4be5ca50a577cbc0 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 04:12:27 -0500 Subject: [PATCH 063/250] Added ability to pause specific subscriptions --- backend/app.js | 8 +++++--- .../edit-subscription-dialog.component.html | 7 +++++-- .../subscription-info-dialog.component.html | 2 +- .../subscription/subscription/subscription.component.html | 2 +- src/app/subscriptions/subscriptions.component.html | 4 ++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/backend/app.js b/backend/app.js index d3f9d57..30e19b4 100644 --- a/backend/app.js +++ b/backend/app.js @@ -701,12 +701,14 @@ async function watchSubscriptions() { if (!subscriptions) return; - let subscriptions_amount = subscriptions.length; + const valid_subscriptions = subscriptions.filter(sub => !sub.paused); + + let subscriptions_amount = valid_subscriptions.length; let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount); let current_delay = 0; - for (let i = 0; i < subscriptions.length; i++) { - let sub = subscriptions[i]; + for (let i = 0; i < valid_subscriptions.length; i++) { + let sub = valid_subscriptions[i]; // don't check the sub if the last check for the same subscription has not completed if (subscription_timeouts[sub.id]) { diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html index 00d5485..f76af9f 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html @@ -1,8 +1,11 @@ -

Editing

 {{sub.name}} +

Editing {{sub.name}} (Paused)

+
+ Paused +
Download all uploads
@@ -31,7 +34,7 @@
-
+
Streaming-only mode
diff --git a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html index 1ebff5a..4517a47 100644 --- a/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html +++ b/src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html @@ -1,4 +1,4 @@ -

{{sub.name}}

+

{{sub.name}} (Paused)

diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index 0d8c917..e404df3 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -2,7 +2,7 @@

- {{subscription.name}} + {{subscription.name}} (Paused)

diff --git a/src/app/subscriptions/subscriptions.component.html b/src/app/subscriptions/subscriptions.component.html index 5bf76bb..1c8e838 100644 --- a/src/app/subscriptions/subscriptions.component.html +++ b/src/app/subscriptions/subscriptions.component.html @@ -9,7 +9,7 @@ - {{ sub.name }} + {{ sub.name }} (Paused)
Name not available. Channel retrieval in progress.
@@ -28,7 +28,7 @@
- {{ sub.name }} + {{ sub.name }} (Paused)
Name not available. Playlist retrieval in progress.
From 7e06d30205ec81deb970af37c09e52430b441d60 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 13:03:49 -0500 Subject: [PATCH 064/250] 401 unauthorized requests now redirect users to the login page --- src/app/app.module.ts | 6 ++-- src/app/components/login/login.component.ts | 2 +- src/app/http.interceptor.ts | 34 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/app/http.interceptor.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6ef4efd..0ae868f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -32,7 +32,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; import { ClipboardModule } from '@angular/cdk/clipboard'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; -import { HttpClientModule, HttpClient } from '@angular/common/http'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { PostsService } from 'app/posts.services'; import { FileCardComponent } from './file-card/file-card.component'; import { RouterModule } from '@angular/router'; @@ -85,6 +85,7 @@ import { CustomPlaylistsComponent } from './components/custom-playlists/custom-p import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit-category-dialog.component'; import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component'; import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component'; +import { H401Interceptor } from './http.interceptor'; registerLocaleData(es, 'es'); @@ -191,7 +192,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible SettingsComponent ], providers: [ - PostsService + PostsService, + { provide: HTTP_INTERCEPTORS, useClass: H401Interceptor, multi: true } ], exports: [ HighlightPipe, diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index d5c5816..edf7f99 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -27,7 +27,7 @@ export class LoginComponent implements OnInit { constructor(private postsService: PostsService, private snackBar: MatSnackBar, private router: Router) { } ngOnInit(): void { - if (this.postsService.isLoggedIn) { + if (this.postsService.isLoggedIn && localStorage.getItem('jwt_token') !== 'null') { this.router.navigate(['/home']); } this.postsService.service_initialized.subscribe(init => { diff --git a/src/app/http.interceptor.ts b/src/app/http.interceptor.ts new file mode 100644 index 0000000..edde22b --- /dev/null +++ b/src/app/http.interceptor.ts @@ -0,0 +1,34 @@ +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +@Injectable() +export class H401Interceptor implements HttpInterceptor { + + constructor(private router: Router, private snackBar: MatSnackBar) { } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe(catchError(err => { + if (err.status === 401) { + localStorage.setItem('jwt_token', null); + if (this.router.url !== '/login') { + this.router.navigate(['/login']).then(() => { + this.openSnackBar('Login expired, please login again.'); + }); + } + } + + const error = err.error.message || err.statusText; + return throwError(error); + })); + } + + public openSnackBar(message: string, action: string = '') { + this.snackBar.open(message, action, { + duration: 2000, + }); + } +} \ No newline at end of file From 4cbfab20e00c6d888daa14441746a4a2c4a9af45 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 19 Dec 2020 14:33:19 -0500 Subject: [PATCH 065/250] Added category info to video info dialog (if present) - you can now search by category on the home page --- src/app/components/recent-videos/recent-videos.component.ts | 2 +- .../video-info-dialog/video-info-dialog.component.html | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 2da73f0..7ca6c9b 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -98,7 +98,7 @@ export class RecentVideosComponent implements OnInit { private filterFiles(value: string) { const filterValue = value.toLowerCase(); - this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue)); + this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue) || option.category?.toLowerCase().includes(filterValue)); this.pageChangeEvent({pageSize: this.pageSize, pageIndex: this.paginator.pageIndex}); } diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html index 7323385..203cfd5 100644 --- a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html @@ -25,6 +25,10 @@
Upload Date: 
{{file.upload_date ? file.upload_date : 'N/A'}}
+
+
Category: 
+
{{file.category}}N/A
+
From afb5e3800c675416f657ee718d04b2559307b15f Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 20 Dec 2020 00:30:48 -0500 Subject: [PATCH 066/250] In the subscription page, the subscription is now continuously retrieved at an interval of 1s to update for new videos or the downloading state - There is now a visual indicator for when a subscription is retrieving videos --- Public API v1.yaml | 4 +- backend/app.js | 24 ++++----- backend/subscriptions.js | 50 ++++++++++++++++--- src/app/posts.services.ts | 2 +- .../subscription/subscription.component.html | 1 + .../subscription/subscription.component.ts | 25 ++++------ 6 files changed, 68 insertions(+), 38 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index e82328a..b57c386 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -261,12 +261,12 @@ paths: $ref: '#/components/schemas/inline_response_200_10' security: - Auth query parameter: [] - /api/getAllSubscriptions: + /api/getSubscriptions: post: tags: - subscriptions summary: Get all subscriptions - operationId: post-api-getAllSubscriptions + operationId: post-api-getSubscriptions requestBody: content: application/json: diff --git a/backend/app.js b/backend/app.js index 0f6db8c..e1790fd 100644 --- a/backend/app.js +++ b/backend/app.js @@ -628,6 +628,9 @@ async function loadConfig() { // get subscriptions if (allowSubscriptions) { + // set downloading to false + let subscriptions = subscriptions_api.getAllSubscriptions(); + subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false}); // runs initially, then runs every ${subscriptionCheckInterval} seconds watchSubscriptions(); setInterval(() => { @@ -686,18 +689,7 @@ function calculateSubcriptionRetrievalDelay(subscriptions_amount) { } async function watchSubscriptions() { - let subscriptions = null; - - const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); - if (multiUserMode) { - subscriptions = []; - let users = users_db.get('users').value(); - for (let i = 0; i < users.length; i++) { - if (users[i]['subscriptions']) subscriptions = subscriptions.concat(users[i]['subscriptions']); - } - } else { - subscriptions = subscriptions_api.getAllSubscriptions(); - } + let subscriptions = subscriptions_api.getAllSubscriptions(); if (!subscriptions) return; @@ -707,6 +699,8 @@ async function watchSubscriptions() { let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount); let current_delay = 0; + + const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); for (let i = 0; i < valid_subscriptions.length; i++) { let sub = valid_subscriptions[i]; @@ -2026,7 +2020,7 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { let files = null; let playlists = null; - let subscriptions = config_api.getConfigItem('ytdl_allow_subscriptions') ? (subscriptions_api.getAllSubscriptions(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()) { @@ -2456,11 +2450,11 @@ app.post('/api/updateSubscription', optionalJwt, async (req, res) => { }); }); -app.post('/api/getAllSubscriptions', optionalJwt, async (req, res) => { +app.post('/api/getSubscriptions', optionalJwt, async (req, res) => { let user_uid = req.isAuthenticated() ? req.user.uid : null; // get subs from api - let subscriptions = subscriptions_api.getAllSubscriptions(user_uid); + let subscriptions = subscriptions_api.getSubscriptions(user_uid); res.send({ subscriptions: subscriptions diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 396516d..dfcff58 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -255,10 +255,6 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, } async function getVideosForSub(sub, user_uid = null) { - if (!subExists(sub.id, user_uid)) { - return false; - } - // get sub_db let sub_db = null; if (user_uid) @@ -266,6 +262,13 @@ async function getVideosForSub(sub, user_uid = null) { else sub_db = db.get('subscriptions').find({id: sub.id}); + const latest_sub_obj = sub_db.value(); + if (!latest_sub_obj || latest_sub_obj['downloading']) { + return false; + } + + updateSubscriptionProperty(sub, {downloading: true}, user_uid); + // get basePath let basePath = null; if (user_uid) @@ -290,6 +293,7 @@ async function getVideosForSub(sub, user_uid = null) { return new Promise(resolve => { youtubedl.exec(sub.url, downloadConfig, {}, async function(err, output) { + updateSubscriptionProperty(sub, {downloading: false}, user_uid); logger.verbose('Subscription: finished check for ' + sub.name); if (err && !output) { logger.error(err.stderr ? err.stderr : err.message); @@ -319,6 +323,7 @@ async function getVideosForSub(sub, user_uid = null) { if (output.length === 0 || (output.length === 1 && output[0] === '')) { logger.verbose('No additional videos to download for ' + sub.name); resolve(true); + return; } for (let i = 0; i < output.length; i++) { let output_json = null; @@ -345,6 +350,7 @@ async function getVideosForSub(sub, user_uid = null) { }); }, err => { logger.error(err); + updateSubscriptionProperty(sub, {downloading: false}, user_uid); }); } @@ -460,13 +466,28 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_ } } -function getAllSubscriptions(user_uid = null) { +function getSubscriptions(user_uid = null) { if (user_uid) return users_db.get('users').find({uid: user_uid}).get('subscriptions').value(); else return db.get('subscriptions').value(); } +function getAllSubscriptions() { + let subscriptions = null; + const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); + if (multiUserMode) { + subscriptions = []; + let users = users_db.get('users').value(); + for (let i = 0; i < users.length; i++) { + if (users[i]['subscriptions']) subscriptions = subscriptions.concat(users[i]['subscriptions']); + } + } else { + subscriptions = subscriptions_api.getSubscriptions(); + } + return subscriptions; +} + function getSubscription(subID, user_uid = null) { if (user_uid) return users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value(); @@ -490,6 +511,21 @@ function updateSubscription(sub, user_uid = null) { return true; } +function updateSubscriptionPropertyMultiple(subs, assignment_obj) { + subs.forEach(sub => { + updateSubscriptionProperty(sub, assignment_obj, sub.user_uid); + }); +} + +function updateSubscriptionProperty(sub, assignment_obj, user_uid = null) { + if (user_uid) { + users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(assignment_obj).write(); + } else { + db.get('subscriptions').find({id: sub.id}).assign(assignment_obj).write(); + } + return true; +} + function subExists(subID, user_uid = null) { if (user_uid) return !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value(); @@ -580,6 +616,7 @@ async function removeIDFromArchive(archive_path, id) { module.exports = { getSubscription : getSubscription, getSubscriptionByName : getSubscriptionByName, + getSubscriptions : getSubscriptions, getAllSubscriptions : getAllSubscriptions, updateSubscription : updateSubscription, subscribe : subscribe, @@ -588,5 +625,6 @@ module.exports = { getVideosForSub : getVideosForSub, removeIDFromArchive : removeIDFromArchive, setLogger : setLogger, - initialize : initialize + initialize : initialize, + updateSubscriptionPropertyMultiple : updateSubscriptionPropertyMultiple } diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 77acefc..4ae90dd 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -374,7 +374,7 @@ export class PostsService implements CanActivate { } getAllSubscriptions() { - return this.http.post(this.path + 'getAllSubscriptions', {}, this.httpOptions); + return this.http.post(this.path + 'getSubscriptions', {}, this.httpOptions); } // current downloads diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index e404df3..82747ac 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -4,6 +4,7 @@

{{subscription.name}} (Paused)

+

diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index df0561d..5f32f40 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { ActivatedRoute, Router, ParamMap } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; @@ -9,7 +9,7 @@ import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-d templateUrl: './subscription.component.html', styleUrls: ['./subscription.component.scss'] }) -export class SubscriptionComponent implements OnInit { +export class SubscriptionComponent implements OnInit, OnDestroy { id = null; subscription = null; @@ -44,22 +44,11 @@ export class SubscriptionComponent implements OnInit { }; filterProperty = this.filterProperties['upload_date']; downloading = false; - - initialized = false; + sub_interval = null; constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router, private dialog: MatDialog) { } ngOnInit() { - this.route.paramMap.subscribe((params: ParamMap) => { - this.id = params.get('id'); - this.postsService.service_initialized.subscribe(init => { - if (init) { - this.initialized = true; - this.getConfig(); - this.getSubscription(); - } - }); - }); if (this.route.snapshot.paramMap.get('id')) { this.id = this.route.snapshot.paramMap.get('id'); @@ -67,6 +56,7 @@ export class SubscriptionComponent implements OnInit { if (init) { this.getConfig(); this.getSubscription(); + this.sub_interval = setInterval(() => this.getSubscription(), 1000); } }); } @@ -78,6 +68,13 @@ export class SubscriptionComponent implements OnInit { } } + ngOnDestroy() { + // prevents subscription getter from running in the background + if (this.sub_interval) { + clearInterval(this.sub_interval); + } + } + goBack() { this.router.navigate(['/subscriptions']); } From 3f1532b4c609951b1998ef8fca7b7f6bea9bac0c Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 22 Dec 2020 01:23:43 -0500 Subject: [PATCH 067/250] Updated migration - Fixed bug in migration process for single-user mode - Changed name of migration Removed unused code for getmp3/mp4 and fixed bug when retrieving playlist if it didn't exist Fixed bug in streaming code where playlist audio files would not play if the file path was not present Fixed bug in getallsubscriptions for single user mode --- backend/app.js | 32 ++++++++++---------------------- backend/subscriptions.js | 2 +- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/backend/app.js b/backend/app.js index e1790fd..98a054e 100644 --- a/backend/app.js +++ b/backend/app.js @@ -212,8 +212,8 @@ async function checkMigrations() { // 4.1->4.2 migration - const add_description_migration_complete = db.get('add_description_migration_complete').value(); - if (!add_description_migration_complete) { + const simplified_db_migration_complete = db.get('simplified_db_migration_complete').value(); + if (!simplified_db_migration_complete) { logger.info('Beginning migration: 4.1->4.2+') let success = await simplifyDBFileStructure(); success = success && await addMetadataPropertyToDB('view_count'); @@ -276,12 +276,12 @@ async function simplifyDBFileStructure() { } if (db.get('files.video').value() !== undefined && db.get('files.audio').value() !== undefined) { - const files = db.get('files.video').value().concat(db.get('files.audio')); + const files = db.get('files.video').value().concat(db.get('files.audio').value()); db.assign({files: files}).write(); } if (db.get('playlists.video').value() !== undefined && db.get('playlists.audio').value() !== undefined) { - const playlists = db.get('playlists.video').value().concat(db.get('playlists.audio')); + const playlists = db.get('playlists.video').value().concat(db.get('playlists.audio').value()); db.assign({playlists: playlists}).write(); } @@ -303,7 +303,7 @@ async function addMetadataPropertyToDB(property_key) { } // sets migration to complete - db.set('add_description_migration_complete', true).write(); + db.set('simplified_db_migration_complete', true).write(); return true; } catch(err) { logger.error(err); @@ -1935,8 +1935,8 @@ async function addThumbnails(files) { // gets all download mp3s app.get('/api/getMp3s', optionalJwt, async function(req, res) { - var mp3s = db.get('files').chain().find({isAudio: true}).value(); // getMp3s(); - var playlists = db.get('playlists.audio').value(); + var mp3s = db.get('files').value().filter(file => file.isAudio === true); + var playlists = db.get('playlists').value(); const is_authenticated = req.isAuthenticated(); if (is_authenticated) { // get user audio files/playlists @@ -1947,12 +1947,6 @@ app.get('/api/getMp3s', optionalJwt, async function(req, res) { mp3s = JSON.parse(JSON.stringify(mp3s)); - if (config_api.getConfigItem('ytdl_include_thumbnail')) { - // add thumbnails if present - // await addThumbnails(mp3s); - } - - res.send({ mp3s: mp3s, playlists: playlists @@ -1961,7 +1955,7 @@ app.get('/api/getMp3s', optionalJwt, async function(req, res) { // gets all download mp4s app.get('/api/getMp4s', optionalJwt, async function(req, res) { - var mp4s = db.get('files').chain().find({isAudio: false}).value(); // getMp4s(); + var mp4s = db.get('files').value().filter(file => file.isAudio === false); var playlists = db.get('playlists').value(); const is_authenticated = req.isAuthenticated(); @@ -1974,11 +1968,6 @@ app.get('/api/getMp4s', optionalJwt, async function(req, res) { mp4s = JSON.parse(JSON.stringify(mp4s)); - if (config_api.getConfigItem('ytdl_include_thumbnail')) { - // add thumbnails if present - // await addThumbnails(mp4s); - } - res.send({ mp4s: mp4s, playlists: playlists @@ -2501,14 +2490,13 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => { if (req.isAuthenticated()) { playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID); - type = playlist.type; } else { playlist = db.get(`playlists`).find({id: playlistID}).value(); } res.send({ playlist: playlist, - type: type, + type: playlist && playlist.type, success: !!playlist }); }); @@ -2746,7 +2734,7 @@ app.get('/api/stream/:id', optionalJwt, (req, res) => { } if (!file_path) { - file_path = path.join(videoFolderPath, id + ext); + file_path = path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext); } const stat = fs.statSync(file_path) diff --git a/backend/subscriptions.js b/backend/subscriptions.js index dfcff58..4bdca93 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -483,7 +483,7 @@ function getAllSubscriptions() { if (users[i]['subscriptions']) subscriptions = subscriptions.concat(users[i]['subscriptions']); } } else { - subscriptions = subscriptions_api.getSubscriptions(); + subscriptions = getSubscriptions(); } return subscriptions; } From 88a1c310901ad6ca6e3dcb8241ebbffc2b9eff96 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 22 Dec 2020 01:24:27 -0500 Subject: [PATCH 068/250] Removed unused code in home page --- src/app/main/main.component.html | 89 ------------------- src/app/main/main.component.ts | 144 ------------------------------- 2 files changed, 233 deletions(-) diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 59956c8..9cb905e 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -187,92 +187,3 @@

Custom playlists

- - \ No newline at end of file diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 013d0f3..30694bf 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -8,7 +8,6 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { saveAs } from 'file-saver'; import { YoutubeSearchService, Result } from '../youtube-search.service'; import { Router, ActivatedRoute } from '@angular/router'; -import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component'; import { Platform } from '@angular/cdk/platform'; import { v4 as uuid } from 'uuid'; import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; @@ -244,13 +243,6 @@ export class MainComponent implements OnInit { this.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent']; this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent']; - - - if (this.fileManagerEnabled) { - this.getMp3s(); - this.getMp4s(); - } - if (this.youtubeSearchEnabled && this.youtubeAPIKey) { this.youtubeSearch.initializeAPI(this.youtubeAPIKey); this.attachToInput(); @@ -335,61 +327,6 @@ export class MainComponent implements OnInit { this.setCols(); } - // file manager stuff - - getMp3s() { - this.postsService.getMp3s().subscribe(result => { - const mp3s = result['mp3s']; - const playlists = result['playlists']; - // if they are different - if (JSON.stringify(this.mp3s) !== JSON.stringify(mp3s)) { this.mp3s = mp3s }; - this.playlists.audio = playlists; - - // get thumbnail url by using first video. this is a temporary hack - for (let i = 0; i < this.playlists.audio.length; i++) { - const playlist = this.playlists.audio[i]; - let videoToExtractThumbnail = null; - for (let j = 0; j < this.mp3s.length; j++) { - if (this.mp3s[j].id === playlist.fileNames[0]) { - // found the corresponding file - videoToExtractThumbnail = this.mp3s[j]; - } - } - - if (videoToExtractThumbnail) { this.playlist_thumbnails[playlist.id] = videoToExtractThumbnail.thumbnailURL; } - } - }, error => { - console.log(error); - }); - } - - getMp4s() { - this.postsService.getMp4s().subscribe(result => { - const mp4s = result['mp4s']; - const playlists = result['playlists']; - // if they are different - if (JSON.stringify(this.mp4s) !== JSON.stringify(mp4s)) { this.mp4s = mp4s }; - this.playlists.video = playlists; - - // get thumbnail url by using first video. this is a temporary hack - for (let i = 0; i < this.playlists.video.length; i++) { - const playlist = this.playlists.video[i]; - let videoToExtractThumbnail = null; - for (let j = 0; j < this.mp4s.length; j++) { - if (this.mp4s[j].id === playlist.fileNames[0]) { - // found the corresponding file - videoToExtractThumbnail = this.mp4s[j]; - } - } - - if (videoToExtractThumbnail) { this.playlist_thumbnails[playlist.id] = videoToExtractThumbnail.thumbnailURL; } - } - }, - error => { - console.log(error); - }); - } - public setCols() { if (window.innerWidth <= 350) { this.files_cols = 1; @@ -437,44 +374,6 @@ export class MainComponent implements OnInit { return null; } - public removeFromMp3(name: string) { - for (let i = 0; i < this.mp3s.length; i++) { - if (this.mp3s[i].id === name || this.mp3s[i].id + '.mp3' === name) { - this.mp3s.splice(i, 1); - } - } - this.getMp3s(); - } - - public removePlaylistMp3(playlistID, index) { - this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => { - if (res['success']) { - this.playlists.audio.splice(index, 1); - this.openSnackBar('Playlist successfully removed.', ''); - } - this.getMp3s(); - }); - } - - public removeFromMp4(name: string) { - for (let i = 0; i < this.mp4s.length; i++) { - if (this.mp4s[i].id === name || this.mp4s[i].id + '.mp4' === name) { - this.mp4s.splice(i, 1); - } - } - this.getMp4s(); - } - - public removePlaylistMp4(playlistID, index) { - this.postsService.removePlaylist(playlistID, 'video').subscribe(res => { - if (res['success']) { - this.playlists.video.splice(index, 1); - this.openSnackBar('Playlist successfully removed.', ''); - } - this.getMp4s(); - }); - } - // download helpers downloadHelperMp3(name, uid, is_playlist = false, forceView = false, new_download = null, navigate_mode = false) { @@ -504,16 +403,6 @@ export class MainComponent implements OnInit { // remove download from current downloads this.removeDownloadFromCurrentDownloads(new_download); - - // reloads mp3s - if (this.fileManagerEnabled) { - this.getMp3s(); - setTimeout(() => { - this.audioFileCards.forEach(filecard => { - filecard.onHoverResponse(); - }); - }, 200); - } } downloadHelperMp4(name, uid, is_playlist = false, forceView = false, new_download = null, navigate_mode = false) { @@ -543,16 +432,6 @@ export class MainComponent implements OnInit { // remove download from current downloads this.removeDownloadFromCurrentDownloads(new_download); - - // reloads mp4s - if (this.fileManagerEnabled) { - this.getMp4s(); - setTimeout(() => { - this.videoFileCards.forEach(filecard => { - filecard.onHoverResponse(); - }); - }, 200); - } } // download click handler @@ -745,8 +624,6 @@ export class MainComponent implements OnInit { if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded this.postsService.deleteFile(name, 'video').subscribe(delRes => { - // reload mp3s - this.getMp3s(); }); } }); @@ -762,8 +639,6 @@ export class MainComponent implements OnInit { if (!this.fileManagerEnabled) { // tell server to delete the file once downloaded this.postsService.deleteFile(name, 'audio').subscribe(delRes => { - // reload mp4s - this.getMp4s(); }); } }); @@ -1110,25 +985,6 @@ export class MainComponent implements OnInit { } } - // creating a playlist - openCreatePlaylistDialog(type) { - const dialogRef = this.dialog.open(CreatePlaylistComponent, { - data: { - filesToSelectFrom: (type === 'audio') ? this.mp3s : this.mp4s, - type: type - } - }); - dialogRef.afterClosed().subscribe(result => { - if (result) { - if (type === 'audio') { this.getMp3s() }; - if (type === 'video') { this.getMp4s() }; - this.openSnackBar('Successfully created playlist!', ''); - } else if (result === false) { - this.openSnackBar('ERROR: failed to create playlist!', ''); - } - }); - } - // modify custom args openArgsModifierDialog() { const dialogRef = this.dialog.open(ArgModifierDialogComponent, { From 2656147570910613881add021cbaf990ec6f3db7 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 22 Dec 2020 01:24:50 -0500 Subject: [PATCH 069/250] Optimized get/set subscription process --- .../edit-subscription-dialog.component.ts | 6 +++++- .../subscription/subscription.component.ts | 10 ++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts index b1d2dd4..19ff03b 100644 --- a/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts +++ b/src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.ts @@ -61,9 +61,13 @@ export class EditSubscriptionDialogComponent implements OnInit { ]; constructor(@Inject(MAT_DIALOG_DATA) public data: any, private dialog: MatDialog, private postsService: PostsService) { - this.sub = this.data.sub; + this.sub = JSON.parse(JSON.stringify(this.data.sub)); this.new_sub = JSON.parse(JSON.stringify(this.sub)); + // ignore videos to keep requests small + delete this.sub['videos']; + delete this.new_sub['videos']; + this.audioOnlyMode = this.sub.type === 'audio'; this.download_all = !this.sub.timerange; diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 5f32f40..461dcfc 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -56,7 +56,7 @@ export class SubscriptionComponent implements OnInit, OnDestroy { if (init) { this.getConfig(); this.getSubscription(); - this.sub_interval = setInterval(() => this.getSubscription(), 1000); + this.sub_interval = setInterval(() => this.getSubscription(true), 1000); } }); } @@ -79,8 +79,14 @@ export class SubscriptionComponent implements OnInit, OnDestroy { this.router.navigate(['/subscriptions']); } - getSubscription() { + getSubscription(low_cost = false) { this.postsService.getSubscription(this.id).subscribe(res => { + if (low_cost && res['subscription'].videos.length === this.subscription?.videos.length) { + if (res['subscription']['downloading'] !== this.subscription['downloading']) { + this.subscription['downloading'] = res['subscription']['downloading']; + } + return; + } this.subscription = res['subscription']; this.files = res['files']; if (this.search_mode) { From 6eb6ffa5e409b817bae494c9486c6cc15877c301 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 22 Dec 2020 01:25:12 -0500 Subject: [PATCH 070/250] Get user videos now accepts an optional type parameter --- backend/authentication/auth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index 3c77edd..ce8f100 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -281,9 +281,9 @@ exports.adminExists = function() { // video stuff -exports.getUserVideos = function(user_uid) { +exports.getUserVideos = function(user_uid, type) { const user = users_db.get('users').find({uid: user_uid}).value(); - return user['files']; + return type ? user['files'].filter(file => file.isAudio = (type === 'audio')) : user['files']; } exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) { From 1cc4df28292bd820a757cf35ca77f1584351d86c Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 22 Dec 2020 01:29:19 -0500 Subject: [PATCH 071/250] Updated translation file to v4.2 --- src/assets/i18n/messages.en.xlf | 3698 ++++++++++++++++--------------- 1 file changed, 1969 insertions(+), 1729 deletions(-) diff --git a/src/assets/i18n/messages.en.xlf b/src/assets/i18n/messages.en.xlf index e7dbf64..a624153 100644 --- a/src/assets/i18n/messages.en.xlf +++ b/src/assets/i18n/messages.en.xlf @@ -2,1386 +2,74 @@ - - Create a playlist - - app/create-playlist/create-playlist.component.html - 1 - - Create a playlist dialog title - - - Name - - app/create-playlist/create-playlist.component.html - 6 - - - app/dialogs/modify-playlist/modify-playlist.component.html - 7 - - Playlist name placeholder - - - Audio - - app/create-playlist/create-playlist.component.html - 12 - - Audio - - - Video - - app/create-playlist/create-playlist.component.html - 13 - - Video - - - Type - - app/create-playlist/create-playlist.component.html - 11 - - Type select - - - Audio files - - app/create-playlist/create-playlist.component.html - 19 - - Audio files title - - - Videos - - app/create-playlist/create-playlist.component.html - 20 - - - app/subscription/subscription/subscription.component.html - 28 - - Videos title - - - Modify youtube-dl args - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 1 - - Modify args title - - - Simulated new args - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 8 - - Simulated args title - - - Add an arg - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 34 - - Add arg card title - - - Search by category - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 60 - - Search args by category button - - - Use arg value - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 64 - - Use arg value checkbox - - - Arg value - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 68 - - Arg value placeholder - - - Add arg - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 73 - - Search args by category button - - - Cancel - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 84 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 72 - - - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 56 - - - app/components/modify-users/modify-users.component.html - 61 - - Arg modifier cancel button - - - Modify - - app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html - 85 - - Arg modifier modify button - - - - Quality - - - app/main/main.component.html - 18 - - Quality select label - - - Use URL - - app/main/main.component.html - 46 - - YT search Use URL button for searched video - - - - View - - - app/main/main.component.html - 49 - - YT search View button for searched video - - - - Only Audio - - - app/main/main.component.html - 59 - - Only Audio checkbox - - - - Multi-download Mode - - - app/main/main.component.html - 64 - - Multi-download Mode checkbox - - - - Download - - - app/main/main.component.html - 73 - - Main download button - - - - Cancel - - - app/main/main.component.html - 78 - - Cancel download button - - - - Advanced - - - app/main/main.component.html - 90 - - Advanced download mode panel - - - - Simulated command: - - - app/main/main.component.html - 96 - - Simulated command label - - - - Use custom args - - - app/main/main.component.html - 104 - - Use custom args checkbox - - - Custom args - - app/main/main.component.html - 110 - - - app/settings/settings.component.html - 120 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 50 - - - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 34 - - Custom args placeholder - - - - No need to include URL, just everything after. Args are delimited using two commas like so: ,, - - - app/main/main.component.html - 112 - - Custom Args input hint - - - - Use custom output - - - app/main/main.component.html - 120 - - Use custom output checkbox - - - Custom output - - app/main/main.component.html - 125 - - Custom output placeholder - - - Documentation - - app/main/main.component.html - 127 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 62 - - - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 46 - - Youtube-dl output template documentation link - - - Path is relative to the config download path. Don't include extension. - - app/main/main.component.html - 128 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 63 - - - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 47 - - Custom Output input hint - - - - Use authentication - - - app/main/main.component.html - 134 - - Use authentication checkbox - - - Username - - app/main/main.component.html - 139 - - YT Username placeholder - - - Password - - app/main/main.component.html - 144 - - - app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html - 10 - - - app/dialogs/add-user-dialog/add-user-dialog.component.html - 11 - - YT Password placeholder - - - Name: - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 5 - - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 6 - - Video name property - - - URL: - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 9 - - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 9 - - Video URL property - - - Uploader: - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 13 - - Video ID property - - - File size: - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 17 - - Video file size property - - - Path: - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 21 - - Video path property - - - Upload Date: - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 25 - - Video upload date property - - - Close - - app/dialogs/video-info-dialog/video-info-dialog.component.html - 31 - - - app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html - 40 - - - app/dialogs/about-dialog/about-dialog.component.html - 59 - - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 27 - - - app/dialogs/share-media-dialog/share-media-dialog.component.html - 30 - - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 23 - - - app/dialogs/update-progress-dialog/update-progress-dialog.component.html - 17 - - - app/dialogs/add-user-dialog/add-user-dialog.component.html - 18 - - - app/components/manage-user/manage-user.component.html - 30 - - - app/components/manage-role/manage-role.component.html - 18 - - Close subscription info button - - - Modify playlist - - app/dialogs/modify-playlist/modify-playlist.component.html - 1 - - Modify playlist dialog title - - - Add more content - - app/dialogs/modify-playlist/modify-playlist.component.html - 17 - - Add more content - - - Save - - app/dialogs/modify-playlist/modify-playlist.component.html - 27 - - - app/settings/settings.component.html - 359 - - - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 58 - - - app/components/modify-users/modify-users.component.html - 58 - - Save - - - ID: - - app/file-card/file-card.component.html - 7 - - - app/download-item/download-item.component.html - 4 - - - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 13 - - File or playlist ID - - - Count: - - app/file-card/file-card.component.html - 8 - - Playlist video count - - - Edit - - app/file-card/file-card.component.html - 19 - - - app/components/unified-file-card/unified-file-card.component.html - 32 - - Playlist edit button - - - Delete - - app/file-card/file-card.component.html - 20 - - - app/file-card/file-card.component.html - 25 - - - app/components/unified-file-card/unified-file-card.component.html - 28 - - - app/components/unified-file-card/unified-file-card.component.html - 34 - - Delete playlist - - - Info - - app/file-card/file-card.component.html - 24 - - - app/subscription/subscription-file-card/subscription-file-card.component.html - 7 - - - app/components/unified-file-card/unified-file-card.component.html - 19 - - Video info button - - - Delete and blacklist - - app/file-card/file-card.component.html - 26 - - - app/components/unified-file-card/unified-file-card.component.html - 29 - - Delete and blacklist video button - - - Upload new cookies - - app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html - 1 - - Cookies uploader dialog title - - - Drag and Drop - - app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html - 11 - - Drag and Drop - - - NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user. - - app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html - 20 - - Cookies upload warning - - - Settings - - app/settings/settings.component.html - 1 - - - app/app.component.html - 28 - - Settings title - - - URL - - app/settings/settings.component.html - 18 - - - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 8 - - URL input placeholder - - - URL this app will be accessed from, without the port. - - app/settings/settings.component.html - 19 - - URL setting input hint - - - Port - - app/settings/settings.component.html - 24 - - Port input placeholder - - - The desired port. Default is 17442. - - app/settings/settings.component.html - 25 - - Port setting input hint - - - Multi-user mode - - app/settings/settings.component.html - 34 - - Multi user mode setting - - - Users base path - - app/settings/settings.component.html - 38 - - Users base path placeholder - - - Base path for users and their downloaded videos. - - app/settings/settings.component.html - 39 - - Users base path hint - - - Allow subscriptions - - app/settings/settings.component.html - 48 - - Allow subscriptions setting - - - Subscriptions base path - - app/settings/settings.component.html - 52 - - Subscriptions base path input setting placeholder - - - Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 53 - - Subscriptions base path setting input hint - - - Check interval - - app/settings/settings.component.html - 58 - - Check interval input setting placeholder - - - Unit is seconds, only include numbers. - - app/settings/settings.component.html - 59 - - Check interval setting input hint - - - Theme - - app/settings/settings.component.html - 69 - - Theme select label - - - Default - - app/settings/settings.component.html - 71 - - Default theme label - - - Dark - - app/settings/settings.component.html - 72 - - - app/app.component.html - 23 - - Dark theme label - - - Allow theme change - - app/settings/settings.component.html - 77 - - Allow theme change setting - - - Language - - app/settings/settings.component.html - 86 - - Language select label - - - Main - - app/settings/settings.component.html - 12 - - Main settings label - - - Audio folder path - - app/settings/settings.component.html - 106 - - Audio folder path input placeholder - - - Path for audio only downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 107 - - Aduio path setting input hint - - - Video folder path - - app/settings/settings.component.html - 113 - - Video folder path input placeholder - - - Path for video downloads. It is relative to YTDL-Material's root folder. - - app/settings/settings.component.html - 114 - - Video path setting input hint - - - Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, - - app/settings/settings.component.html - 121 - - Custom args setting input hint - - - Use youtube-dl archive - - app/settings/settings.component.html - 127 - - Use youtubedl archive setting - - - Include thumbnail - - app/settings/settings.component.html - 131 - - Include thumbnail setting - - - Include metadata - - app/settings/settings.component.html - 135 - - Include metadata setting - - - Kill all downloads - - app/settings/settings.component.html - 139 - - Kill all downloads button - - - Downloader - - app/settings/settings.component.html - 99 - - Downloader settings label - - - Top title - - app/settings/settings.component.html - 152 - - Top title input placeholder - - - File manager enabled - - app/settings/settings.component.html - 157 - - File manager enabled setting - - - Downloads manager enabled - - app/settings/settings.component.html - 160 - - Downloads manager enabled setting - - - Allow quality select - - app/settings/settings.component.html - 163 - - Allow quality seelct setting - - - Download only mode - - app/settings/settings.component.html - 166 - - Download only mode setting - - - Allow multi-download mode - - app/settings/settings.component.html - 169 - - Allow multi-download mode setting - - - Enable Public API - - app/settings/settings.component.html - 177 - - Enable Public API key setting - - - Public API Key - - app/settings/settings.component.html - 182 - - Public API Key setting placeholder - - - View documentation - - app/settings/settings.component.html - 183 - - View API docs setting hint - - - Generate - - app/settings/settings.component.html - 187 - - Generate key button - - - This will delete your old API key! - - app/settings/settings.component.html - 187 - - delete api key tooltip - - - Use YouTube API - - app/settings/settings.component.html - 196 - - Use YouTube API setting - - - Youtube API Key - - app/settings/settings.component.html - 200 - - Youtube API Key setting placeholder - - - Generating a key is easy! - - app/settings/settings.component.html - 201 - - Youtube API Key setting hint - - - Click here - - app/settings/settings.component.html - 211 - - - app/settings/settings.component.html - 217 - - - app/dialogs/about-dialog/about-dialog.component.html - 25 - - Chrome ext click here - - - to download the official YoutubeDL-Material Chrome extension manually. - - app/settings/settings.component.html - 211 - - Chrome click here suffix - - - You must manually load the extension and modify the extension's settings to set the frontend URL. - - app/settings/settings.component.html - 212 - - Chrome setup suffix - - - to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. - - app/settings/settings.component.html - 217 - - Firefox click here suffix - - - Detailed setup instructions. - - app/settings/settings.component.html - 218 - - Firefox setup prefix link - - - Not much is required other than changing the extension's settings to set the frontend URL. - - app/settings/settings.component.html - 218 - - Firefox setup suffix - - - Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. - - app/settings/settings.component.html - 223 - - Bookmarklet instructions - - - Generate 'audio only' bookmarklet - - app/settings/settings.component.html - 224 - - Generate audio only bookmarklet checkbox - - - Extra - - app/settings/settings.component.html - 146 - - Extra settings label - - - Use default downloading agent - - app/settings/settings.component.html - 238 - - Use default downloading agent setting - - - Select a downloader - - app/settings/settings.component.html - 242 - - Custom downloader select label - - - Log Level - - app/settings/settings.component.html - 256 - - Log Level label - - - Login expiration - - app/settings/settings.component.html - 268 - - Login expiration select label - - - Allow advanced download - - app/settings/settings.component.html - 279 - - Allow advanced downloading setting - - - Use Cookies - - app/settings/settings.component.html - 287 - - Use cookies setting - - - Set Cookies - - app/settings/settings.component.html - 288 - - Set cookies button - - - Advanced - - app/settings/settings.component.html - 233 - - Host settings label - - - Allow user registration - - app/settings/settings.component.html - 302 - - Allow registration setting - - - Internal - - app/settings/settings.component.html - 308 - - Internal auth method - - - LDAP - - app/settings/settings.component.html - 311 - - LDAP auth method - - - Auth method - - app/settings/settings.component.html - 306 - - Auth method select - - - LDAP URL - - app/settings/settings.component.html - 318 - - LDAP URL - - - Bind DN - - app/settings/settings.component.html - 323 - - Bind DN - - - Bind Credentials - - app/settings/settings.component.html - 328 - - Bind Credentials - - - Search Base - - app/settings/settings.component.html - 333 - - Search Base - - - Search Filter - - app/settings/settings.component.html - 338 - - Search Filter - - - Users - - app/settings/settings.component.html - 298 - - Users settings label - - - Logs - - app/settings/settings.component.html - 346 - - Logs settings label - - - {VAR_SELECT, select, true {Close} false {Cancel} other {otha} } - - app/settings/settings.component.html - 362 - - Settings cancel and close button - - - About YoutubeDL-Material - - app/dialogs/about-dialog/about-dialog.component.html - 1 - - About dialog title - - - is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. - - app/dialogs/about-dialog/about-dialog.component.html - 12 - - About first paragraph - - - has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. - - app/dialogs/about-dialog/about-dialog.component.html - 15 - - About second paragraph - - - Installed version: - - app/dialogs/about-dialog/about-dialog.component.html - 20 - - Version label - - - Checking for updates... - - app/dialogs/about-dialog/about-dialog.component.html - 20 - - Checking for updates text - - - Update available - - app/dialogs/about-dialog/about-dialog.component.html - 21 - - View latest update - - - You can update from the settings menu. - - app/dialogs/about-dialog/about-dialog.component.html - 21 - - Update through settings menu hint - - - Found a bug or have a suggestion? - - app/dialogs/about-dialog/about-dialog.component.html - 25 - - About bug prefix - - - to create an issue! - - app/dialogs/about-dialog/about-dialog.component.html - 25 - - About bug suffix - - - Your Profile - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 1 - - User profile dialog title - - - UID: - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 9 - - UID - - - Created: - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 12 - - Created - - - You are not logged in. - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 19 - - Not logged in notification - - - Login - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 20 - - - app/app.component.html - 44 - - - app/components/login/login.component.html - 15 - - Login - - - Logout - - app/dialogs/user-profile-dialog/user-profile-dialog.component.html - 28 - - Logout - - - Create admin account - - app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html - 1 - - Create admin account dialog title - - - No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. - - app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html - 5 - - No default admin detected explanation - - - Create - - app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html - 17 - - Create - - - Profile - - app/app.component.html - 19 - - Profile menu label - About - app/app.component.html + src/app/app.component.html 32 About menu label + + Profile + + src/app/app.component.html + 19 + + Profile menu label + + + Dark + + src/app/app.component.html + 23 + + + src/app/settings/settings.component.html + 75 + + Dark mode toggle label + + + Settings + + src/app/app.component.html + 28 + + + src/app/settings/settings.component.html + 1 + + Settings menu label + Home - app/app.component.html + src/app/app.component.html 43 Navigation menu Home Page title + + Login + + src/app/app.component.html + 44 + + + src/app/components/login/login.component.html + 15 + + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 20 + + Navigation menu Login Page title + Subscriptions - app/app.component.html + src/app/app.component.html 45 Navigation menu Subscriptions Page title @@ -1389,143 +77,299 @@ Downloads - app/app.component.html + src/app/app.component.html 46 Navigation menu Downloads Page title - - Share playlist + + Only Audio - app/dialogs/share-media-dialog/share-media-dialog.component.html - 2 + src/app/main/main.component.html + 60,61 - Share playlist dialog title + Only Audio checkbox - - Share video + + Download - app/dialogs/share-media-dialog/share-media-dialog.component.html - 3 + src/app/main/main.component.html + 74,75 - Share video dialog title + Main download button - - Share audio + + Quality - app/dialogs/share-media-dialog/share-media-dialog.component.html - 4 + src/app/main/main.component.html + 19,20 - Share audio dialog title + Quality select label - - Enable sharing + + Use URL - app/dialogs/share-media-dialog/share-media-dialog.component.html + src/app/main/main.component.html + 46 + + YT search Use URL button for searched video + + + View + + src/app/main/main.component.html + 50,51 + + YT search View button for searched video + + + Multi-download Mode + + src/app/main/main.component.html + 65,66 + + Multi-download Mode checkbox + + + Cancel + + src/app/main/main.component.html + 79,80 + + Cancel download button + + + Advanced + + src/app/main/main.component.html + 91,92 + + Advanced download mode panel + + + Use custom args + + src/app/main/main.component.html + 105,106 + + Use custom args checkbox + + + Custom args + + src/app/main/main.component.html + 110 + + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 57 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 44 + + Custom args placeholder + + + No need to include URL, just everything after. Args are delimited using two commas like so: ,, + + src/app/main/main.component.html + 113,114 + + Custom Args input hint + + + Use custom output + + src/app/main/main.component.html + 121,122 + + Use custom output checkbox + + + Custom output + + src/app/main/main.component.html + 125 + + Custom output placeholder + + + Documentation + + src/app/main/main.component.html + 127 + + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 69 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 56 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 47 + + + src/app/settings/settings.component.html + 125 + + Youtube-dl output template documentation link + + + Path is relative to the config download path. Don't include extension. + + src/app/main/main.component.html + 128 + + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 70 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 57 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 48 + + Custom Output input hint + + + Simulated command: + + src/app/main/main.component.html + 97,98 + + Simulated command label + + + Use authentication + + src/app/main/main.component.html + 135,136 + + Use authentication checkbox + + + Username + + src/app/main/main.component.html + 139 + + YT Username placeholder + + + Password + + src/app/main/main.component.html + 144 + + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 11 + + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html 10 - Enable sharing checkbox + YT Password placeholder - - Use timestamp + + Create a playlist - app/dialogs/share-media-dialog/share-media-dialog.component.html + src/app/create-playlist/create-playlist.component.html + 1 + + Create a playlist dialog title + + + Name + + src/app/create-playlist/create-playlist.component.html + 6 + + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 7 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 5 + + Playlist name placeholder + + + Type + + src/app/create-playlist/create-playlist.component.html + 11 + + Type select + + + Audio + + src/app/create-playlist/create-playlist.component.html + 12 + + Audio + + + Video + + src/app/create-playlist/create-playlist.component.html 13 - Use timestamp + Video - - Seconds + + Audio files - app/dialogs/share-media-dialog/share-media-dialog.component.html - 15 + src/app/create-playlist/create-playlist.component.html + 19 - Seconds + Audio files title - - Copy to clipboard + + Videos - app/dialogs/share-media-dialog/share-media-dialog.component.html - 24 + src/app/create-playlist/create-playlist.component.html + 20 - Copy to clipboard button - - - Save changes - app/player/player.component.html - 22 + src/app/subscription/subscription/subscription.component.html + 28 - Playlist save changes button - - - The download was successful - - app/download-item/download-item.component.html - 8 - - download successful tooltip - - - An error has occurred - - app/download-item/download-item.component.html - 9 - - download error tooltip - - - Details - - app/download-item/download-item.component.html - 18 - - Details - - - An error has occurred: - - app/download-item/download-item.component.html - 27 - - Error label - - - Download start: - - app/download-item/download-item.component.html - 32 - - Download start label - - - Download end: - - app/download-item/download-item.component.html - 35 - - Download end label - - - File path(s): - - app/download-item/download-item.component.html - 38 - - File path(s) label + Videos title Subscribe to playlist or channel - app/dialogs/subscribe-dialog/subscribe-dialog.component.html + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html 1 Subscribe dialog title + + URL + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 8 + + + src/app/settings/settings.component.html + 18 + + Subscription URL input placeholder + The playlist or channel URL - app/dialogs/subscribe-dialog/subscribe-dialog.component.html + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html 9 Subscription URL input hint @@ -1533,7 +377,7 @@ Custom name - app/dialogs/subscribe-dialog/subscribe-dialog.component.html + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html 19 Subscription custom name placeholder @@ -1541,103 +385,207 @@ Download all uploads - app/dialogs/subscribe-dialog/subscribe-dialog.component.html + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html 23 - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 7 + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 10 Download all uploads subscription setting - - Download videos uploaded in the last + + Max quality - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 26 + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 10 + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 32 - Download time range prefix + Max quality placeholder Audio-only mode - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 40 + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 47 - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 24 + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 27 Streaming-only mode Streaming-only mode - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 45 + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 52 - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 29 + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 39 Streaming-only mode These are added after the standard args. - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 53 + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 60 - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 37 + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 47 Custom args hint Custom file output - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 59 + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 66 - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html - 43 + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 53 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 44 Subscription custom file output placeholder + + Cancel + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 79 + + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 84 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 66 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 54 + + + src/app/components/modify-users/modify-users.component.html + 61 + + Subscribe cancel button + Subscribe - app/dialogs/subscribe-dialog/subscribe-dialog.component.html - 74 + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 81 Subscribe button + + Download videos uploaded in the last + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 26 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 13 + + Download time range prefix + Type: - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html 5 Subscription type property - - Archive: + + URL: - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html - 17 + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 9 + + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 9 + + Subscription URL property + + + ID: + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 13 + + + src/app/file-card/file-card.component.html + 7 + + + src/app/download-item/download-item.component.html + 4 Subscription ID property + + Close + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 23 + + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 35 + + + src/app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 17 + + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 18 + + + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 40 + + + src/app/dialogs/about-dialog/about-dialog.component.html + 59 + + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 30 + + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 27 + + + src/app/components/manage-user/manage-user.component.html + 30 + + + src/app/components/manage-role/manage-role.component.html + 18 + + Close subscription info button + Export Archive - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html 24 Export Archive button @@ -1645,187 +593,171 @@ Unsubscribe - app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html 26 Unsubscribe button - - Your subscriptions + + (Paused) - app/subscriptions/subscriptions.component.html - 3 - - Subscriptions title - - - Channels - - app/subscriptions/subscriptions.component.html - 8 - - Subscriptions channels title - - - Name not available. Channel retrieval in progress. - - app/subscriptions/subscriptions.component.html - 14 - - Subscription playlist not available text - - - You have no channel subscriptions. - - app/subscriptions/subscriptions.component.html - 24 - - No channel subscriptions text - - - Playlists - - app/subscriptions/subscriptions.component.html - 27 - - Subscriptions playlists title - - - Name not available. Playlist retrieval in progress. - - app/subscriptions/subscriptions.component.html - 33 - - Subscription playlist not available text - - - You have no playlist subscriptions. - - app/subscriptions/subscriptions.component.html - 43 - - No playlist subscriptions text - - - Editing - - app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html 1 - Edit subscription dialog title prefix + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + + src/app/subscriptions/subscriptions.component.html + 12 + + + src/app/subscriptions/subscriptions.component.html + 31 + + + src/app/subscription/subscription/subscription.component.html + 5 + + Paused suffix - - Search + + Archive: - app/subscription/subscription/subscription.component.html - 32 + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 17 - - app/components/modify-users/modify-users.component.html - 7 - - - app/components/recent-videos/recent-videos.component.html - 24 - - Subscription videos search placeholder + Subscription ID property - - Length: + + Name: - app/subscription/subscription-file-card/subscription-file-card.component.html - 3 + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 5 - Video duration label - - - Delete and redownload - app/subscription/subscription-file-card/subscription-file-card.component.html + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 6 + + Video name property + + + Uploader: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 13 + + Video ID property + + + File size: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 17 + + Video file size property + + + Path: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 21 + + Video path property + + + Upload Date: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 25 + + Video upload date property + + + Category: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 29 + + Category property + + + Modify youtube-dl args + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 1 + + Modify args title + + + Simulated new args + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html 8 - - app/components/unified-file-card/unified-file-card.component.html - 23 - - Delete and redownload subscription video button + Simulated args title - - Delete forever + + Add an arg - app/subscription/subscription-file-card/subscription-file-card.component.html - 9 + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 34 + Add arg card title + + + Search by category - app/components/unified-file-card/unified-file-card.component.html - 26 + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 60 - Delete forever subscription video button + Search args by category button + + + Use arg value + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 64 + + Use arg value checkbox + + + Add arg + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 73 + + Search args by category button + + + Modify + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 85 + + Arg modifier modify button + + + Arg value + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 68 + + Arg value placeholder Updater - app/dialogs/update-progress-dialog/update-progress-dialog.component.html + src/app/dialogs/update-progress-dialog/update-progress-dialog.component.html 1 Update progress dialog title - - Select a version: - - app/updater/updater.component.html - 3 - - Select a version - - - Register - - app/components/login/login.component.html - 35 - - - app/dialogs/add-user-dialog/add-user-dialog.component.html - 17 - - Register - - - Session ID: - - app/components/downloads/downloads.component.html - 5 - - Session ID - - - (current) - - app/components/downloads/downloads.component.html - 6 - - Current session - - - Clear all downloads - - app/components/downloads/downloads.component.html - 18 - - clear all downloads action button - - - No downloads available! - - app/components/downloads/downloads.component.html - 25 - - No downloads label - Register a user - app/dialogs/add-user-dialog/add-user-dialog.component.html + src/app/dialogs/add-user-dialog/add-user-dialog.component.html 1 Register user dialog title @@ -1833,131 +765,1283 @@ User name - app/dialogs/add-user-dialog/add-user-dialog.component.html + src/app/dialogs/add-user-dialog/add-user-dialog.component.html 6 User name placeholder - - Manage user + + Register - app/components/manage-user/manage-user.component.html + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 17 + + + src/app/components/login/login.component.html + 35 + + Register user button + + + Upload new cookies + + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html 1 - - app/components/modify-users/modify-users.component.html - 70 - - Manage user dialog title + Cookies uploader dialog title - - User UID: + + NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user. - app/components/manage-user/manage-user.component.html - 4 + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 - User UID + Cookies upload warning - - New password + + Drag and Drop - app/components/manage-user/manage-user.component.html - 8 + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 11 - New password placeholder + Drag and Drop - - Set new password + + Modify playlist - app/components/manage-user/manage-user.component.html - 10 + src/app/dialogs/modify-playlist/modify-playlist.component.html + 1 - Set new password + Modify playlist dialog title - - Use role default + + Add content - app/components/manage-user/manage-user.component.html + src/app/dialogs/modify-playlist/modify-playlist.component.html 19 - Use role default + Add content - - Yes + + Save - app/components/manage-user/manage-user.component.html + src/app/dialogs/modify-playlist/modify-playlist.component.html + 37 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 68 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 56 + + + src/app/settings/settings.component.html + 416 + + + src/app/components/modify-users/modify-users.component.html + 58 + + Save + + + Normal order  + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 13 + + Normal order + + + Reverse order  + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 14 + + Reverse order + + + My videos + + src/app/components/recent-videos/recent-videos.component.html + 20 + + My videos title + + + Search + + src/app/components/recent-videos/recent-videos.component.html + 24 + + + src/app/components/modify-users/modify-users.component.html + 7 + + + src/app/subscription/subscription/subscription.component.html + 32 + + Files search placeholder + + + No videos found. + + src/app/components/recent-videos/recent-videos.component.html + 38 + + No videos found + + + Editing + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + Edit subscription dialog title prefix + + + Paused + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 7 + + Paused subscription setting + + + Editing category + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 1 + + Editing category dialog title + + + Rules + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 10 + + Rules + + + Add new rule + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 39 + + Add new rule tooltip + + + Download Twitch Chat + + src/app/components/twitch-chat/twitch-chat.component.html + 10 + + Download Twitch Chat button + + + Edit + + src/app/file-card/file-card.component.html + 19 + + + src/app/components/unified-file-card/unified-file-card.component.html + 32 + + Playlist edit button + + + Delete + + src/app/file-card/file-card.component.html 20 - app/components/manage-role/manage-role.component.html + src/app/file-card/file-card.component.html + 25 + + + src/app/components/unified-file-card/unified-file-card.component.html + 28 + + + src/app/components/unified-file-card/unified-file-card.component.html + 34 + + Delete playlist + + + Info + + src/app/file-card/file-card.component.html + 24 + + + src/app/components/unified-file-card/unified-file-card.component.html + 19 + + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 7 + + Video info button + + + Count: + + src/app/file-card/file-card.component.html + 8 + + Playlist video count + + + Delete and blacklist + + src/app/file-card/file-card.component.html + 26 + + + src/app/components/unified-file-card/unified-file-card.component.html + 29 + + Delete and blacklist video button + + + views + + src/app/player/player.component.html + 15 + + View count label + + + Save changes + + src/app/player/player.component.html + 59 + + Playlist save changes button + + + The download was successful + + src/app/download-item/download-item.component.html + 8 + + + src/app/download-item/download-item.component.html + 8 + + download successful tooltip + + + An error has occurred + + src/app/download-item/download-item.component.html 9 - Yes - - - No - app/components/manage-user/manage-user.component.html - 21 + src/app/download-item/download-item.component.html + 9 - - app/components/manage-role/manage-role.component.html - 10 - - No + download error tooltip - - Manage role + + Details - app/components/manage-role/manage-role.component.html - 1 + src/app/download-item/download-item.component.html + 18 - Manage role dialog title + Details - - User name + + An error has occurred: - app/components/modify-users/modify-users.component.html - 17 + src/app/download-item/download-item.component.html + 27 - Username users table header + Error label - - Role + + Download start: - app/components/modify-users/modify-users.component.html + src/app/download-item/download-item.component.html + 32 + + Download start label + + + Download end: + + src/app/download-item/download-item.component.html 35 - Role users table header + Download end label - - Actions + + File path(s): - app/components/modify-users/modify-users.component.html - 55 + src/app/download-item/download-item.component.html + 38 - Actions users table header + File path(s) label - - Edit user + + Your subscriptions - app/components/modify-users/modify-users.component.html - 66 + src/app/subscriptions/subscriptions.component.html + 3 - edit user action button tooltip + Subscriptions title - - Delete user + + Channels - app/components/modify-users/modify-users.component.html - 73 + src/app/subscriptions/subscriptions.component.html + 8 - delete user action button tooltip + Subscriptions channels title + + + Playlists + + src/app/subscriptions/subscriptions.component.html + 27 + + Subscriptions playlists title + + + Name not available. Channel retrieval in progress. + + src/app/subscriptions/subscriptions.component.html + 14 + + Subscription playlist not available text + + + You have no channel subscriptions. + + src/app/subscriptions/subscriptions.component.html + 24 + + No channel subscriptions text + + + Name not available. Playlist retrieval in progress. + + src/app/subscriptions/subscriptions.component.html + 33 + + Subscription playlist not available text + + + You have no playlist subscriptions. + + src/app/subscriptions/subscriptions.component.html + 43 + + No playlist subscriptions text + + + Main + + src/app/settings/settings.component.html + 12 + + Main settings label + + + Downloader + + src/app/settings/settings.component.html + 102 + + Downloader settings label + + + Extra + + src/app/settings/settings.component.html + 182 + + Extra settings label + + + Advanced + + src/app/settings/settings.component.html + 281 + + Host settings label + + + Users + + src/app/settings/settings.component.html + 355 + + + src/app/settings/settings.component.html + 355 + + Users settings label + + + Logs + + src/app/settings/settings.component.html + 403 + + + src/app/settings/settings.component.html + 403 + + Logs settings label + + + {VAR_SELECT, select, true {Close} false {Cancel} other {otha}} + + src/app/settings/settings.component.html + 419 + + Settings cancel and close button + + + URL this app will be accessed from, without the port. + + src/app/settings/settings.component.html + 19 + + URL setting input hint + + + Port + + src/app/settings/settings.component.html + 24 + + Port input placeholder + + + The desired port. Default is 17442. + + src/app/settings/settings.component.html + 25 + + Port setting input hint + + + Multi-user mode + + src/app/settings/settings.component.html + 34 + + Multi user mode setting + + + Users base path + + src/app/settings/settings.component.html + 38 + + Users base path placeholder + + + Base path for users and their downloaded videos. + + src/app/settings/settings.component.html + 39 + + Users base path hint + + + Allow subscriptions + + src/app/settings/settings.component.html + 48 + + Allow subscriptions setting + + + Subscriptions base path + + src/app/settings/settings.component.html + 52 + + Subscriptions base path input setting placeholder + + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + + src/app/settings/settings.component.html + 53 + + Subscriptions base path setting input hint + + + Check interval + + src/app/settings/settings.component.html + 58 + + Check interval input setting placeholder + + + Unit is seconds, only include numbers. + + src/app/settings/settings.component.html + 59 + + Check interval setting input hint + + + Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day. + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads tooltip + + + Redownload fresh uploads + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads + + + Theme + + src/app/settings/settings.component.html + 72 + + Theme select label + + + Default + + src/app/settings/settings.component.html + 74 + + Default theme label + + + Allow theme change + + src/app/settings/settings.component.html + 80 + + Allow theme change setting + + + Language + + src/app/settings/settings.component.html + 89 + + Language select label + + + Audio folder path + + src/app/settings/settings.component.html + 109 + + Audio folder path input placeholder + + + Path for audio only downloads. It is relative to YTDL-Material's root folder. + + src/app/settings/settings.component.html + 110 + + Aduio path setting input hint + + + Video folder path + + src/app/settings/settings.component.html + 116 + + Video folder path input placeholder + + + Path for video downloads. It is relative to YTDL-Material's root folder. + + src/app/settings/settings.component.html + 117 + + Video path setting input hint + + + Default file output + + src/app/settings/settings.component.html + 123 + + Default file output placeholder + + + Path is relative to the above download paths. Don't include extension. + + src/app/settings/settings.component.html + 126 + + Custom Output input hint + + + Global custom args + + src/app/settings/settings.component.html + 133 + + Custom args input placeholder + + + Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, + + src/app/settings/settings.component.html + 134 + + Custom args setting input hint + + + Categories + + src/app/settings/settings.component.html + 144 + + Categories + + + Use youtube-dl archive + + src/app/settings/settings.component.html + 163 + + Use youtubedl archive setting + + + Include thumbnail + + src/app/settings/settings.component.html + 167 + + Include thumbnail setting + + + Include metadata + + src/app/settings/settings.component.html + 171 + + Include metadata setting + + + Kill all downloads + + src/app/settings/settings.component.html + 175 + + Kill all downloads button + + + Top title + + src/app/settings/settings.component.html + 188 + + Top title input placeholder + + + File manager enabled + + src/app/settings/settings.component.html + 193 + + File manager enabled setting + + + Downloads manager enabled + + src/app/settings/settings.component.html + 196 + + Downloads manager enabled setting + + + Allow quality select + + src/app/settings/settings.component.html + 199 + + Allow quality seelct setting + + + Download only mode + + src/app/settings/settings.component.html + 202 + + Download only mode setting + + + Allow multi-download mode + + src/app/settings/settings.component.html + 205 + + Allow multi-download mode setting + + + Enable Public API + + src/app/settings/settings.component.html + 213 + + Enable Public API key setting + + + Public API Key + + src/app/settings/settings.component.html + 218 + + Public API Key setting placeholder + + + View documentation + + src/app/settings/settings.component.html + 219 + + View API docs setting hint + + + This will delete your old API key! + + src/app/settings/settings.component.html + 223 + + delete api key tooltip + + + Generate + + src/app/settings/settings.component.html + 223 + + Generate key button + + + Use YouTube API + + src/app/settings/settings.component.html + 232 + + Use YouTube API setting + + + Youtube API Key + + src/app/settings/settings.component.html + 236 + + Youtube API Key setting placeholder + + + Generating a key is easy! + + src/app/settings/settings.component.html + 237 + + + src/app/settings/settings.component.html + 249 + + Youtube API Key setting hint + + + Use Twitch API + + src/app/settings/settings.component.html + 241 + + Use Twitch API setting + + + Twitch API Key + + src/app/settings/settings.component.html + 248 + + Twitch API Key setting placeholder + + + Also known as a Client ID. + + src/app/settings/settings.component.html + 249 + + Twitch API Key setting hint AKA preamble + + + Auto-download Twitch Chat + + src/app/settings/settings.component.html + 244 + + Auto download Twitch Chat setting + + + Click here + + src/app/settings/settings.component.html + 259 + + + src/app/settings/settings.component.html + 265 + + + src/app/dialogs/about-dialog/about-dialog.component.html + 25 + + Chrome ext click here + + + to download the official YoutubeDL-Material Chrome extension manually. + + src/app/settings/settings.component.html + 259 + + Chrome click here suffix + + + You must manually load the extension and modify the extension's settings to set the frontend URL. + + src/app/settings/settings.component.html + 260 + + Chrome setup suffix + + + to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. + + src/app/settings/settings.component.html + 265 + + Firefox click here suffix + + + Detailed setup instructions. + + src/app/settings/settings.component.html + 266 + + Firefox setup prefix link + + + Not much is required other than changing the extension's settings to set the frontend URL. + + src/app/settings/settings.component.html + 266 + + Firefox setup suffix + + + Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. + + src/app/settings/settings.component.html + 271 + + Bookmarklet instructions + + + Generate 'audio only' bookmarklet + + src/app/settings/settings.component.html + 272 + + Generate audio only bookmarklet checkbox + + + Select a downloader + + src/app/settings/settings.component.html + 287 + + Default downloader select label + + + Use default downloading agent + + src/app/settings/settings.component.html + 295 + + Use default downloading agent setting + + + Select a download agent + + src/app/settings/settings.component.html + 299 + + Custom downloader select label + + + Log Level + + src/app/settings/settings.component.html + 313 + + Log Level label + + + Login expiration + + src/app/settings/settings.component.html + 325 + + Login expiration select label + + + Allow advanced download + + src/app/settings/settings.component.html + 336 + + Allow advanced downloading setting + + + Use Cookies + + src/app/settings/settings.component.html + 344 + + Use cookies setting + + + Set Cookies + + src/app/settings/settings.component.html + 345 + + Set cookies button + + + Allow user registration + + src/app/settings/settings.component.html + 359 + + Allow registration setting + + + Auth method + + src/app/settings/settings.component.html + 363 + + Auth method select + + + Internal + + src/app/settings/settings.component.html + 365 + + Internal auth method + + + LDAP + + src/app/settings/settings.component.html + 368 + + LDAP auth method + + + LDAP URL + + src/app/settings/settings.component.html + 375 + + LDAP URL + + + Bind DN + + src/app/settings/settings.component.html + 380 + + Bind DN + + + Bind Credentials + + src/app/settings/settings.component.html + 385 + + Bind Credentials + + + Search Base + + src/app/settings/settings.component.html + 390 + + Search Base + + + Search Filter + + src/app/settings/settings.component.html + 395 + + Search Filter + + + About YoutubeDL-Material + + src/app/dialogs/about-dialog/about-dialog.component.html + 1 + + About dialog title + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + + src/app/dialogs/about-dialog/about-dialog.component.html + 12 + + About first paragraph + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. + + src/app/dialogs/about-dialog/about-dialog.component.html + 15 + + About second paragraph + + + Installed version: + + src/app/dialogs/about-dialog/about-dialog.component.html + 20 + + Version label + + + Found a bug or have a suggestion? + + src/app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug prefix + + + to create an issue! + + src/app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug suffix + + + Checking for updates... + + src/app/dialogs/about-dialog/about-dialog.component.html + 20 + + Checking for updates text + + + Update available + + src/app/dialogs/about-dialog/about-dialog.component.html + 21 + + View latest update + + + You can update from the settings menu. + + src/app/dialogs/about-dialog/about-dialog.component.html + 21 + + Update through settings menu hint + + + Select a version: + + src/app/updater/updater.component.html + 3 + + Select a version + + + Enable sharing + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 10 + + Enable sharing checkbox + + + Use timestamp + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 13 + + Use timestamp + + + Seconds + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 15 + + Seconds + + + Copy to clipboard + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 24 + + Copy to clipboard button + + + Share playlist + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 2 + + Share playlist dialog title + + + Share video + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 3 + + Share video dialog title + + + Share audio + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 4 + + Share audio dialog title + + + Session ID: + + src/app/components/downloads/downloads.component.html + 5 + + Session ID + + + Clear all downloads + + src/app/components/downloads/downloads.component.html + 18 + + clear all downloads action button + + + (current) + + src/app/components/downloads/downloads.component.html + 6 + + Current session + + + No downloads available! + + src/app/components/downloads/downloads.component.html + 25 + + No downloads label + + + Your Profile + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 1 + + User profile dialog title + + + Logout + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 28 + + Logout + + + UID: + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 9 + + UID + + + Created: + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 12 + + Created + + + You are not logged in. + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 19 + + Not logged in notification + + + Create admin account + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 1 + + Create admin account dialog title + + + No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 5 + + No default admin detected explanation + + + Create + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 17 + + Create Add Users - app/components/modify-users/modify-users.component.html + src/app/components/modify-users/modify-users.component.html 90 Add users button @@ -1965,15 +2049,131 @@ Edit Role - app/components/modify-users/modify-users.component.html + src/app/components/modify-users/modify-users.component.html 95 Edit role + + User name + + src/app/components/modify-users/modify-users.component.html + 17 + + Username users table header + + + Role + + src/app/components/modify-users/modify-users.component.html + 35 + + Role users table header + + + Actions + + src/app/components/modify-users/modify-users.component.html + 55 + + Actions users table header + + + Manage user + + src/app/components/modify-users/modify-users.component.html + 70 + + + src/app/components/manage-user/manage-user.component.html + 1 + + manage user action button tooltip + + + Delete user + + src/app/components/modify-users/modify-users.component.html + 73 + + delete user action button tooltip + + + Edit user + + src/app/components/modify-users/modify-users.component.html + 66 + + edit user action button tooltip + + + User UID: + + src/app/components/manage-user/manage-user.component.html + 4 + + User UID + + + New password + + src/app/components/manage-user/manage-user.component.html + 8 + + New password placeholder + + + Set new password + + src/app/components/manage-user/manage-user.component.html + 10 + + Set new password + + + Use role default + + src/app/components/manage-user/manage-user.component.html + 19 + + Use role default + + + Yes + + src/app/components/manage-user/manage-user.component.html + 20 + + + src/app/components/manage-role/manage-role.component.html + 9 + + Yes + + + No + + src/app/components/manage-user/manage-user.component.html + 21 + + + src/app/components/manage-role/manage-role.component.html + 10 + + No + + + Manage role + + src/app/components/manage-role/manage-role.component.html + 1 + + Manage role dialog title + Lines: - app/components/logs-viewer/logs-viewer.component.html + src/app/components/logs-viewer/logs-viewer.component.html 22 Label for lines select in logger view @@ -1981,7 +2181,7 @@ Clear logs - app/components/logs-viewer/logs-viewer.component.html + src/app/components/logs-viewer/logs-viewer.component.html 34 Clear logs button @@ -1989,7 +2189,7 @@ Open file - app/components/unified-file-card/unified-file-card.component.html + src/app/components/unified-file-card/unified-file-card.component.html 13 Open file button @@ -1997,7 +2197,7 @@ Open file in new tab - app/components/unified-file-card/unified-file-card.component.html + src/app/components/unified-file-card/unified-file-card.component.html 14 Open file in new tab @@ -2005,18 +2205,58 @@ Go to subscription - app/components/unified-file-card/unified-file-card.component.html + src/app/components/unified-file-card/unified-file-card.component.html 20 Go to subscription menu item - - My videos + + Delete and redownload - app/components/recent-videos/recent-videos.component.html - 20 + src/app/components/unified-file-card/unified-file-card.component.html + 23 - My videos title + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 8 + + Delete and redownload subscription video button + + + Delete forever + + src/app/components/unified-file-card/unified-file-card.component.html + 26 + + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 9 + + Delete forever subscription video button + + + See more. + + src/app/components/see-more/see-more.component.html + 5,6 + + See more + + + See less. + + src/app/components/see-more/see-more.component.html + 8,9 + + See less + + + Length: + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 3 + + Video duration label From 9a57080bb32dd7553caeb437a7cd581372541df9 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 23 Dec 2020 01:24:43 -0500 Subject: [PATCH 072/250] Category is now properly stored in the database --- backend/db.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/db.js b/backend/db.js index b7298d0..38dfdcb 100644 --- a/backend/db.js +++ b/backend/db.js @@ -15,7 +15,7 @@ function initialize(input_db, input_users_db, input_logger) { setLogger(input_logger); } -function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null) { +function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null, category = null) { let db_path = null; const file_id = file_path.substring(0, file_path.length-4); const file_object = generateFileObject(file_id, type, customPath || multiUserMode && multiUserMode.file_path, sub); @@ -29,6 +29,9 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null, custo // add thumbnail path file_object['thumbnailPath'] = utils.getDownloadedThumbnail(file_id, type, customPath || multiUserMode && multiUserMode.file_path); + // if category exists, only include essential info + if (category) file_object['category'] = {name: category['name'], uid: category['uid']}; + if (!sub) { if (multiUserMode) { const user_uid = multiUserMode.user; From c63a64ebefacdf6f0514ae0d4756964cf896fdc7 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 23 Dec 2020 01:29:22 -0500 Subject: [PATCH 073/250] Categories will now auto-generate playlists --- backend/app.js | 35 +++++++++++++-- backend/authentication/auth.js | 44 ++++++++++++++++--- .../custom-playlists.component.ts | 2 +- .../recent-videos/recent-videos.component.ts | 2 +- .../unified-file-card.component.html | 9 +++- .../unified-file-card.component.scss | 5 +++ .../video-info-dialog.component.html | 2 +- src/app/player/player.component.ts | 4 ++ 8 files changed, 88 insertions(+), 15 deletions(-) diff --git a/backend/app.js b/backend/app.js index 98a054e..d284b3b 100644 --- a/backend/app.js +++ b/backend/app.js @@ -79,7 +79,7 @@ const logger = winston.createLogger({ }); config_api.initialize(logger); -auth_api.initialize(users_db, logger); +auth_api.initialize(db, users_db, logger); db_api.initialize(db, users_db, logger); subscriptions_api.initialize(db, users_db, logger, db_api); categories_api.initialize(db, users_db, logger, db_api); @@ -1215,7 +1215,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { const customPath = options.noRelativePath ? path.dirname(full_file_path).split(path.sep).pop() : null; // registers file in DB - file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath); + file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category); if (file_name) file_names.push(file_name); } @@ -2014,10 +2014,37 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { // get basic info depending on multi-user mode being enabled if (req.isAuthenticated()) { files = auth_api.getUserVideos(req.user.uid); - playlists = auth_api.getUserPlaylists(req.user.uid); + playlists = auth_api.getUserPlaylists(req.user.uid, files); } else { files = db.get('files').value(); - playlists = db.get('playlists').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 + }); + } + }); + } } // loop through subscriptions and add videos diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index ce8f100..e5f04ec 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -15,15 +15,16 @@ var JwtStrategy = require('passport-jwt').Strategy, // other required vars let logger = null; -var users_db = null; +let db = null; +let users_db = null; let SERVER_SECRET = null; let JWT_EXPIRATION = null; let opts = null; let saltRounds = null; -exports.initialize = function(input_users_db, input_logger) { +exports.initialize = function(input_db, input_users_db, input_logger) { setLogger(input_logger) - setDB(input_users_db); + setDB(input_db, input_users_db); /************************* * Authentication module @@ -61,7 +62,8 @@ function setLogger(input_logger) { logger = input_logger; } -function setDB(input_users_db) { +function setDB(input_db, input_users_db) { + db = input_db; users_db = input_users_db; } @@ -310,9 +312,39 @@ exports.removePlaylist = function(user_uid, playlistID) { return true; } -exports.getUserPlaylists = function(user_uid) { +exports.getUserPlaylists = function(user_uid, user_files = null) { const user = users_db.get('users').find({uid: user_uid}).value(); - return user['playlists']; + 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; } exports.getUserPlaylist = function(user_uid, playlistID, requireSharing = false) { diff --git a/src/app/components/custom-playlists/custom-playlists.component.ts b/src/app/components/custom-playlists/custom-playlists.component.ts index d2d65d6..73e3036 100644 --- a/src/app/components/custom-playlists/custom-playlists.component.ts +++ b/src/app/components/custom-playlists/custom-playlists.component.ts @@ -62,7 +62,7 @@ export class CustomPlaylistsComponent implements OnInit { } 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}]); + this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID, uid: playlistID, auto: playlist.auto}]); } } else { // playlist not found diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 7ca6c9b..83bd72f 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -98,7 +98,7 @@ export class RecentVideosComponent implements OnInit { private filterFiles(value: string) { const filterValue = value.toLowerCase(); - this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue) || option.category?.toLowerCase().includes(filterValue)); + this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue) || option.category?.name?.toLowerCase().includes(filterValue)); this.pageChangeEvent({pageSize: this.pageSize, pageIndex: this.paginator.pageIndex}); } diff --git a/src/app/components/unified-file-card/unified-file-card.component.html b/src/app/components/unified-file-card/unified-file-card.component.html index a080324..066d78e 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.html +++ b/src/app/components/unified-file-card/unified-file-card.component.html @@ -1,5 +1,10 @@
-
{{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}}  {{file_obj.registered | date:'shortDate' : undefined : locale.ngID}}
+
+ {{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}} +    + Auto-generated + {{file_obj.registered | date:'shortDate' : undefined : locale.ngID}} +
- + diff --git a/src/app/components/unified-file-card/unified-file-card.component.scss b/src/app/components/unified-file-card/unified-file-card.component.scss index 067ef4a..8d8ea1a 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.scss +++ b/src/app/components/unified-file-card/unified-file-card.component.scss @@ -111,6 +111,11 @@ top: 1px; left: 5px; z-index: 99999; + width: calc(100% - 8px); + white-space: nowrap; + overflow: hidden; + display: block; + text-overflow: ellipsis; } .audio-video-icon { diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html index 203cfd5..e62d1c9 100644 --- a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html @@ -27,7 +27,7 @@
Category: 
-
{{file.category}}N/A
+
{{file.category.name}}N/A
diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index c2aa1c2..123e2b4 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -207,6 +207,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { } getPlaylistFiles() { + if (this.route.snapshot.paramMap.get('auto') === 'true') { + this.show_player = true; + return; + } this.postsService.getPlaylist(this.id, null, this.uuid).subscribe(res => { if (res['playlist']) { this.db_playlist = res['playlist']; From 48350936067417729de11054d55304097861b68e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 00:10:54 -0500 Subject: [PATCH 074/250] Fixed issue where some non-YT videos would fail as the pre-check was incompatible --- backend/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index d284b3b..8c032c4 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1119,10 +1119,10 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { // get video info prior to download let info = await getVideoInfoByURL(url, downloadConfig, download); - if (!info) { + if (!info && url.includes('youtu')) { resolve(false); return; - } else { + } else if (info) { // check if it fits into a category. If so, then get info again using new downloadConfig category = await categories_api.categorize(info); From c08993e20bd471135a49bd53a811a7ff95fe5b74 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 02:02:05 -0500 Subject: [PATCH 075/250] Old database files are now backed up prior to migration to simplified structure --- backend/app.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/app.js b/backend/app.js index 8c032c4..8730f36 100644 --- a/backend/app.js +++ b/backend/app.js @@ -260,6 +260,13 @@ async function runFilesToDBMigration() { } async function simplifyDBFileStructure() { + // back up db files + const old_db_file = fs.readJSONSync('./appdata/db.json'); + const old_users_db_file = fs.readJSONSync('./appdata/users.json'); + fs.writeJSONSync('appdata/db.old.json', old_db_file); + fs.writeJSONSync('appdata/users.old.json', old_users_db_file); + + // simplify let users = users_db.get('users').value(); for (let i = 0; i < users.length; i++) { const user = users[i]; From 33fc74b7e763178fa3eee60ef6995f9641b0b9aa Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 02:04:43 -0500 Subject: [PATCH 076/250] Updated dev config --- src/assets/default.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/assets/default.json b/src/assets/default.json index 893896f..532b32e 100644 --- a/src/assets/default.json +++ b/src/assets/default.json @@ -26,7 +26,9 @@ "use_API_key": false, "API_key": "", "use_youtube_API": false, - "youtube_API_key": "" + "youtube_API_key": "", + "use_twitch_API": false, + "twitch_API_key": "" }, "Themes": { "default_theme": "default", @@ -57,7 +59,8 @@ "allow_advanced_download": true, "jwt_expiration": 86400, "logger_level": "debug", - "use_cookies": false + "use_cookies": false, + "default_downloader": "youtube-dlc" } } } \ No newline at end of file From 0e7bc1979fe4e4ed8139c3bb5d386a102243f0fd Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 02:05:30 -0500 Subject: [PATCH 077/250] Updated versioning info --- backend/consts.js | 2 +- package.json | 2 +- src/app/consts.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/consts.js b/backend/consts.js index 64e6e09..fa14171 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -196,5 +196,5 @@ AVAILABLE_PERMISSIONS = [ module.exports = { CONFIG_ITEMS: CONFIG_ITEMS, AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS, - CURRENT_VERSION: 'v4.1' + CURRENT_VERSION: 'v4.2' } diff --git a/package.json b/package.json index e34f8a8..ab15811 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "youtube-dl-material", - "version": "4.1.0", + "version": "4.2.0", "license": "MIT", "scripts": { "ng": "ng", diff --git a/src/app/consts.ts b/src/app/consts.ts index ad0197d..950aff6 100644 --- a/src/app/consts.ts +++ b/src/app/consts.ts @@ -1 +1 @@ -export const CURRENT_VERSION = 'v4.1'; +export const CURRENT_VERSION = 'v4.2'; From 3c206c31d5209331eb1d2600a17050480e3e3fa1 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 03:21:56 -0500 Subject: [PATCH 078/250] Updated translations base file --- src/assets/i18n/messages.en.xlf | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/assets/i18n/messages.en.xlf b/src/assets/i18n/messages.en.xlf index a624153..edad180 100644 --- a/src/assets/i18n/messages.en.xlf +++ b/src/assets/i18n/messages.en.xlf @@ -342,7 +342,7 @@ src/app/subscription/subscription/subscription.component.html - 28 + 29 Videos title @@ -882,7 +882,7 @@ src/app/subscription/subscription/subscription.component.html - 32 + 33 Files search placeholder @@ -950,7 +950,7 @@ src/app/components/unified-file-card/unified-file-card.component.html - 32 + 37 Playlist edit button @@ -966,11 +966,11 @@ src/app/components/unified-file-card/unified-file-card.component.html - 28 + 33 src/app/components/unified-file-card/unified-file-card.component.html - 34 + 39 Delete playlist @@ -982,7 +982,7 @@ src/app/components/unified-file-card/unified-file-card.component.html - 19 + 24 src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -1006,7 +1006,7 @@ src/app/components/unified-file-card/unified-file-card.component.html - 29 + 34 Delete and blacklist video button @@ -2186,11 +2186,19 @@ Clear logs button + + Auto-generated + + src/app/components/unified-file-card/unified-file-card.component.html + 5 + + Auto-generated label + Open file src/app/components/unified-file-card/unified-file-card.component.html - 13 + 18 Open file button @@ -2198,7 +2206,7 @@ Open file in new tab src/app/components/unified-file-card/unified-file-card.component.html - 14 + 19 Open file in new tab @@ -2206,7 +2214,7 @@ Go to subscription src/app/components/unified-file-card/unified-file-card.component.html - 20 + 25 Go to subscription menu item @@ -2214,7 +2222,7 @@ Delete and redownload src/app/components/unified-file-card/unified-file-card.component.html - 23 + 28 src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -2226,7 +2234,7 @@ Delete forever src/app/components/unified-file-card/unified-file-card.component.html - 26 + 31 src/app/subscription/subscription-file-card/subscription-file-card.component.html From a1af5496c7b8bd53a6789165ef41d7a773cc9b26 Mon Sep 17 00:00:00 2001 From: Tzahi12345 Date: Thu, 24 Dec 2020 03:41:38 -0500 Subject: [PATCH 079/250] Update README.md Updated preview images in README --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0599607..c073161 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,11 @@ Check out the prerequisites, and go to the installation section. Easy as pie! Here's an image of what it'll look like once you're done: -![frontpage](https://i.imgur.com/w8iofbb.png) - -With optional file management enabled (default): - -![frontpage_with_files](https://i.imgur.com/FTATqBM.png) + Dark mode: -![dark_mode](https://i.imgur.com/r5ZtBqd.png) + ### Prerequisites From c19e0bb881c5cf1f0661d621859fcf34ef4c8653 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 16:08:46 -0500 Subject: [PATCH 080/250] Adds manually-triggered GH workflow for release builds --- .github/workflows/docker-release.yml | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/docker-release.yml diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml new file mode 100644 index 0000000..22036f5 --- /dev/null +++ b/.github/workflows/docker-release.yml @@ -0,0 +1,32 @@ +name: docker + +on: + workflow_dispatch: + inputs: + tags: + description: 'Docker tags' + required: true + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: setup platform emulator + uses: docker/setup-qemu-action@v1 + - name: setup multi-arch docker build + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build & push images + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm,linux/arm64/v8 + push: true + tags: ${{ github.event.inputs.tags }} From 75c1c9e9b7fc841e13f4baa43a3a3779c6732bb1 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 24 Dec 2020 16:10:57 -0500 Subject: [PATCH 081/250] Fixed name of docker release workflow --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 22036f5..96ec2c8 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -1,4 +1,4 @@ -name: docker +name: docker-release on: workflow_dispatch: From b0cb09309d67a8f4bc33437b33b70f47d2210d51 Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Fri, 25 Dec 2020 18:21:05 +0100 Subject: [PATCH 082/250] Fix release asset name creation The complete git ref name was used as part of the release asset filename for tagged commits. This includes the refs/tags prefix, which fails as "/" characters can't be part of filenames. To work around this, a step is added that extracts the pure tag name first. --- .github/workflows/build.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9eb71be..db12f7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,17 +69,20 @@ jobs: with: name: youtubedl-material path: ${{runner.temp}}/youtubedl-material + - name: extract tag name + id: tag_name + run: echo ::set-output name=tag_name::${GITHUB_REF#refs/tags/} - name: prepare release asset shell: pwsh - run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ github.ref }}.zip + run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip - name: upload build asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./youtubedl-material-${{ github.ref }}.zip - asset_name: youtubedl-material-${{ github.ref }}.zip + asset_path: ./youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip + asset_name: youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip asset_content_type: application/zip - name: upload docker-compose asset uses: actions/upload-release-asset@v1 From 8e4e0c790855ffa4ed5932ed265c64a5f5b6ce4b Mon Sep 17 00:00:00 2001 From: Florian Gabsteiger Date: Fri, 25 Dec 2020 18:32:32 +0100 Subject: [PATCH 083/250] fix wrongly named ci step --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db12f7b..d3b911e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,7 +75,7 @@ jobs: - name: prepare release asset shell: pwsh run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip - - name: upload build asset + - name: upload release asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From f74ce4b8651984a18db2f07345e8a51f4dd8a03b Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 26 Dec 2020 15:35:13 -0500 Subject: [PATCH 084/250] Fixed bug that caused the UI to fail loading after creating a user in multi-user mode --- backend/authentication/auth.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index e5f04ec..442d7fa 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -536,14 +536,8 @@ function generateUserObject(userid, username, hash, auth_method = 'internal') { name: username, uid: userid, passhash: auth_method === 'internal' ? hash : null, - files: { - audio: [], - video: [] - }, - playlists: { - audio: [], - video: [] - }, + files: [], + playlists: [], subscriptions: [], created: Date.now(), role: userid === 'admin' && auth_method === 'internal' ? 'admin' : 'user', From dbf08e127658b58e2003e19f631892462535d5d4 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 26 Dec 2020 15:51:13 -0500 Subject: [PATCH 085/250] Fixed bug where audio files that had a stale webm extension in the metadata file path would fail to register --- backend/app.js | 20 +++++++------------- backend/db.js | 2 +- backend/utils.js | 7 +++++++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/backend/app.js b/backend/app.js index 8730f36..189492d 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1188,7 +1188,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } // get filepath with no extension - const filepath_no_extension = removeFileExtension(output_json['_filename']); + const filepath_no_extension = utils.removeFileExtension(output_json['_filename']); var full_file_path = filepath_no_extension + ext; var file_name = filepath_no_extension.substring(fileFolderPath.length, filepath_no_extension.length); @@ -1310,7 +1310,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) { video.on('info', function(info) { video_info = info; file_size = video_info.size; - const json_path = removeFileExtension(video_info._filename) + '.info.json'; + const json_path = utils.removeFileExtension(video_info._filename) + '.info.json'; fs.ensureFileSync(json_path); fs.writeJSONSync(json_path, video_info); video.pipe(fs.createWriteStream(video_info._filename, { flags: 'w' })) @@ -1336,14 +1336,14 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) { let difference = (new_date - date)/1000; logger.debug(`Video download delay: ${difference} seconds.`); download['timestamp_end'] = Date.now(); - download['fileNames'] = [removeFileExtension(video_info._filename) + ext]; + download['fileNames'] = [utils.removeFileExtension(video_info._filename) + ext]; download['complete'] = true; updateDownloads(); // audio-only cleanup if (is_audio) { // filename fix - video_info['_filename'] = removeFileExtension(video_info['_filename']) + '.mp3'; + video_info['_filename'] = utils.removeFileExtension(video_info['_filename']) + '.mp3'; // ID3 tagging let tags = { @@ -1353,8 +1353,8 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) { let success = NodeID3.write(tags, video_info._filename); if (!success) logger.error('Failed to apply ID3 tag to audio file ' + video_info._filename); - const possible_webm_path = removeFileExtension(video_info['_filename']) + '.webm'; - const possible_mp4_path = removeFileExtension(video_info['_filename']) + '.mp4'; + const possible_webm_path = utils.removeFileExtension(video_info['_filename']) + '.webm'; + const possible_mp4_path = utils.removeFileExtension(video_info['_filename']) + '.mp4'; // check if audio file is webm if (fs.existsSync(possible_webm_path)) await convertFileToMp3(possible_webm_path, video_info['_filename']); else if (fs.existsSync(possible_mp4_path)) await convertFileToMp3(possible_mp4_path, video_info['_filename']); @@ -1371,7 +1371,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) { fs.appendFileSync(archive_path, diff); } - videopathEncoded = encodeURIComponent(removeFileExtension(base_file_name)); + videopathEncoded = encodeURIComponent(utils.removeFileExtension(base_file_name)); resolve({ [is_audio ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded, @@ -1778,12 +1778,6 @@ async function checkExistsWithTimeout(filePath, timeout) { }); } -function removeFileExtension(filename) { - const filename_parts = filename.split('.'); - filename_parts.splice(filename_parts.length - 1) - return filename_parts.join('.'); -} - app.use(function(req, res, next) { res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization"); res.header("Access-Control-Allow-Origin", getOrigin()); diff --git a/backend/db.js b/backend/db.js index 38dfdcb..6d5cdb6 100644 --- a/backend/db.js +++ b/backend/db.js @@ -17,7 +17,7 @@ function initialize(input_db, input_users_db, input_logger) { function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null, category = null) { let db_path = null; - const file_id = file_path.substring(0, file_path.length-4); + const file_id = utils.removeFileExtension(file_path); const file_object = generateFileObject(file_id, type, customPath || multiUserMode && multiUserMode.file_path, sub); if (!file_object) { logger.error(`Could not find associated JSON file for ${type} file ${file_id}`); diff --git a/backend/utils.js b/backend/utils.js index b18ed6a..3524041 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -181,6 +181,12 @@ async function recFindByExt(base,ext,files,result) return result } +function removeFileExtension(filename) { + const filename_parts = filename.split('.'); + filename_parts.splice(filename_parts.length - 1); + return filename_parts.join('.'); +} + // objects function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) { @@ -210,5 +216,6 @@ module.exports = { deleteJSONFile: deleteJSONFile, getDownloadedFilesByType: getDownloadedFilesByType, recFindByExt: recFindByExt, + removeFileExtension: removeFileExtension, File: File } From 250f150587408c645db5c98c4b87dcf61f027968 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 26 Dec 2020 18:56:01 -0500 Subject: [PATCH 086/250] Download checker now only runs if the video info was successfully retrieved --- backend/app.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/app.js b/backend/app.js index 189492d..b9f3fab 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1124,6 +1124,8 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { const download = downloads[session][download_uid]; updateDownloads(); + let download_checker = null; + // get video info prior to download let info = await getVideoInfoByURL(url, downloadConfig, download); if (!info && url.includes('youtu')) { @@ -1144,13 +1146,12 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { // store info in download for future use download['_filename'] = info['_filename']; download['filesize'] = utils.getExpectedFileSize(info); + download_checker = setInterval(() => checkDownloadPercent(download), 1000); } - const download_checker = setInterval(() => checkDownloadPercent(download), 1000); - // download file youtubedl.exec(url, downloadConfig, {}, function(err, output) { - clearInterval(download_checker); // stops the download checker from running as the download finished (or errored) + if (download_checker) clearInterval(download_checker); // stops the download checker from running as the download finished (or errored) download['downloading'] = false; download['timestamp_end'] = Date.now(); From cf1dd43d3610ee81ad6d63c5214f6c19d2092b4d Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 26 Dec 2020 19:27:03 -0500 Subject: [PATCH 087/250] Fixed bug that prevented the menu for file cards from being opened --- .../unified-file-card/unified-file-card.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/unified-file-card/unified-file-card.component.scss b/src/app/components/unified-file-card/unified-file-card.component.scss index 8d8ea1a..0c06b22 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.scss +++ b/src/app/components/unified-file-card/unified-file-card.component.scss @@ -110,7 +110,7 @@ position: absolute; top: 1px; left: 5px; - z-index: 99999; + z-index: 9999; width: calc(100% - 8px); white-space: nowrap; overflow: hidden; From 085849c7ee7b6e6e1103c2d719bc0cc6f2b7c929 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 27 Dec 2020 21:13:24 -0500 Subject: [PATCH 088/250] Fixed bug that prevented the menu for file cards from being opened (2) --- .../unified-file-card/unified-file-card.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/unified-file-card/unified-file-card.component.scss b/src/app/components/unified-file-card/unified-file-card.component.scss index 0c06b22..9726f44 100644 --- a/src/app/components/unified-file-card/unified-file-card.component.scss +++ b/src/app/components/unified-file-card/unified-file-card.component.scss @@ -110,7 +110,7 @@ position: absolute; top: 1px; left: 5px; - z-index: 9999; + z-index: 999; width: calc(100% - 8px); white-space: nowrap; overflow: hidden; From ea959547fdd68a37a8901c182578abb94b0f2368 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 28 Dec 2020 00:22:14 -0500 Subject: [PATCH 089/250] Fixed bug where file indices were incorrectly assigned --- src/app/components/recent-videos/recent-videos.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 83bd72f..68125cf 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -127,12 +127,12 @@ export class RecentVideosComponent implements OnInit { this.normal_files_received = false; this.postsService.getAllFiles().subscribe(res => { this.files = res['files']; + this.files.sort(this.sortFiles); for (let i = 0; i < this.files.length; i++) { const file = this.files[i]; file.duration = typeof file.duration !== 'string' ? file.duration : this.durationStringToNumber(file.duration); file.index = i; } - this.files.sort(this.sortFiles); if (this.search_mode) { this.filterFiles(this.search_text); } else { From b6c09324d93f924481441fdebabcec6cd9d8d70c Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 1 Jan 2021 17:29:50 -0500 Subject: [PATCH 090/250] Updated error messages to make them more verbose and fixed ID3 tagging for file names --- backend/app.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index b9f3fab..6f51c45 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1215,7 +1215,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { title: output_json['title'], artist: output_json['artist'] ? output_json['artist'] : output_json['uploader'] } - let success = NodeID3.write(tags, output_json['_filename']); + let success = NodeID3.write(tags, utils.removeFileExtension(output_json['_filename']) + '.mp3'); if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']); } @@ -1535,6 +1535,9 @@ async function getVideoInfoByURL(url, args = [], download = null) { resolve(output); } else { logger.error(`Error while retrieving info on video with URL ${url} with the following message: ${err}`); + if (err.stderr) { + logger.error(`${err.stderr}`) + } if (download) { download['error'] = `Failed pre-check for video info: ${err}`; updateDownloads(); @@ -1562,7 +1565,7 @@ async function getUrlInfos(urls) { let difference = (new_date - startDate)/1000; logger.debug(`URL info retrieval delay: ${difference} seconds.`); if (err) { - logger.error('Error during parsing:' + err); + logger.error(`Error during parsing: ${err}`); resolve(null); } let try_putput = null; From c09dd7a03ba9f7393b7dac28e96720e3576430ae Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 1 Jan 2021 17:38:08 -0500 Subject: [PATCH 091/250] Updated Chinese and Spanish translations and added Italian translations --- src/app/settings/settings.component.ts | 2 +- src/assets/i18n/messages.es.json | 28 +- src/assets/i18n/messages.es.xlf | 2261 +++++++++++++-------- src/assets/i18n/messages.it.json | 249 +++ src/assets/i18n/messages.it.xlf | 2482 +++++++++++++++++++++++ src/assets/i18n/messages.zh.json | 33 +- src/assets/i18n/messages.zh.xlf | 2578 ++++++++++++++++++++++++ 7 files changed, 6835 insertions(+), 798 deletions(-) create mode 100644 src/assets/i18n/messages.it.json create mode 100644 src/assets/i18n/messages.it.xlf create mode 100644 src/assets/i18n/messages.zh.xlf diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 224a1b2..824b270 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -20,7 +20,7 @@ import { EditCategoryDialogComponent } from 'app/dialogs/edit-category-dialog/ed }) export class SettingsComponent implements OnInit { all_locales = isoLangs; - supported_locales = ['en', 'es', 'de', 'fr', 'zh', 'nb', 'en-GB']; + supported_locales = ['en', 'es', 'de', 'fr', 'zh', 'nb', 'it', 'en-GB']; initialLocale = localStorage.getItem('locale'); initial_config = null; diff --git a/src/assets/i18n/messages.es.json b/src/assets/i18n/messages.es.json index 91585e4..c7e3277 100644 --- a/src/assets/i18n/messages.es.json +++ b/src/assets/i18n/messages.es.json @@ -241,5 +241,31 @@ "3697f8583ea42868aa269489ad366103d94aece7": "Editando", "0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Nivel de registro", "a8b7b9c168fd936a75e500806a8c0d7755ef1198": "NOTA: La carga de cookies nuevas anulará las cookies anteriores y las cookies son para toda la instancia, no por usuario.", - "511b600ae4cf037e4eb3b7a58410842cd5727490": "Agregar más contenido" + "511b600ae4cf037e4eb3b7a58410842cd5727490": "Agregar más contenido", + "56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Ver menos.", + "ddc31f2885b1b33a7651963254b0c197f2a64086": "Ver más.", + "c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Seleccione un agente de descarga", + "5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Descarga automática de Twitch Chat", + "84ffcebac2709ca0785f4a1d5ba274433b5beabc": "También conocido como ID de cliente.", + "8ae23bc4302a479f687f4b20a84c276182e2519c": "Clave de API de Twitch", + "d162f9fcd6a7187b391e004f072ab3da8377c47d": "Usar la API de Twitch", + "04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorías", + "ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Args personalizados globales", + "1148fd45287ff09955b938756bc302042bcb29c7": "La ruta es relativa a las rutas de descarga anteriores. No incluya la extensión.", + "cfe829634b1144bc44b6d38cf5584ea65db9804f": "Salida de archivo predeterminada", + "3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Volver a descargar nuevas cargas", + "13759b09a7f4074ceee8fa2f968f9815fdf63295": "A veces, los videos nuevos se descargan antes de procesarse por completo. Esta configuración significará que los nuevos videos se verificarán para una versión de mayor calidad al día siguiente.", + "dad95154dcef3509b8cc705046061fd24994bbb7": "vistas", + "792dc6a57f28a1066db283f2e736484f066005fd": "Descargar Twitch Chat", + "e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Añadir nueva regla", + "2489eefea00931942b91f4a1ae109514b591e2e1": "Reglas", + "c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Editando la categoría", + "07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Pausado", + "73423607944a694ce6f9e55cfee329681bb4d9f9": "No se encontraron vídeos.", + "29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Orden inverso", + "33026f57ea65cd9c8a5d917a08083f71a718933a": "Orden normal", + "5caadefa4143cf6766a621b0f54f91f373a1f164": "Añadir contenido", + "0cc1dec590ecd74bef71a865fb364779bc42a749": "Categoría:", + "303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Pausado)", + "d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Calidad máxima" } \ No newline at end of file diff --git a/src/assets/i18n/messages.es.xlf b/src/assets/i18n/messages.es.xlf index f116163..04a30ba 100644 --- a/src/assets/i18n/messages.es.xlf +++ b/src/assets/i18n/messages.es.xlf @@ -1,799 +1,1474 @@ - + - - No need to include URL, just everything after. - No es necesario incluir URL, solo todo después - - - Global custom args for downloads on the home page. - Argumentos personalizados globales para descargas en la página de inicio. - - - Create a playlist - Crea una lista de reproducción - - - Name - Nombre - - - Audio files - Archivos de sonido - - - Videos - Archivos de video - - - Modify youtube-dl args - Modificar args de youtube-dl - - - Simulated new args - Args nuevos simulados - - - Add an arg - Añadir un arg - - - Search by category - Busqueda por categoria - - - Use arg value - Usar valor de arg - - - Arg value - Valor de arg - - - Add arg - Añadir arg - - - Cancel - Cancelar - - - Modify - Modificar - - - Youtube Downloader - Descargador de Youtube - - - Please enter a valid URL! - Por favor entre una URL válida - - - Quality - Calidad - - - Use URL - Usa URL - - - View - Ver - - - Only Audio - Solo audio - - - Multi-download Mode - Descarga múltiple - - - Download - Descarga - - - Cancel - Cancelar - - - Advanced - Avanzado - - - Simulated command: - Commando simulado: - - - Use custom args - Usar argumentos personalizados - - - Custom args - Argumentos personalizados - - - No need to include URL, just everything after. Args are delimited using two commas like so: ,, - No es necesario incluir URL, solo todo después. Los argumentos se delimitan usando dos comas así: ,, - - - Use custom output - Usar salida personalizada - - - Custom output - Salida personalizada - - - Documentation - Documentación - - - Path is relative to the config download path. Don't include extension. - La ruta es relativa a la ruta de descarga de la config. No incluya el extensión. - - - Use authentication - Usa autenticación - - - Username - Nombre de usuario - - - Password - Contraseña - - - Audio - Audio - - - Your audio files are here - Tus archivos de audio están aquí - - - Playlists - Listas de reproducción - - - No playlists available. Create one from your downloading audio files by clicking the blue plus button. - No hay listas de reproducción disponibles. Cree uno de tus archivos de audio haciendo clic en el botón azul más. - - - Video - Vídeo - - - Your video files are here - Tus archivos de video son aquí - - - No playlists available. Create one from your downloading video files by clicking the blue plus button. - No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más. - - - Name: - Nombre: - - - URL: - URL: - - - Uploader: - Cargador: - - - File size: - Tamaño del archivo: - - - Path: - Ruta: - - - Upload Date: - Subido: - - - Close - Cerca - - - ID: - ID: - - - Count: - Cuenta: - - - Info - Información - - - Delete - Eliminar - - - Delete and blacklist - Eliminar y pones en la lista negra - - - Settings - Configuraciones - - - URL - URL - - - URL this app will be accessed from, without the port. - URL desde la que se accederá a esta aplicación, sin el puerto. - - - Port - Puerto - - - The desired port. Default is 17442. - Puerto deseado. El valor predeterminado es 17442. - - - Multi-user mode - Modo multiusuario - - - Users base path - Ruta base de usuarios - - - Base path for users and their downloaded videos. - Ruta base para los usuarios y sus videos descargados. - - - Use encryption - Usa cifrado - - - Cert file path - Ruta del archivo de certificado - - - Key file path - Ruta de archivo de clave - - - Allow subscriptions - Permitir suscripciones - - - Subscriptions base path - Ruta base de suscripciones - - - Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. - Ruta base para videos de sus canales y listas de reproducción suscritos. Es relativo a la carpeta raíz de YTDL-Material. - - - Check interval - Intervalo de comprobación - - - Unit is seconds, only include numbers. - La unidad es segundos, solo incluye números. - - - Use youtube-dl archive - Usa el archivo de youtube-dl - - - With youtube-dl's archive - Con la función de archivo de youtube-dl, - - - feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. - los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones. - - - This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. - Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos. - - - Theme - Tema - - - Default - Defecto - - - Dark - Oscura - - - Allow theme change - Permitir cambio de tema - - - Language - Idioma - - - Main - Principal - - - Audio folder path - Ruta de la carpeta de audio - - - Path for audio only downloads. It is relative to YTDL-Material's root folder. - Ruta para descargas de solo audio. Es relativo a la carpeta raíz de YTDL-Material. - - - Video folder path - Ruta de la carpeta de video - - - Path for video downloads. It is relative to YTDL-Material's root folder. - Ruta de descarga de videos. Es relativo a la carpeta raíz de YTDL-Material. - - - Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, - Argumentos personalizados globales para descargas en la página de inicio. Los argumentos se delimitan usando dos comas así: ,, - - - Downloader - Descargador - - - Top title - Título superior - - - File manager enabled - Administrador de archivos habilitado - - - Downloads manager enabled - Administrador de descargas habilitado - - - Allow quality select - Permitir selección de calidad - - - Download only mode - Modo de solo descarga - - - Allow multi-download mode - Permitir el modo de descarga múltiple - - - Require pin for settings - Requiere pin para la configuración - - - Set New Pin - Establecer nuevo pin - - - Enable Public API - Habilitar API pública - - - Public API Key - Clave API pública - - - View documentation - Ver documentación - - - Generate - Generar - - - Use YouTube API - Utilizar la API de YouTube - - - Youtube API Key - Clave API de YouTube - - - Generating a key is easy! - ¡Generar una clave es fácil! - - - Click here - ¡Haga clic aquí - - - to download the official YoutubeDL-Material Chrome extension manually. - para descargar la extensión Chrome oficial de YoutubeDL-Material manualmente. - - - You must manually load the extension and modify the extension's settings to set the frontend URL. - Debe cargar manualmente la extensión y modificar la configuración de la extensión para establecer la URL de la interfaz. - - - to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. - para instalar la extensión Firefox oficial de YoutubeDL-Material directamente desde la página de extensiones de Firefox. - - - Detailed setup instructions. - Instrucciones detalladas de configuración. - - - Not much is required other than changing the extension's settings to set the frontend URL. - No se requiere mucho más que cambiar la configuración de la extensión para establecer la URL de la interfaz. - - - Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. - Arrastra el enlace de abajo a tus marcadores, ¡y listo! Simplemente navegue hasta el video de YouTube que desea descargar y haga clic en el marcador. - - - Generate 'audio only' bookmarklet - Generar bookmarklet solo de audio - - - Extra - Extra - - - Use default downloading agent - Usar agente de descarga predeterminado - - - Select a downloader - Seleccione un descargador - - - Allow advanced download - Permitir descarga avanzada - - - Advanced - Avanzado - - - Allow user registration - Permitir registro de usuario - - - Users - Usuarios - - - Save - Salvar - - - {VAR_SELECT, select, true {Close} false {Cancel} other {otha} } - {VAR_SELECT, select, true {Cerrar} false {Cancelar} other {Otro} } - - - About YoutubeDL-Material - Sobre YoutubeDL-Material - - - is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. - es un descargador de código abierto de YouTube creado bajo las especificaciones de "Material Design" de Google. Puede descargar sin problemas sus videos favoritos como archivos de video o audio, e incluso suscribirse a sus canales favoritos y listas de reproducción para mantenerse actualizado con sus nuevos videos. - - - has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. - tiene algunas características increíbles incluidas! Una amplia API, soporte de Docker y soporte de localización (traducción). Lea todas las funciones compatibles haciendo clic en el icono de GitHub que se encuentra arriba. - - - Installed version: - Versión instalada: - - - Checking for updates... - Comprobando actualizaciones... - - - Update available - Actualización disponible - - - You can update from the settings menu. - Puede actualizar desde el menú de configuración. - - - Found a bug or have a suggestion? - ¿Encontró un error o tiene una sugerencia? - - - to create an issue! - para crear una cuestión! - - - Your Profile - Tu perfil - - - UID: - UID: - - - Created: - Creado: - - - You are not logged in. - Usted no se ha identificado. - - - Login - Identificarse - - - Logout - Salir - - - Create admin account - Crear cuenta de administrador - - - No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. - No se detectó una cuenta de administrador predeterminada. Esto creará y establecerá la contraseña para una cuenta de administrador con el nombre de usuario como 'admin'. - - - Create - Crear - - - Profile - Perfil - - - About - Sobre - - - Home - Inicio - - - Subscriptions - Suscripciones - - - Downloads - Descargas - - - Share playlist - Compartir lista de reproducción - - - Share video - Compartir vídeo - - - Share audio - Compartir audio - - - Enable sharing - Habilitar compartir - - - Use timestamp - Usar marca de tiempo - - - Seconds - Segundos - - - Copy to clipboard - Copiar al Portapapeles - - - Save changes - Guardar cambios - - - Details - Detalles - - - An error has occured: - Se ha producido un error: - - - Download start: - Inicio de descarga: - - - Download end: - Fin de descarga: - - - File path(s): - Ruta(s) del archivo: - - - Subscribe to playlist or channel - Suscríbase a la lista de reproducción o al canal - - - The playlist or channel URL - La lista de reproducción o la URL del canal - - - Custom name - Nombre personalizado - - - This is optional - Esto es opcional - - - Download all uploads - Descargar todas las cargas - - - Download videos uploaded in the last - Descargar videos subidos en el último - - - Streaming-only mode - Modo de solo transmisión - - - Subscribe - Subscribe - - - Type: - Tipo: - - - Archive: - Archivo: - - - Export Archive - Exportar el archivo - - - Unsubscribe - Darse de baja - - - Your subscriptions - Sus suscripciones - - - Channels - Canales - - - Name not available. Channel retrieval in progress. - Nombre no disponible. Recuperación de canales en progreso. - - - You have no channel subscriptions. - No tienes suscripciones de canal. - - - Name not available. Playlist retrieval in progress. - Nombre no disponible. Recuperación de listas de reproducción en progreso. - - - You have no playlist subscriptions. - No tienes suscripciones a listas de reproducción. - - - Search - Buscar - - - Length: - Duración: - - - Delete and redownload - Eliminar y volver a descargar - - - Delete forever - Borrar para siempre - - - Updater - Updater - - - Select a version: - Seleccione una versión: - - - Register - Registrarse - - - Session ID: - ID de sesión: - - - (current) - (actual) - - - No downloads available! - ¡No hay descargas disponibles! - - - Register a user - Registrar un usuario - - - User name - Nombre de usuario - - - Manage user - Administrar usuario - - - User UID: - UID de usuario: - - - New password - Nueva contraseña - - - Set new password - Establecer nueva contraseña - - - Use default - Uso por defecto - - - Yes - Si - - - No - No - - - Manage role - Gestionar rol - - - User name - Nombre de usuario - - - Role - Rol - - - Actions - Acciones - - - Add Users - Agregar Usuarios - - - Edit Role - Editar Rol - + + No need to include URL, just everything after. + No es necesario incluir URL, solo todo después + + + Global custom args for downloads on the home page. + Argumentos personalizados globales para descargas en la página de inicio. + + + Create a playlist + Crea una lista de reproducción + + + Name + Nombre + + + Audio files + Archivos de sonido + + + Videos + Archivos de video + + + Modify youtube-dl args + Modificar args de youtube-dl + + + Simulated new args + Args nuevos simulados + + + Add an arg + Añadir un arg + + + Search by category + Busqueda por categoria + + + Use arg value + Usar valor de arg + + + Arg value + Valor de arg + + + Add arg + Añadir arg + + + Cancel + Cancelar + + + Modify + Modificar + + + Youtube Downloader + Descargador de Youtube + + + Please enter a valid URL! + Por favor entre una URL válida + + + Quality + Calidad + + + Use URL + Usa URL + + + View + Ver + + + Only Audio + Solo audio + + + Multi-download Mode + Descarga múltiple + + + Download + Descarga + + + Cancel + Cancelar + + + Advanced + Avanzado + + + Simulated command: + Commando simulado: + + + Use custom args + Usar argumentos personalizados + + + Custom args + Argumentos personalizados + + + No need to include URL, just everything after. Args are delimited using two commas like so: ,, + No es necesario incluir URL, solo todo después. Los argumentos se delimitan usando dos comas así: ,, + + + Use custom output + Usar salida personalizada + + + Custom output + Salida personalizada + + + Documentation + Documentación + + + Path is relative to the config download path. Don't include extension. + La ruta es relativa a la ruta de descarga de la config. No incluya el extensión. + + + Use authentication + Usa autenticación + + + Username + Nombre de usuario + + + Password + Contraseña + + + Audio + Audio + + + Your audio files are here + Tus archivos de audio están aquí + + + Playlists + Listas de reproducción + + + No playlists available. Create one from your downloading audio files by clicking the blue plus button. + No hay listas de reproducción disponibles. Cree uno de tus archivos de audio haciendo clic en el botón azul más. + + + Video + Vídeo + + + Your video files are here + Tus archivos de video son aquí + + + No playlists available. Create one from your downloading video files by clicking the blue plus button. + No hay listas de reproducción disponibles. Cree uno de tus archivos de video haciendo clic en el botón azul más. + + + Name: + Nombre: + + + URL: + URL: + + + Uploader: + Cargador: + + + File size: + Tamaño del archivo: + + + Path: + Ruta: + + + Upload Date: + Subido: + + + Close + Cerca + + + ID: + ID: + + + Count: + Cuenta: + + + Info + Información + + + Delete + Eliminar + + + Delete and blacklist + Eliminar y pones en la lista negra + + + Settings + Configuraciones + + + URL + URL + + + URL this app will be accessed from, without the port. + URL desde la que se accederá a esta aplicación, sin el puerto. + + + Port + Puerto + + + The desired port. Default is 17442. + Puerto deseado. El valor predeterminado es 17442. + + + Multi-user mode + Modo multiusuario + + + Users base path + Ruta base de usuarios + + + Base path for users and their downloaded videos. + Ruta base para los usuarios y sus videos descargados. + + + Use encryption + Usa cifrado + + + Cert file path + Ruta del archivo de certificado + + + Key file path + Ruta de archivo de clave + + + Allow subscriptions + Permitir suscripciones + + + Subscriptions base path + Ruta base de suscripciones + + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + Ruta base para videos de sus canales y listas de reproducción suscritos. Es relativo a la carpeta raíz de YTDL-Material. + + + Check interval + Intervalo de comprobación + + + Unit is seconds, only include numbers. + La unidad es segundos, solo incluye números. + + + Use youtube-dl archive + Usa el archivo de youtube-dl + + + With youtube-dl's archive + Con la función de archivo de youtube-dl, + + + feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. + los videos descargados de sus suscripciones se graban en un archivo de texto en el subdirectorio del archivo de suscripciones. + + + This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. + Esto permite eliminar videos de sus suscripciones de forma permanente sin darse de baja y le permite grabar los videos que descargó en caso de pérdida de datos. + + + Theme + Tema + + + Default + Defecto + + + Dark + Oscura + + + Allow theme change + Permitir cambio de tema + + + Language + Idioma + + + Main + Principal + + + Audio folder path + Ruta de la carpeta de audio + + + Path for audio only downloads. It is relative to YTDL-Material's root folder. + Ruta para descargas de solo audio. Es relativo a la carpeta raíz de YTDL-Material. + + + Video folder path + Ruta de la carpeta de video + + + Path for video downloads. It is relative to YTDL-Material's root folder. + Ruta de descarga de videos. Es relativo a la carpeta raíz de YTDL-Material. + + + Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, + Argumentos personalizados globales para descargas en la página de inicio. Los argumentos se delimitan usando dos comas así: ,, + + + Downloader + Descargador + + + Top title + Título superior + + + File manager enabled + Administrador de archivos habilitado + + + Downloads manager enabled + Administrador de descargas habilitado + + + Allow quality select + Permitir selección de calidad + + + Download only mode + Modo de solo descarga + + + Allow multi-download mode + Permitir el modo de descarga múltiple + + + Require pin for settings + Requiere pin para la configuración + + + Set New Pin + Establecer nuevo pin + + + Enable Public API + Habilitar API pública + + + Public API Key + Clave API pública + + + View documentation + Ver documentación + + + Generate + Generar + + + Use YouTube API + Utilizar la API de YouTube + + + Youtube API Key + Clave API de YouTube + + + Generating a key is easy! + ¡Generar una clave es fácil! + + + Click here + ¡Haga clic aquí + + + to download the official YoutubeDL-Material Chrome extension manually. + para descargar la extensión Chrome oficial de YoutubeDL-Material manualmente. + + + You must manually load the extension and modify the extension's settings to set the frontend URL. + Debe cargar manualmente la extensión y modificar la configuración de la extensión para establecer la URL de la interfaz. + + + to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. + para instalar la extensión Firefox oficial de YoutubeDL-Material directamente desde la página de extensiones de Firefox. + + + Detailed setup instructions. + Instrucciones detalladas de configuración. + + + Not much is required other than changing the extension's settings to set the frontend URL. + No se requiere mucho más que cambiar la configuración de la extensión para establecer la URL de la interfaz. + + + Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. + Arrastra el enlace de abajo a tus marcadores, ¡y listo! Simplemente navegue hasta el video de YouTube que desea descargar y haga clic en el marcador. + + + Generate 'audio only' bookmarklet + Generar bookmarklet solo de audio + + + Extra + Extra + + + Use default downloading agent + Usar agente de descarga predeterminado + + + Select a downloader + Seleccione un descargador + + + Allow advanced download + Permitir descarga avanzada + + + Advanced + Avanzado + + + Allow user registration + Permitir registro de usuario + + + Users + Usuarios + + + Save + Salvar + + + {VAR_SELECT, select, true {Close} false {Cancel} other {otha} } + {VAR_SELECT, select, true {Cerrar} false {Cancelar} other {Otro} } + + + About YoutubeDL-Material + Sobre YoutubeDL-Material + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + es un descargador de código abierto de YouTube creado bajo las especificaciones de "Material Design" de Google. Puede descargar sin problemas sus videos favoritos como archivos de video o audio, e incluso suscribirse a sus canales favoritos y listas de reproducción para mantenerse actualizado con sus nuevos videos. + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. + tiene algunas características increíbles incluidas! Una amplia API, soporte de Docker y soporte de localización (traducción). Lea todas las funciones compatibles haciendo clic en el icono de GitHub que se encuentra arriba. + + + Installed version: + Versión instalada: + + + Checking for updates... + Comprobando actualizaciones... + + + Update available + Actualización disponible + + + You can update from the settings menu. + Puede actualizar desde el menú de configuración. + + + Found a bug or have a suggestion? + ¿Encontró un error o tiene una sugerencia? + + + to create an issue! + para crear una cuestión! + + + Your Profile + Tu perfil + + + UID: + UID: + + + Created: + Creado: + + + You are not logged in. + Usted no se ha identificado. + + + Login + Identificarse + + + Logout + Salir + + + Create admin account + Crear cuenta de administrador + + + No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. + No se detectó una cuenta de administrador predeterminada. Esto creará y establecerá la contraseña para una cuenta de administrador con el nombre de usuario como 'admin'. + + + Create + Crear + + + Profile + Perfil + + + About + Sobre + + + Home + Inicio + + + Subscriptions + Suscripciones + + + Downloads + Descargas + + + Share playlist + Compartir lista de reproducción + + + Share video + Compartir vídeo + + + Share audio + Compartir audio + + + Enable sharing + Habilitar compartir + + + Use timestamp + Usar marca de tiempo + + + Seconds + Segundos + + + Copy to clipboard + Copiar al Portapapeles + + + Save changes + Guardar cambios + + + Details + Detalles + + + An error has occured: + Se ha producido un error: + + + Download start: + Inicio de descarga: + + + Download end: + Fin de descarga: + + + File path(s): + Ruta(s) del archivo: + + + Subscribe to playlist or channel + Suscríbase a la lista de reproducción o al canal + + + The playlist or channel URL + La lista de reproducción o la URL del canal + + + Custom name + Nombre personalizado + + + This is optional + Esto es opcional + + + Download all uploads + Descargar todas las cargas + + + Download videos uploaded in the last + Descargar videos subidos en el último + + + Streaming-only mode + Modo de solo transmisión + + + Subscribe + Subscribe + + + Type: + Tipo: + + + Archive: + Archivo: + + + Export Archive + Exportar el archivo + + + Unsubscribe + Darse de baja + + + Your subscriptions + Sus suscripciones + + + Channels + Canales + + + Name not available. Channel retrieval in progress. + Nombre no disponible. Recuperación de canales en progreso. + + + You have no channel subscriptions. + No tienes suscripciones de canal. + + + Name not available. Playlist retrieval in progress. + Nombre no disponible. Recuperación de listas de reproducción en progreso. + + + You have no playlist subscriptions. + No tienes suscripciones a listas de reproducción. + + + Search + Buscar + + + Length: + Duración: + + + Delete and redownload + Eliminar y volver a descargar + + + Delete forever + Borrar para siempre + + + Updater + Updater + + + Select a version: + Seleccione una versión: + + + Register + Registrarse + + + Session ID: + ID de sesión: + + + (current) + (actual) + + + No downloads available! + ¡No hay descargas disponibles! + + + Register a user + Registrar un usuario + + + User name + Nombre de usuario + + + Manage user + Administrar usuario + + + User UID: + UID de usuario: + + + New password + Nueva contraseña + + + Set new password + Establecer nueva contraseña + + + Use default + Uso por defecto + + + Yes + Si + + + No + No + + + Manage role + Gestionar rol + + + User name + Nombre de usuario + + + Role + Rol + + + Actions + Acciones + + + Add Users + Agregar Usuarios + + + Edit Role + Editar Rol + + + Logs + Registros + + app/settings/settings.component.html + 346 + + Logs settings label + + + Search Base + Search Base + + app/settings/settings.component.html + 333 + + Search Base + + + Search Filter + Search Filter + + app/settings/settings.component.html + 338 + + Search Filter + + + Bind Credentials + Bind Credentials + + app/settings/settings.component.html + 328 + + Bind Credentials + + + Bind DN + Bind DN + + app/settings/settings.component.html + 323 + + Bind DN + + + LDAP URL + URL LDAP + + app/settings/settings.component.html + 318 + + LDAP URL + + + Auth method + Método de autenticación + + app/settings/settings.component.html + 306 + + Auth method select + + + LDAP + LDAP + + app/settings/settings.component.html + 311 + + LDAP auth method + + + Internal + Interno + + app/settings/settings.component.html + 308 + + Internal auth method + + + Set Cookies + Configurar Cookies + + app/settings/settings.component.html + 288 + + Set cookies button + + + Use Cookies + Usar Cookies + + app/settings/settings.component.html + 287 + + Use cookies setting + + + Login expiration + Caducidad de inicio de sesión + + app/settings/settings.component.html + 268 + + Login expiration select label + + + Select a logger level + Seleccione un nivel de registrador + + app/settings/settings.component.html + 256 + + Logger level select label + + + This will delete your old API key! + ¡Esto eliminará su vieja clave API! + + app/settings/settings.component.html + 187 + + delete api key tooltip + + + Kill all downloads + Mata todas las descargas + + app/settings/settings.component.html + 139 + + Kill all downloads button + + + Include metadata + Incluir metadatos + + app/settings/settings.component.html + 135 + + Include metadata setting + + + Include thumbnail + Incluir miniatura + + app/settings/settings.component.html + 131 + + Include thumbnail setting + + + NOTE: Uploading new cookies will overrride your previous cookies. Also note that cookies are instance-wide, not per-user. + NOTA: La carga de cookies nuevas anulará las cookies anteriores y las cookies son para toda la instancia, no por usuario. + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 + + Cookies upload warning + + + Drag and Drop + Arrastrar y soltar + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 11 + + Drag and Drop + + + Upload new cookies + Subir nuevas cookies + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 1 + + Cookies uploader dialog title + + + Edit + Editar + + app/file-card/file-card.component.html + 19 + + + app/components/unified-file-card/unified-file-card.component.html + 32 + + Playlist edit button + + + Modify playlist + Modificar lista de reproducción + + app/dialogs/modify-playlist/modify-playlist.component.html + 1 + + Modify playlist dialog title + + + Type + Tipo + + app/create-playlist/create-playlist.component.html + 11 + + Type select + + + Video + Vídeo + + app/create-playlist/create-playlist.component.html + 13 + + Video + + + Audio + Audio + + app/create-playlist/create-playlist.component.html + 12 + + Audio + + + My videos + Mis videos + + app/components/recent-videos/recent-videos.component.html + 20 + + My videos title + + + Go to subscription + Ir a suscripción + + app/components/unified-file-card/unified-file-card.component.html + 20 + + Go to subscription menu item + + + Open file in new tab + Abrir archivo en nueva pestaña + + app/components/unified-file-card/unified-file-card.component.html + 14 + + Open file in new tab + + + Open file + Abrir archivo + + app/components/unified-file-card/unified-file-card.component.html + 13 + + Open file button + + + Clear logs + Borrar registros + + app/components/logs-viewer/logs-viewer.component.html + 34 + + Clear logs button + + + Lines: + Líneas: + + app/components/logs-viewer/logs-viewer.component.html + 22 + + Label for lines select in logger view + + + Delete user + Eliminar usuario + + app/components/modify-users/modify-users.component.html + 73 + + delete user action button tooltip + + + Edit user + Editar usuario + + app/components/modify-users/modify-users.component.html + 66 + + edit user action button tooltip + + + Custom file output + Salida de archivo personalizado + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 59 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 43 + + Subscription custom file output placeholder + + + These are added after the standard args. + Estos se agregan después de los argumentos estándar. + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 53 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 37 + + Custom args hint + + + Audio-only mode + Modo de solo audio + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 24 + + Streaming-only mode + + + An error has occurred: + Un error ha ocurrido: + + app/download-item/download-item.component.html + 27 + + Error label + + + An error has occurred + Un error ha ocurrido + + app/download-item/download-item.component.html + 9 + + download error tooltip + + + The download was successful + La descarga era exitosa + + app/download-item/download-item.component.html + 8 + + download successful tooltip + + + Use role default + Usar rol predeterminado + + app/components/manage-user/manage-user.component.html + 19 + + Use role default + + + Clear all downloads + Claro todas las descargas + + app/components/downloads/downloads.component.html + 18 + + clear all downloads action button + + + Editing + Editando + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + Edit subscription dialog title prefix + + + Log Level + Nivel de registro + + app/settings/settings.component.html + 256 + + Log Level label + + + NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user. + NOTA: La carga de cookies nuevas anulará las cookies anteriores y las cookies son para toda la instancia, no por usuario. + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 + + Cookies upload warning + + + Add more content + Agregar más contenido + + app/dialogs/modify-playlist/modify-playlist.component.html + 17 + + Add more content + + + See less. + Ver menos. + + src/app/components/see-more/see-more.component.html + 8,9 + + See less + + + See more. + Ver más. + + src/app/components/see-more/see-more.component.html + 5,6 + + See more + + + Select a download agent + Seleccione un agente de descarga + + src/app/settings/settings.component.html + 299 + + Custom downloader select label + + + Auto-download Twitch Chat + Descarga automática de Twitch Chat + + src/app/settings/settings.component.html + 244 + + Auto download Twitch Chat setting + + + Also known as a Client ID. + También conocido como ID de cliente. + + src/app/settings/settings.component.html + 249 + + Twitch API Key setting hint AKA preamble + + + Twitch API Key + Clave de API de Twitch + + src/app/settings/settings.component.html + 248 + + Twitch API Key setting placeholder + + + Use Twitch API + Usar la API de Twitch + + src/app/settings/settings.component.html + 241 + + Use Twitch API setting + + + Categories + Categorías + + src/app/settings/settings.component.html + 144 + + Categories + + + Global custom args + Args personalizados globales + + src/app/settings/settings.component.html + 133 + + Custom args input placeholder + + + Path is relative to the above download paths. Don't include extension. + La ruta es relativa a las rutas de descarga anteriores. No incluya la extensión. + + src/app/settings/settings.component.html + 126 + + Custom Output input hint + + + Default file output + Salida de archivo predeterminada + + src/app/settings/settings.component.html + 123 + + Default file output placeholder + + + Redownload fresh uploads + Volver a descargar nuevas cargas + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads + + + Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day. + A veces, los videos nuevos se descargan antes de procesarse por completo. Esta configuración significará que los nuevos videos se verificarán para una versión de mayor calidad al día siguiente. + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads tooltip + + + views + vistas + + src/app/player/player.component.html + 15 + + View count label + + + Download Twitch Chat + Descargar Twitch Chat + + src/app/components/twitch-chat/twitch-chat.component.html + 10 + + Download Twitch Chat button + + + Add new rule + Añadir nueva regla + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 39 + + Add new rule tooltip + + + Rules + Reglas + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 10 + + Rules + + + Editing category + Editando la categoría + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 1 + + Editing category dialog title + + + Paused + Pausado + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 7 + + Paused subscription setting + + + No videos found. + No se encontraron vídeos. + + src/app/components/recent-videos/recent-videos.component.html + 38 + + No videos found + + + Reverse order + Orden inverso + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 14 + + Reverse order + + + Normal order + Orden normal + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 13 + + Normal order + + + Add content + Añadir contenido + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 19 + + Add content + + + Category: + Categoría: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 29 + + Category property + + + (Paused) + (Pausado) + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 1 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + + src/app/subscriptions/subscriptions.component.html + 12 + + + src/app/subscriptions/subscriptions.component.html + 31 + + + src/app/subscription/subscription/subscription.component.html + 5 + + Paused suffix + + + Max quality + Calidad máxima + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 32 + + Max quality placeholder + diff --git a/src/assets/i18n/messages.it.json b/src/assets/i18n/messages.it.json new file mode 100644 index 0000000..8a79d82 --- /dev/null +++ b/src/assets/i18n/messages.it.json @@ -0,0 +1,249 @@ +{ + "17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Crea una scaletta", + "cff1428d10d59d14e45edec3c735a27b5482db59": "Nome", + "f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio", + "2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video", + "f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Tipo", + "f47e2d56dd8a145b2e9599da9730c049d52962a2": "File audio", + "a52dae09be10ca3a65da918533ced3d3f4992238": "Video", + "d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Modifica i parametri di youtube-dl", + "7fc1946abe2b40f60059c6cd19975d677095fd19": "Simula i nuovi parametri", + "0b71824ae71972f236039bed43f8d2323e8fd570": "Aggiungi un'impostazione", + "c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Cerca per categoria", + "9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Usa valore impostato", + "25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Valore impostato", + "7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Aggiungi impostazione", + "d7b35c384aecd25a516200d6921836374613dfe7": "Annulla", + "b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Modifica", + "a38ae1082fec79ba1f379978337385a539a28e73": "Qualità", + "4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Utilizza URL", + "d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Visualizza", + "4a9889d36910edc8323d7bab60858ab3da6d91df": "Solo audio", + "96a01fafe135afc58b0f8071a4ab00234495ce18": "Modalità download multiplo", + "6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Scarica", + "6a3777f913cf3f288664f0632b9f24794fdcc24e": "Annulla", + "322ed150e02666fe2259c5b4614eac7066f4ffa0": "Avanzato", + "b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Comando simulato:", + "4e4c721129466be9c3862294dc40241b64045998": "Usa parametri personalizzati", + "ad2f8ac8b7de7945b80c8e424484da94e597125f": "Parametri personalizzati", + "a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Non è necessario includere l'URL, solo ciò che viene dopo. I parametri sono delimitati da due vigole: ,,", + "3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Usa output personalizzato", + "d9c02face477f2f9cdaae318ccee5f89856851fb": "Output personalizzata", + "fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentazione", + "19d1ae64d94d28a29b2c57ae8671aace906b5401": "Il percorso è relativo al percorso di download configurato. Non includere l'estensione.", + "8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Utilizza l'autenticazione", + "08c74dc9762957593b91f6eb5d65efdfc975bf48": "Nome utente", + "c32ef07f8803a223a83ed17024b38e8d82292407": "Password", + "616e206cb4f25bd5885fc35925365e43cf5fb929": "Nome:", + "c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:", + "c6eb45d085384903e53ab001a3513d1de6a1dbac": "Caricato da:", + "109c6f4a5e46efb933612ededfaf52a13178b7e0": "Dimensioni file:", + "bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Percorso:", + "a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Data di caricamento:", + "f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Chiudi", + "4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modifica playlist", + "511b600ae4cf037e4eb3b7a58410842cd5727490": "Aggiungi più contenuto", + "52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Salva", + "ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:", + "e684046d73bcee88e82f7ff01e2852789a05fc32": "Conteggio:", + "28f86ffd419b869711aa13f5e5ff54be6d70731c": "Modifica", + "826b25211922a1b46436589233cb6f1a163d89b7": "Elimina", + "321e4419a943044e674beb55b8039f42a9761ca5": "Informazioni", + "34504b488c24c27e68089be549f0eeae6ebaf30b": "Elimina e aggiungi alla lista nera", + "ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Carica nuovi cookie", + "98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Trascina e rilascia", + "a8b7b9c168fd936a75e500806a8c0d7755ef1198": "NOTA: il caricamento di nuovi cookie sovrascriverà i cookie precedenti. Inoltre tieni presente che i cookie sono a livello di processo, non per utente.", + "121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Impostazioni", + "801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL", + "54c512cca1923ab72faf1a0bd98d3d172469629a": "URL con cui si accederà a questa applicazione, senza porta.", + "cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Porta", + "22e8f1d0423a3b784fe40fab187b92c06541b577": "Porta personalizzata. La predefinita è 17442.", + "d4477669a560750d2064051a510ef4d7679e2f3e": "Modalità multiutente", + "2eb03565fcdce7a7a67abc277a936a32fcf51557": "Percorso profili utente", + "a64505c41150663968e277ec9b3ddaa5f4838798": "Percorso per profili utente e per video scaricati da ognuno.", + "4e3120311801c4acd18de7146add2ee4a4417773": "Consenti iscrizioni", + "4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Percorso salvataggio playlist sottoscritte", + "bc9892814ee2d119ae94378c905ea440a249b84a": "Percorso salvataggio per i video dei canali e delle playlist sottoscritte. È relativo alla cartella principale di YTDL-Material.", + "5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Intervallo di verifica", + "0f56a7449b77630c114615395bbda4cab398efd8": "Unità in secondi, inserire solo numeri.", + "27a56aad79d8b61269ed303f11664cc78bcc2522": "Tema", + "ff7cee38a2259526c519f878e71b964f41db4348": "Predefinito", + "adb4562d2dbd3584370e44496969d58c511ecb63": "Scuro", + "7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Consenti variazione tema", + "fe46ccaae902ce974e2441abe752399288298619": "Lingua", + "82421c3e46a0453a70c42900eab51d58d79e6599": "Principale", + "ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Percorso della cartella audio", + "c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Percorso per download solo audio. È relativo alla cartella principale di YTDL-Material.", + "46826331da1949bd6fb74624447057099c9d20cd": "Percorso cartella Video", + "17c92e6d47a213fa95b5aa344b3f258147123f93": "Percorso per il download di video. È relativo alla cartella principale di YTDL-Material.", + "6b995e7130b4d667eaab6c5f61b362ace486d26d": "Parametri personalizzati generali per i download sulla home page. I parametri sono delimitati da due virgole: ,,", + "78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Usa l'archivio youtube-dl", + "ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Includi anteprima", + "384de8f8f112c9e6092eb2698706d391553f3e8d": "Includi metadati", + "fb35145bfb84521e21b6385363d59221f436a573": "Interrompi tutti i download", + "0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Scaricato da", + "61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Titolo della barra superiore", + "78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Abilita il file manager", + "a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Abilita il download manager", + "c33bd5392b39dbed36b8e5a1145163a15d45835f": "Consenti la selezione della qualità", + "bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Modalità solo download", + "09d31c803a7252658694e1e3176b97f5655a3fe3": "Consenti la modalità di download multiplo", + "1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Abilita l'API Pubblica", + "23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Chiave API Pubblica", + "41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Visualizza la documentazione", + "1b258b258b4cc475ceb2871305b61756b0134f4a": "Genera", + "00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Cancellerai la tua chiave API precedente!", + "d5d7c61349f3b0859336066e6d453fc35d334fe5": "Usa l'API di YouTube", + "ce10d31febb3d9d60c160750570310f303a22c22": "Chiave API YouTube", + "8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Generare una chiave è facile!", + "9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Premi qui", + "7f09776373995003161235c0c8d02b7f91dbc4df": "per scaricare manualmente l'estensione ufficiale per Chrome di YoutubeDL-Material.", + "5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "È necessario installare manualmente l'estensione e modificarne le impostazioni inserendo l'URL della pagina principale.", + "9a2ec6da48771128384887525bdcac992632c863": "per installare l'estensione ufficiale YoutubeDL-Material per Firefox direttamente dalla pagina delle estensioni di Firefox.", + "eb81be6b49e195e5307811d1d08a19259d411f37": "Istruzioni dettagliate per la configurazione.", + "cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Viene richiesto semplicemente di modificare le impostazioni dell'estensione, inserendo l'URL della pagina principale.", + "61b81b11aad0b9d970ece2fce18405f07eac69c2": "Trascina il link qui sotto tra tuoi preferiti e sei a posto! Vai al video YouTube che desideri scaricare e fai clic sul preferito.", + "c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Genera un preferito \"solo audio\"", + "d5f69691f9f05711633128b5a3db696783266b58": "Extra", + "5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Usa agente di download predefinito", + "ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Seleziona un metodo di download", + "0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Livello di Log", + "db6c192032f4cab809aad35215f0aa4765761897": "Scadenza accesso", + "dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Consenti download avanzato", + "431e5f3a0dde88768d1074baedd65266412b3f02": "Usa i cookie", + "80651a7ad1229ea6613557d3559f702cfa5aecf5": "Imposta i cookie", + "bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avanzate", + "37224420db54d4bc7696f157b779a7225f03ca9d": "Consenti registrazione utente", + "4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Interno", + "e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP", + "fa548cee6ea11c160a416cac3e6bdec0363883dc": "Metodo di autenticazione", + "1db9789b93069861019bd0ccaa5d4706b00afc61": "URL LDAP", + "f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Associa DN", + "080cc6abcba236390fc22e79792d0d3443a3bd2a": "Associa credenziali", + "cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Base di ricerca", + "e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Filtro di ricerca", + "4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Utenti", + "eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Registri", + "fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, seleziona, vero {Close} falso {Cancel} altro {otha}}", + "cec82c0a545f37420d55a9b6c45c20546e82f94e": "Informazioni su YoutubeDL-Material", + "199c17e5d6a419313af3c325f06dcbb9645ca618": "è un downloader di YouTube open source costruito secondo le specifiche Material Design di Google. Puoi scaricare agevolmente i tuoi video preferiti come file video o solo audio e persino iscriverti ai tuoi canali e playlist preferiti per tenerti aggiornato con i nuovi video pubblicati.", + "bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "ha alcune fantastiche funzionalità incluse! Una solida API, supporto Docker e supporto per la localizzazione (traduzione). Leggi tutte le funzionalità supportate cliccando sull'icona GitHub in alto.", + "a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versione installata:", + "e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Verifica aggiornamenti in corso...", + "a16e92385b4fd9677bb830a4b796b8b79c113290": "Aggiornamento disponibile", + "189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "È possibile eseguire l'aggiornamento dal menù impostazioni.", + "b33536f59b94ec935a16bd6869d836895dc5300c": "Hai trovato un errore o hai un suggerimento?", + "e1f398f38ff1534303d4bb80bd6cece245f24016": "per segnalare un problema!", + "42ff677ec14f111e88bd6cdd30145378e994d1bf": "Il tuo profilo", + "ac9d09de42edca1296371e4d801349c9096ac8de": "UID:", + "a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Creato:", + "fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Non hai eseguito l'accesso.", + "6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Accedi", + "bb694b49d408265c91c62799c2b3a7e3151c824d": "Esci", + "a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Crea un account amministratore", + "2d2adf3ca26a676bca2269295b7455a26fd26980": "Nessun account amministratore predefinito rilevato. Verrà creato e impostata la password per un account amministratore con il nome utente \"admin\".", + "70a67e04629f6d412db0a12d51820b480788d795": "Crea", + "994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profilo", + "004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Informazioni", + "92eee6be6de0b11c924e3ab27db30257159c0a7c": "Pagina principale", + "357064ca9d9ac859eb618e28e8126fa32be049e2": "Iscrizioni", + "822fab38216f64e8166d368b59fe756ca39d301b": "Download", + "a249a5ae13e0835383885aaf697d2890cc3e53e9": "Condividi playlist", + "15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Condividi il video", + "1d540dcd271b316545d070f9d182c372d923aadd": "Condividi l'audio", + "1f6d14a780a37a97899dc611881e6bc971268285": "Abilita la condivisione", + "6580b6a950d952df847cb3d8e7176720a740adc8": "Usa data e ora", + "4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Secondi", + "3a6e5a6aa78ca864f6542410c5dafb6334538106": "Copia negli appunti", + "5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Salva le modifiche", + "4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Il download è riuscito", + "348cc5d553b18e862eb1c1770e5636f6b05ba130": "Si è verificato un errore", + "4f8b2bb476981727ab34ed40fde1218361f92c45": "Dettagli", + "e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Si è verificato un errore:", + "77b0c73840665945b25bd128709aa64c8f017e1c": "Inizio download:", + "08ff9375ec078065bcdd7637b7ea65fce2979266": "Termine download:", + "ad127117f9471612f47d01eae09709da444a36a4": "Percorso(i) file:", + "a9806cf78ce00eb2613eeca11354a97e033377b8": "Iscriviti alla playlist o al canale", + "93efc99ae087fc116de708ecd3ace86ca237cf30": "URL della playlist o del canale", + "08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Nome personalizzato", + "ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Scarica tutti i file caricati", + "28a678e9cabf86e44c32594c43fa0e890135c20f": "Scarica i video caricati negli ultimi", + "c76a955642714b8949ff3e4b4990864a2e2cac95": "Modalità solo audio", + "408ca4911457e84a348cecf214f02c69289aa8f1": "Modalità solo streaming", + "f432e1a8d6adb12e612127978ce2e0ced933959c": "Questi vengono aggiunti dopo ai parametri standard.", + "98b6ec9ec138186d663e64770267b67334353d63": "File di uscita personalizzato", + "d0336848b0c375a1c25ba369b3481ee383217a4f": "Iscriviti", + "e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Tipo:", + "a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archivio:", + "8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Esporta archivio", + "3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Annulla l'iscrizione", + "e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "I tuoi abbonamenti", + "807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Canali", + "29b89f751593e1b347eef103891b7a1ff36ec03f": "Nome non disponibile. Recupero del canale in corso.", + "4636cd4a1379c50d471e98786098c4d39e1e82ad": "Non sei iscritto a nessun canale.", + "47546e45bbb476baaaad38244db444c427ddc502": "Scalette", + "2e0a410652cb07d069f576b61eab32586a18320d": "Nome non disponibile. Recupero playlist in corso.", + "587b57ced54965d8874c3fd0e9dfedb987e5df04": "Non sei iscritto a nessuna playlist.", + "3697f8583ea42868aa269489ad366103d94aece7": "Modifica", + "7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Cerca", + "2054791b822475aeaea95c0119113de3200f5e1c": "Durata:", + "94e01842dcee90531caa52e4147f70679bac87fe": "Elimina e scarica di nuovo", + "2031adb51e07a41844e8ba7704b054e98345c9c1": "Elimina definitivamente", + "91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Aggiornato da", + "1372e61c5bd06100844bd43b98b016aabc468f62": "Seleziona una versione:", + "cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrati", + "a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "ID sessione:", + "eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(attuale)", + "b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Cancella tutti i download", + "7117fc42f860e86d983bfccfcf2654e5750f3406": "Nessun download disponibile!", + "b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Registra un utente", + "024886ca34a6f309e3e51c2ed849320592c3faaa": "Nome utente", + "2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gestisci utente", + "29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "UID utente:", + "e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nuova password", + "6498fa1b8f563988f769654a75411bb8060134b9": "Imposta nuova password", + "544e09cdc99a8978f48521d45f62db0da6dcf742": "Usa il ruolo predefinito", + "4f20f2d5a6882190892e58b85f6ccbedfa737952": "Sì", + "3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "No", + "57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Gestisci ruolo", + "746f64ddd9001ac456327cd9a3d5152203a4b93c": "Nome utente", + "52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Ruolo", + "59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Azioni", + "632e8b20c98e8eec4059a605a4b011bb476137af": "Modifica utente", + "95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Elimina utente", + "4d92a0395dd66778a931460118626c5794a3fc7a": "Aggiungi utenti", + "b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Modifica ruolo", + "5009630cdf32ab4f1c78737b9617b8773512c05a": "Linee:", + "8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Cancella i registri", + "ccf5ea825526ac490974336cb5c24352886abc07": "Apri il file", + "5656a06f17c24b2d7eae9c221567b209743829a9": "Apri il file in una nuova scheda", + "a0720c36ee1057e5c54a86591b722485c62d7b1a": "Vai alle iscrizioni", + "d02888c485d3aeab6de628508f4a00312a722894": "I miei video", + "ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Parametri personalizzati generali", + "56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Nascondi.", + "84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Conosciuto anche come Client ID.", + "cfe829634b1144bc44b6d38cf5584ea65db9804f": "File output predefinito", + "3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Riscarica i nuovi contenuti", + "13759b09a7f4074ceee8fa2f968f9815fdf63295": "A volte i nuovi video vengono scaricati prima di essere completamente elaborati. Questa impostazione significa che per i nuovi video verrà effettuata una verifica il giorno successivo per la ricerca di versioni di qualità superiore.", + "ddc31f2885b1b33a7651963254b0c197f2a64086": "Vedi altro.", + "24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Generato automaticamente", + "c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Seleziona un agente di download", + "5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Scarica automaticamente le Chat Twitch", + "8ae23bc4302a479f687f4b20a84c276182e2519c": "Chiave dell'API Twitch", + "d162f9fcd6a7187b391e004f072ab3da8377c47d": "Usa l'API Twitch", + "04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorie", + "1148fd45287ff09955b938756bc302042bcb29c7": "Il percorso è riferito ai percorsi di download sopra. Non includere l'estensione.", + "dad95154dcef3509b8cc705046061fd24994bbb7": "visualizzazioni", + "792dc6a57f28a1066db283f2e736484f066005fd": "Scarica Chat Twitch", + "e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Aggiungi nuova regola", + "2489eefea00931942b91f4a1ae109514b591e2e1": "Regole", + "c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Categoria in modifica", + "07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "In pausa", + "73423607944a694ce6f9e55cfee329681bb4d9f9": "Nessun video trovato.", + "29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Ordine inverso", + "33026f57ea65cd9c8a5d917a08083f71a718933a": "Ordine normale", + "5caadefa4143cf6766a621b0f54f91f373a1f164": "Aggiungi contenuto", + "0cc1dec590ecd74bef71a865fb364779bc42a749": "Categoria:", + "303e45ffae995c9817e510e38cb969e6bb3adcbf": "(In pausa)", + "d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Qualità massima" +} \ No newline at end of file diff --git a/src/assets/i18n/messages.it.xlf b/src/assets/i18n/messages.it.xlf new file mode 100644 index 0000000..9f86229 --- /dev/null +++ b/src/assets/i18n/messages.it.xlf @@ -0,0 +1,2482 @@ + + + + + + Create a playlist + Crea una scaletta + + app/create-playlist/create-playlist.component.html + 1 + + Create a playlist dialog title + + + Name + Nome + + app/create-playlist/create-playlist.component.html + 6 + + + app/dialogs/modify-playlist/modify-playlist.component.html + 7 + + Playlist name placeholder + + + Audio + Audio + + app/create-playlist/create-playlist.component.html + 12 + + Audio + + + Video + Video + + app/create-playlist/create-playlist.component.html + 13 + + Video + + + Type + Tipo + + app/create-playlist/create-playlist.component.html + 11 + + Type select + + + Audio files + File audio + + app/create-playlist/create-playlist.component.html + 19 + + Audio files title + + + Videos + Video + + app/create-playlist/create-playlist.component.html + 20 + + + app/subscription/subscription/subscription.component.html + 28 + + Videos title + + + Modify youtube-dl args + Modifica i parametri di youtube-dl + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 1 + + Modify args title + + + Simulated new args + Simula i nuovi parametri + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 8 + + Simulated args title + + + Add an arg + Aggiungi un'impostazione + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 34 + + Add arg card title + + + Search by category + Cerca per categoria + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 60 + + Search args by category button + + + Use arg value + Usa valore impostato + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 64 + + Use arg value checkbox + + + Arg value + Valore impostato + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 68 + + Arg value placeholder + + + Add arg + Aggiungi impostazione + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 73 + + Search args by category button + + + Cancel + Annulla + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 84 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 72 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 56 + + + app/components/modify-users/modify-users.component.html + 61 + + Arg modifier cancel button + + + Modify + Modifica + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 85 + + Arg modifier modify button + + + Quality + Qualità + + app/main/main.component.html + 18 + + Quality select label + + + Use URL + Utilizza URL + + app/main/main.component.html + 46 + + YT search Use URL button for searched video + + + View + Visualizza + + app/main/main.component.html + 49 + + YT search View button for searched video + + + Only Audio + Solo audio + + app/main/main.component.html + 59 + + Only Audio checkbox + + + Multi-download Mode + Modalità download multiplo + + app/main/main.component.html + 64 + + Multi-download Mode checkbox + + + Download + Scarica + + app/main/main.component.html + 73 + + Main download button + + + Cancel + Annulla + + app/main/main.component.html + 78 + + Cancel download button + + + Advanced + Avanzato + + app/main/main.component.html + 90 + + Advanced download mode panel + + + Simulated command: + Comando simulato: + + app/main/main.component.html + 96 + + Simulated command label + + + Use custom args + Usa parametri personalizzati + + app/main/main.component.html + 104 + + Use custom args checkbox + + + Custom args + Parametri personalizzati + + app/main/main.component.html + 110 + + + app/settings/settings.component.html + 120 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 50 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 34 + + Custom args placeholder + + + No need to include URL, just everything after. Args are delimited using two commas like so: ,, + Non è necessario includere l'URL, solo ciò che viene dopo. I parametri sono delimitati da due vigole: ,, + + app/main/main.component.html + 112 + + Custom Args input hint + + + Use custom output + Usa output personalizzato + + app/main/main.component.html + 120 + + Use custom output checkbox + + + Custom output + Output personalizzata + + app/main/main.component.html + 125 + + Custom output placeholder + + + Documentation + Documentazione + + app/main/main.component.html + 127 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 62 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 46 + + Youtube-dl output template documentation link + + + Path is relative to the config download path. Don't include extension. + Il percorso è relativo al percorso di download configurato. Non includere l'estensione. + + app/main/main.component.html + 128 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 63 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 47 + + Custom Output input hint + + + Use authentication + Utilizza l'autenticazione + + app/main/main.component.html + 134 + + Use authentication checkbox + + + Username + Nome utente + + app/main/main.component.html + 139 + + YT Username placeholder + + + Password + Password + + app/main/main.component.html + 144 + + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 10 + + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 11 + + YT Password placeholder + + + Name: + Nome: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 5 + + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 6 + + Video name property + + + URL: + URL: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 9 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 9 + + Video URL property + + + Uploader: + Caricato da: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 13 + + Video ID property + + + File size: + Dimensioni file: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 17 + + Video file size property + + + Path: + Percorso: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 21 + + Video path property + + + Upload Date: + Data di caricamento: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 25 + + Video upload date property + + + Close + Chiudi + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 31 + + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 40 + + + app/dialogs/about-dialog/about-dialog.component.html + 59 + + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 27 + + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 30 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 23 + + + app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 17 + + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 18 + + + app/components/manage-user/manage-user.component.html + 30 + + + app/components/manage-role/manage-role.component.html + 18 + + Close subscription info button + + + Modify playlist + Modifica playlist + + app/dialogs/modify-playlist/modify-playlist.component.html + 1 + + Modify playlist dialog title + + + Add more content + Aggiungi più contenuto + + app/dialogs/modify-playlist/modify-playlist.component.html + 17 + + Add more content + + + Save + Salva + + app/dialogs/modify-playlist/modify-playlist.component.html + 27 + + + app/settings/settings.component.html + 359 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 58 + + + app/components/modify-users/modify-users.component.html + 58 + + Save + + + ID: + ID: + + app/file-card/file-card.component.html + 7 + + + app/download-item/download-item.component.html + 4 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 13 + + File or playlist ID + + + Count: + Conteggio: + + app/file-card/file-card.component.html + 8 + + Playlist video count + + + Edit + Modifica + + app/file-card/file-card.component.html + 19 + + + app/components/unified-file-card/unified-file-card.component.html + 32 + + Playlist edit button + + + Delete + Elimina + + app/file-card/file-card.component.html + 20 + + + app/file-card/file-card.component.html + 25 + + + app/components/unified-file-card/unified-file-card.component.html + 28 + + + app/components/unified-file-card/unified-file-card.component.html + 34 + + Delete playlist + + + Info + Informazioni + + app/file-card/file-card.component.html + 24 + + + app/subscription/subscription-file-card/subscription-file-card.component.html + 7 + + + app/components/unified-file-card/unified-file-card.component.html + 19 + + Video info button + + + Delete and blacklist + Elimina e aggiungi alla lista nera + + app/file-card/file-card.component.html + 26 + + + app/components/unified-file-card/unified-file-card.component.html + 29 + + Delete and blacklist video button + + + Upload new cookies + Carica nuovi cookie + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 1 + + Cookies uploader dialog title + + + Drag and Drop + Trascina e rilascia + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 11 + + Drag and Drop + + + NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user. + NOTA: il caricamento di nuovi cookie sovrascriverà i cookie precedenti. Inoltre tieni presente che i cookie sono a livello di processo, non per utente. + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 + + Cookies upload warning + + + Settings + Impostazioni + + app/settings/settings.component.html + 1 + + + app/app.component.html + 28 + + Settings title + + + URL + URL + + app/settings/settings.component.html + 18 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 8 + + URL input placeholder + + + URL this app will be accessed from, without the port. + URL con cui si accederà a questa applicazione, senza porta. + + app/settings/settings.component.html + 19 + + URL setting input hint + + + Port + Porta + + app/settings/settings.component.html + 24 + + Port input placeholder + + + The desired port. Default is 17442. + Porta personalizzata. La predefinita è 17442. + + app/settings/settings.component.html + 25 + + Port setting input hint + + + Multi-user mode + Modalità multiutente + + app/settings/settings.component.html + 34 + + Multi user mode setting + + + Users base path + Percorso profili utente + + app/settings/settings.component.html + 38 + + Users base path placeholder + + + Base path for users and their downloaded videos. + Percorso per profili utente e per video scaricati da ognuno. + + app/settings/settings.component.html + 39 + + Users base path hint + + + Allow subscriptions + Consenti iscrizioni + + app/settings/settings.component.html + 48 + + Allow subscriptions setting + + + Subscriptions base path + Percorso salvataggio playlist sottoscritte + + app/settings/settings.component.html + 52 + + Subscriptions base path input setting placeholder + + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + Percorso salvataggio per i video dei canali e delle playlist sottoscritte. È relativo alla cartella principale di YTDL-Material. + + app/settings/settings.component.html + 53 + + Subscriptions base path setting input hint + + + Check interval + Intervallo di verifica + + app/settings/settings.component.html + 58 + + Check interval input setting placeholder + + + Unit is seconds, only include numbers. + Unità in secondi, inserire solo numeri. + + app/settings/settings.component.html + 59 + + Check interval setting input hint + + + Theme + Tema + + app/settings/settings.component.html + 69 + + Theme select label + + + Default + Predefinito + + app/settings/settings.component.html + 71 + + Default theme label + + + Dark + Scuro + + app/settings/settings.component.html + 72 + + + app/app.component.html + 23 + + Dark theme label + + + Allow theme change + Consenti variazione tema + + app/settings/settings.component.html + 77 + + Allow theme change setting + + + Language + Lingua + + app/settings/settings.component.html + 86 + + Language select label + + + Main + Principale + + app/settings/settings.component.html + 12 + + Main settings label + + + Audio folder path + Percorso della cartella audio + + app/settings/settings.component.html + 106 + + Audio folder path input placeholder + + + Path for audio only downloads. It is relative to YTDL-Material's root folder. + Percorso per download solo audio. È relativo alla cartella principale di YTDL-Material. + + app/settings/settings.component.html + 107 + + Aduio path setting input hint + + + Video folder path + Percorso cartella Video + + app/settings/settings.component.html + 113 + + Video folder path input placeholder + + + Path for video downloads. It is relative to YTDL-Material's root folder. + Percorso per il download di video. È relativo alla cartella principale di YTDL-Material. + + app/settings/settings.component.html + 114 + + Video path setting input hint + + + Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, + Parametri personalizzati generali per i download sulla home page. I parametri sono delimitati da due virgole: ,, + + app/settings/settings.component.html + 121 + + Custom args setting input hint + + + Use youtube-dl archive + Usa l'archivio youtube-dl + + app/settings/settings.component.html + 127 + + Use youtubedl archive setting + + + Include thumbnail + Includi anteprima + + app/settings/settings.component.html + 131 + + Include thumbnail setting + + + Include metadata + Includi metadati + + app/settings/settings.component.html + 135 + + Include metadata setting + + + Kill all downloads + Interrompi tutti i download + + app/settings/settings.component.html + 139 + + Kill all downloads button + + + Downloader + Scaricato da + + app/settings/settings.component.html + 99 + + Downloader settings label + + + Top title + Titolo della barra superiore + + app/settings/settings.component.html + 152 + + Top title input placeholder + + + File manager enabled + Abilita il file manager + + app/settings/settings.component.html + 157 + + File manager enabled setting + + + Downloads manager enabled + Abilita il download manager + + app/settings/settings.component.html + 160 + + Downloads manager enabled setting + + + Allow quality select + Consenti la selezione della qualità + + app/settings/settings.component.html + 163 + + Allow quality seelct setting + + + Download only mode + Modalità solo download + + app/settings/settings.component.html + 166 + + Download only mode setting + + + Allow multi-download mode + Consenti la modalità di download multiplo + + app/settings/settings.component.html + 169 + + Allow multi-download mode setting + + + Enable Public API + Abilita l'API Pubblica + + app/settings/settings.component.html + 177 + + Enable Public API key setting + + + Public API Key + Chiave API Pubblica + + app/settings/settings.component.html + 182 + + Public API Key setting placeholder + + + View documentation + Visualizza la documentazione + + app/settings/settings.component.html + 183 + + View API docs setting hint + + + Generate + Genera + + app/settings/settings.component.html + 187 + + Generate key button + + + This will delete your old API key! + Cancellerai la tua chiave API precedente! + + app/settings/settings.component.html + 187 + + delete api key tooltip + + + Use YouTube API + Usa l'API di YouTube + + app/settings/settings.component.html + 196 + + Use YouTube API setting + + + Youtube API Key + Chiave API YouTube + + app/settings/settings.component.html + 200 + + Youtube API Key setting placeholder + + + Generating a key is easy! + Generare una chiave è facile! + + app/settings/settings.component.html + 201 + + Youtube API Key setting hint + + + Click here + Premi qui + + app/settings/settings.component.html + 211 + + + app/settings/settings.component.html + 217 + + + app/dialogs/about-dialog/about-dialog.component.html + 25 + + Chrome ext click here + + + to download the official YoutubeDL-Material Chrome extension manually. + per scaricare manualmente l'estensione ufficiale per Chrome di YoutubeDL-Material. + + app/settings/settings.component.html + 211 + + Chrome click here suffix + + + You must manually load the extension and modify the extension's settings to set the frontend URL. + È necessario installare manualmente l'estensione e modificarne le impostazioni inserendo l'URL della pagina principale. + + app/settings/settings.component.html + 212 + + Chrome setup suffix + + + to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. + per installare l'estensione ufficiale YoutubeDL-Material per Firefox direttamente dalla pagina delle estensioni di Firefox. + + app/settings/settings.component.html + 217 + + Firefox click here suffix + + + Detailed setup instructions. + Istruzioni dettagliate per la configurazione. + + app/settings/settings.component.html + 218 + + Firefox setup prefix link + + + Not much is required other than changing the extension's settings to set the frontend URL. + Viene richiesto semplicemente di modificare le impostazioni dell'estensione, inserendo l'URL della pagina principale. + + app/settings/settings.component.html + 218 + + Firefox setup suffix + + + Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. + Trascina il link qui sotto tra tuoi preferiti e sei a posto! Vai al video YouTube che desideri scaricare e fai clic sul preferito. + + app/settings/settings.component.html + 223 + + Bookmarklet instructions + + + Generate 'audio only' bookmarklet + Genera un preferito "solo audio" + + app/settings/settings.component.html + 224 + + Generate audio only bookmarklet checkbox + + + Extra + Extra + + app/settings/settings.component.html + 146 + + Extra settings label + + + Use default downloading agent + Usa agente di download predefinito + + app/settings/settings.component.html + 238 + + Use default downloading agent setting + + + Select a downloader + Seleziona un metodo di download + + app/settings/settings.component.html + 242 + + Custom downloader select label + + + Log Level + Livello di Log + + app/settings/settings.component.html + 256 + + Log Level label + + + Login expiration + Scadenza accesso + + app/settings/settings.component.html + 268 + + Login expiration select label + + + Allow advanced download + Consenti download avanzato + + app/settings/settings.component.html + 279 + + Allow advanced downloading setting + + + Use Cookies + Usa i cookie + + app/settings/settings.component.html + 287 + + Use cookies setting + + + Set Cookies + Imposta i cookie + + app/settings/settings.component.html + 288 + + Set cookies button + + + Advanced + Avanzate + + app/settings/settings.component.html + 233 + + Host settings label + + + Allow user registration + Consenti registrazione utente + + app/settings/settings.component.html + 302 + + Allow registration setting + + + Internal + Interno + + app/settings/settings.component.html + 308 + + Internal auth method + + + LDAP + LDAP + + app/settings/settings.component.html + 311 + + LDAP auth method + + + Auth method + Metodo di autenticazione + + app/settings/settings.component.html + 306 + + Auth method select + + + LDAP URL + URL LDAP + + app/settings/settings.component.html + 318 + + LDAP URL + + + Bind DN + Associa DN + + app/settings/settings.component.html + 323 + + Bind DN + + + Bind Credentials + Associa credenziali + + app/settings/settings.component.html + 328 + + Bind Credentials + + + Search Base + Base di ricerca + + app/settings/settings.component.html + 333 + + Search Base + + + Search Filter + Filtro di ricerca + + app/settings/settings.component.html + 338 + + Search Filter + + + Users + Utenti + + app/settings/settings.component.html + 298 + + Users settings label + + + Logs + Registri + + app/settings/settings.component.html + 346 + + Logs settings label + + + {VAR_SELECT, select, true {Close} false {Cancel} other {otha}} + {VAR_SELECT, seleziona, vero {Close} falso {Cancel} altro {otha}} + + app/settings/settings.component.html + 362 + + Settings cancel and close button + + + About YoutubeDL-Material + Informazioni su YoutubeDL-Material + + app/dialogs/about-dialog/about-dialog.component.html + 1 + + About dialog title + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + è un downloader di YouTube open source costruito secondo le specifiche Material Design di Google. Puoi scaricare agevolmente i tuoi video preferiti come file video o solo audio e persino iscriverti ai tuoi canali e playlist preferiti per tenerti aggiornato con i nuovi video pubblicati. + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About first paragraph + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. + ha alcune fantastiche funzionalità incluse! Una solida API, supporto Docker e supporto per la localizzazione (traduzione). Leggi tutte le funzionalità supportate cliccando sull'icona GitHub in alto. + + app/dialogs/about-dialog/about-dialog.component.html + 15 + + About second paragraph + + + Installed version: + Versione installata: + + app/dialogs/about-dialog/about-dialog.component.html + 20 + + Version label + + + Checking for updates... + Verifica aggiornamenti in corso... + + app/dialogs/about-dialog/about-dialog.component.html + 20 + + Checking for updates text + + + Update available + Aggiornamento disponibile + + app/dialogs/about-dialog/about-dialog.component.html + 21 + + View latest update + + + You can update from the settings menu. + È possibile eseguire l'aggiornamento dal menù impostazioni. + + app/dialogs/about-dialog/about-dialog.component.html + 21 + + Update through settings menu hint + + + Found a bug or have a suggestion? + Hai trovato un errore o hai un suggerimento? + + app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug prefix + + + to create an issue! + per segnalare un problema! + + app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug suffix + + + Your Profile + Il tuo profilo + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 1 + + User profile dialog title + + + UID: + UID: + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 9 + + UID + + + Created: + Creato: + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 12 + + Created + + + You are not logged in. + Non hai eseguito l'accesso. + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 19 + + Not logged in notification + + + Login + Accedi + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 20 + + + app/app.component.html + 44 + + + app/components/login/login.component.html + 15 + + Login + + + Logout + Esci + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 28 + + Logout + + + Create admin account + Crea un account amministratore + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 1 + + Create admin account dialog title + + + No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. + Nessun account amministratore predefinito rilevato. Verrà creato e impostata la password per un account amministratore con il nome utente "admin". + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 5 + + No default admin detected explanation + + + Create + Crea + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 17 + + Create + + + Profile + Profilo + + app/app.component.html + 19 + + Profile menu label + + + About + Informazioni + + app/app.component.html + 32 + + About menu label + + + Home + Pagina principale + + app/app.component.html + 43 + + Navigation menu Home Page title + + + Subscriptions + Iscrizioni + + app/app.component.html + 45 + + Navigation menu Subscriptions Page title + + + Downloads + Download + + app/app.component.html + 46 + + Navigation menu Downloads Page title + + + Share playlist + Condividi playlist + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 2 + + Share playlist dialog title + + + Share video + Condividi il video + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 3 + + Share video dialog title + + + Share audio + Condividi l'audio + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 4 + + Share audio dialog title + + + Enable sharing + Abilita la condivisione + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 10 + + Enable sharing checkbox + + + Use timestamp + Usa data e ora + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 13 + + Use timestamp + + + Seconds + Secondi + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 15 + + Seconds + + + Copy to clipboard + Copia negli appunti + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 24 + + Copy to clipboard button + + + Save changes + Salva le modifiche + + app/player/player.component.html + 22 + + Playlist save changes button + + + The download was successful + Il download è riuscito + + app/download-item/download-item.component.html + 8 + + download successful tooltip + + + An error has occurred + Si è verificato un errore + + app/download-item/download-item.component.html + 9 + + download error tooltip + + + Details + Dettagli + + app/download-item/download-item.component.html + 18 + + Details + + + An error has occurred: + Si è verificato un errore: + + app/download-item/download-item.component.html + 27 + + Error label + + + Download start: + Inizio download: + + app/download-item/download-item.component.html + 32 + + Download start label + + + Download end: + Termine download: + + app/download-item/download-item.component.html + 35 + + Download end label + + + File path(s): + Percorso(i) file: + + app/download-item/download-item.component.html + 38 + + File path(s) label + + + Subscribe to playlist or channel + Iscriviti alla playlist o al canale + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 1 + + Subscribe dialog title + + + The playlist or channel URL + URL della playlist o del canale + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 9 + + Subscription URL input hint + + + Custom name + Nome personalizzato + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 19 + + Subscription custom name placeholder + + + Download all uploads + Scarica tutti i file caricati + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 23 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 7 + + Download all uploads subscription setting + + + Download videos uploaded in the last + Scarica i video caricati negli ultimi + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 26 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 10 + + Download time range prefix + + + Audio-only mode + Modalità solo audio + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 24 + + Streaming-only mode + + + Streaming-only mode + Modalità solo streaming + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 45 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 29 + + Streaming-only mode + + + These are added after the standard args. + Questi vengono aggiunti dopo ai parametri standard. + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 53 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 37 + + Custom args hint + + + Custom file output + File di uscita personalizzato + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 59 + + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 43 + + Subscription custom file output placeholder + + + Subscribe + Iscriviti + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 74 + + Subscribe button + + + Type: + Tipo: + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 5 + + Subscription type property + + + Archive: + Archivio: + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 17 + + Subscription ID property + + + Export Archive + Esporta archivio + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 24 + + Export Archive button + + + Unsubscribe + Annulla l'iscrizione + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 26 + + Unsubscribe button + + + Your subscriptions + I tuoi abbonamenti + + app/subscriptions/subscriptions.component.html + 3 + + Subscriptions title + + + Channels + Canali + + app/subscriptions/subscriptions.component.html + 8 + + Subscriptions channels title + + + Name not available. Channel retrieval in progress. + Nome non disponibile. Recupero del canale in corso. + + app/subscriptions/subscriptions.component.html + 14 + + Subscription playlist not available text + + + You have no channel subscriptions. + Non sei iscritto a nessun canale. + + app/subscriptions/subscriptions.component.html + 24 + + No channel subscriptions text + + + Playlists + Scalette + + app/subscriptions/subscriptions.component.html + 27 + + Subscriptions playlists title + + + Name not available. Playlist retrieval in progress. + Nome non disponibile. Recupero playlist in corso. + + app/subscriptions/subscriptions.component.html + 33 + + Subscription playlist not available text + + + You have no playlist subscriptions. + Non sei iscritto a nessuna playlist. + + app/subscriptions/subscriptions.component.html + 43 + + No playlist subscriptions text + + + Editing + Modifica + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + Edit subscription dialog title prefix + + + Search + Cerca + + app/subscription/subscription/subscription.component.html + 32 + + + app/components/modify-users/modify-users.component.html + 7 + + + app/components/recent-videos/recent-videos.component.html + 24 + + Subscription videos search placeholder + + + Length: + Durata: + + app/subscription/subscription-file-card/subscription-file-card.component.html + 3 + + Video duration label + + + Delete and redownload + Elimina e scarica di nuovo + + app/subscription/subscription-file-card/subscription-file-card.component.html + 8 + + + app/components/unified-file-card/unified-file-card.component.html + 23 + + Delete and redownload subscription video button + + + Delete forever + Elimina definitivamente + + app/subscription/subscription-file-card/subscription-file-card.component.html + 9 + + + app/components/unified-file-card/unified-file-card.component.html + 26 + + Delete forever subscription video button + + + Updater + Aggiornato da + + app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 1 + + Update progress dialog title + + + Select a version: + Seleziona una versione: + + app/updater/updater.component.html + 3 + + Select a version + + + Register + Registrati + + app/components/login/login.component.html + 35 + + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 17 + + Register + + + Session ID: + ID sessione: + + app/components/downloads/downloads.component.html + 5 + + Session ID + + + (current) + (attuale) + + app/components/downloads/downloads.component.html + 6 + + Current session + + + Clear all downloads + Cancella tutti i download + + app/components/downloads/downloads.component.html + 18 + + clear all downloads action button + + + No downloads available! + Nessun download disponibile! + + app/components/downloads/downloads.component.html + 25 + + No downloads label + + + Register a user + Registra un utente + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 1 + + Register user dialog title + + + User name + Nome utente + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 6 + + User name placeholder + + + Manage user + Gestisci utente + + app/components/manage-user/manage-user.component.html + 1 + + + app/components/modify-users/modify-users.component.html + 70 + + Manage user dialog title + + + User UID: + UID utente: + + app/components/manage-user/manage-user.component.html + 4 + + User UID + + + New password + Nuova password + + app/components/manage-user/manage-user.component.html + 8 + + New password placeholder + + + Set new password + Imposta nuova password + + app/components/manage-user/manage-user.component.html + 10 + + Set new password + + + Use role default + Usa il ruolo predefinito + + app/components/manage-user/manage-user.component.html + 19 + + Use role default + + + Yes + + + app/components/manage-user/manage-user.component.html + 20 + + + app/components/manage-role/manage-role.component.html + 9 + + Yes + + + No + No + + app/components/manage-user/manage-user.component.html + 21 + + + app/components/manage-role/manage-role.component.html + 10 + + No + + + Manage role + Gestisci ruolo + + app/components/manage-role/manage-role.component.html + 1 + + Manage role dialog title + + + User name + Nome utente + + app/components/modify-users/modify-users.component.html + 17 + + Username users table header + + + Role + Ruolo + + app/components/modify-users/modify-users.component.html + 35 + + Role users table header + + + Actions + Azioni + + app/components/modify-users/modify-users.component.html + 55 + + Actions users table header + + + Edit user + Modifica utente + + app/components/modify-users/modify-users.component.html + 66 + + edit user action button tooltip + + + Delete user + Elimina utente + + app/components/modify-users/modify-users.component.html + 73 + + delete user action button tooltip + + + Add Users + Aggiungi utenti + + app/components/modify-users/modify-users.component.html + 90 + + Add users button + + + Edit Role + Modifica ruolo + + app/components/modify-users/modify-users.component.html + 95 + + Edit role + + + Lines: + Linee: + + app/components/logs-viewer/logs-viewer.component.html + 22 + + Label for lines select in logger view + + + Clear logs + Cancella i registri + + app/components/logs-viewer/logs-viewer.component.html + 34 + + Clear logs button + + + Open file + Apri il file + + app/components/unified-file-card/unified-file-card.component.html + 13 + + Open file button + + + Open file in new tab + Apri il file in una nuova scheda + + app/components/unified-file-card/unified-file-card.component.html + 14 + + Open file in new tab + + + Go to subscription + Vai alle iscrizioni + + app/components/unified-file-card/unified-file-card.component.html + 20 + + Go to subscription menu item + + + My videos + I miei video + + app/components/recent-videos/recent-videos.component.html + 20 + + My videos title + + + Global custom args + Parametri personalizzati generali + + src/app/settings/settings.component.html + 133 + + Custom args input placeholder + + + See less. + Nascondi. + + src/app/components/see-more/see-more.component.html + 8,9 + + See less + + + Also known as a Client ID. + Conosciuto anche come Client ID. + + src/app/settings/settings.component.html + 249 + + Twitch API Key setting hint AKA preamble + + + Default file output + File output predefinito + + src/app/settings/settings.component.html + 123 + + Default file output placeholder + + + Redownload fresh uploads + Riscarica i nuovi contenuti + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads + + + Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day. + A volte i nuovi video vengono scaricati prima di essere completamente elaborati. Questa impostazione significa che per i nuovi video verrà effettuata una verifica il giorno successivo per la ricerca di versioni di qualità superiore. + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads tooltip + + + See more. + Vedi altro. + + src/app/components/see-more/see-more.component.html + 5,6 + + See more + + + Auto-generated + Generato automaticamente + + src/app/components/unified-file-card/unified-file-card.component.html + 5 + + Auto-generated label + + + Select a download agent + Seleziona un agente di download + + src/app/settings/settings.component.html + 299 + + Custom downloader select label + + + Auto-download Twitch Chat + Scarica automaticamente le Chat Twitch + + src/app/settings/settings.component.html + 244 + + Auto download Twitch Chat setting + + + Twitch API Key + Chiave dell'API Twitch + + src/app/settings/settings.component.html + 248 + + Twitch API Key setting placeholder + + + Use Twitch API + Usa l'API Twitch + + src/app/settings/settings.component.html + 241 + + Use Twitch API setting + + + Categories + Categorie + + src/app/settings/settings.component.html + 144 + + Categories + + + Path is relative to the above download paths. Don't include extension. + Il percorso è riferito ai percorsi di download sopra. Non includere l'estensione. + + src/app/settings/settings.component.html + 126 + + Custom Output input hint + + + views + visualizzazioni + + src/app/player/player.component.html + 15 + + View count label + + + Download Twitch Chat + Scarica Chat Twitch + + src/app/components/twitch-chat/twitch-chat.component.html + 10 + + Download Twitch Chat button + + + Add new rule + Aggiungi nuova regola + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 39 + + Add new rule tooltip + + + Rules + Regole + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 10 + + Rules + + + Editing category + Categoria in modifica + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 1 + + Editing category dialog title + + + Paused + In pausa + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 7 + + Paused subscription setting + + + No videos found. + Nessun video trovato. + + src/app/components/recent-videos/recent-videos.component.html + 38 + + No videos found + + + Reverse order + Ordine inverso + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 14 + + Reverse order + + + Normal order + Ordine normale + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 13 + + Normal order + + + Add content + Aggiungi contenuto + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 19 + + Add content + + + Category: + Categoria: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 29 + + Category property + + + (Paused) + (In pausa) + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 1 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + + src/app/subscriptions/subscriptions.component.html + 12 + + + src/app/subscriptions/subscriptions.component.html + 31 + + + src/app/subscription/subscription/subscription.component.html + 5 + + Paused suffix + + + Max quality + Qualità massima + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 32 + + Max quality placeholder + + + + diff --git a/src/assets/i18n/messages.zh.json b/src/assets/i18n/messages.zh.json index ab8be32..683f6b9 100644 --- a/src/assets/i18n/messages.zh.json +++ b/src/assets/i18n/messages.zh.json @@ -113,7 +113,7 @@ "61b81b11aad0b9d970ece2fce18405f07eac69c2": "只需将下面的链接拖放到书签栏中。在YouTube页面上您只需单击书签即可下载视频。", "c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "生成“仅音频”书签", "d5f69691f9f05711633128b5a3db696783266b58": "额外", - "5fab47f146b0a4b809dcebf3db9da94df6299ea1": "使用默认下载代理", + "5fab47f146b0a4b809dcebf3db9da94df6299ea1": "使用默认下载程序", "ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "选择下载器", "00e274c496b094a019f0679c3fab3945793f3335": "选择日志级别", "dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "开启高级下载选项", @@ -124,7 +124,7 @@ "4d13a9cd5ed3dcee0eab22cb25198d43886942be": "用户", "eb3d5aefff38a814b76da74371cbf02c0789a1ef": "日志", "52c9a103b812f258bcddc3d90a6e3f46871d25fe": "保存", - "fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {关} false {取消} }", + "fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {关} false {取消} other {其他} }", "cec82c0a545f37420d55a9b6c45c20546e82f94e": "关于 YoutubeDL-Material", "199c17e5d6a419313af3c325f06dcbb9645ca618": "是根据Google的Material Design规范构建的开源YouTube下载器。您可以将喜欢的视频下载为视频或音频文件,并且可以订阅喜欢的频道和播放列表,以便及时下载他们的新视频。", "bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "包含很多很棒的功能!支持API,Docker和本地化。在Github上查找所有受支持的功能。", @@ -238,5 +238,32 @@ "cfa67d14d84fe0e9fadf251dc51ffc181173b662": "搜索起点", "544e09cdc99a8978f48521d45f62db0da6dcf742": "使用角色预设", "3697f8583ea42868aa269489ad366103d94aece7": "编辑中", - "fb35145bfb84521e21b6385363d59221f436a573": "取消所有下载" + "fb35145bfb84521e21b6385363d59221f436a573": "取消所有下载", + "56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "查看更少", + "c776eb4992b6c98f58cd89b20c1ea8ac37888521": "选择一个下载程序", + "d641b8fa5ac5e85114c733b1f7de6976bd091f70": "最高画质", + "ddc31f2885b1b33a7651963254b0c197f2a64086": "查看更多...", + "5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "自动下载Twitch弹幕", + "84ffcebac2709ca0785f4a1d5ba274433b5beabc": "也称为客户ID", + "8ae23bc4302a479f687f4b20a84c276182e2519c": "Twitch API 密钥", + "d162f9fcd6a7187b391e004f072ab3da8377c47d": "使用Twitch API", + "04201f9d27abd7d6f58a4328ab98063ce1072006": "分类", + "ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "全局自定义变量", + "1148fd45287ff09955b938756bc302042bcb29c7": "路径相对于上述下载路径,不包括扩展名。", + "cfe829634b1144bc44b6d38cf5584ea65db9804f": "默认输出文件夹", + "3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "重新下载新上传的内容", + "13759b09a7f4074ceee8fa2f968f9815fdf63295": "有时新视频会在完全处理前下载。这项设置指新视频会在第二天检查视频是否有更高画质。", + "dad95154dcef3509b8cc705046061fd24994bbb7": "浏览", + "792dc6a57f28a1066db283f2e736484f066005fd": "下载Twitch弹幕", + "e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "添加新规则", + "2489eefea00931942b91f4a1ae109514b591e2e1": "规则", + "c3b0b86523f1d10e84a71f9b188d54913a11af3b": "编辑类别", + "07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "暂停", + "73423607944a694ce6f9e55cfee329681bb4d9f9": "找不到视频", + "29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "倒序", + "33026f57ea65cd9c8a5d917a08083f71a718933a": "正序", + "5caadefa4143cf6766a621b0f54f91f373a1f164": "添加内容", + "0cc1dec590ecd74bef71a865fb364779bc42a749": "类别:", + "303e45ffae995c9817e510e38cb969e6bb3adcbf": "(暂停)", + "24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "自动生成的" } \ No newline at end of file diff --git a/src/assets/i18n/messages.zh.xlf b/src/assets/i18n/messages.zh.xlf new file mode 100644 index 0000000..a0b6008 --- /dev/null +++ b/src/assets/i18n/messages.zh.xlf @@ -0,0 +1,2578 @@ + + + + + + Create a playlist + 创建播放列表 + + app/create-playlist/create-playlist.component.html + 1 + + Create a playlist dialog title + + + Name + 名称 + + app/create-playlist/create-playlist.component.html + 5 + + + app/dialogs/modify-playlist/modify-playlist.component.html + 7 + + Playlist name placeholder + + + Audio files + 音频文件 + + app/create-playlist/create-playlist.component.html + 10 + + Audio files title + + + Videos + 视频文件 + + app/create-playlist/create-playlist.component.html + 11 + + + app/subscription/subscription/subscription.component.html + 28 + + Videos title + + + Modify youtube-dl args + 修改youtube-dl参数 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 1 + + Modify args title + + + Simulated new args + 模拟新参数 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 8 + + Simulated args title + + + Add an arg + 添加参数 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 34 + + Add arg card title + + + Search by category + 按类别搜索 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 60 + + Search args by category button + + + Use arg value + 使用参数值 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 64 + + Use arg value checkbox + + + Arg value + 参数值 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 68 + + Arg value placeholder + + + Add arg + 添加参数 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 73 + + Search args by category button + + + Cancel + 取消 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 84 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 70 + + Arg modifier cancel button + + + Modify + 修改 + + app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 85 + + Arg modifier modify button + + + Youtube Downloader + Youtube下载器 + + app/main/main.component.html + 5 + + Youtube downloader home page label + + + Quality + 质量 + + app/main/main.component.html + 21 + + Quality select label + + + Use URL + 使用URL + + app/main/main.component.html + 49 + + YT search Use URL button for searched video + + + View + 查看 + + app/main/main.component.html + 52 + + YT search View button for searched video + + + Only Audio + 仅音频 + + app/main/main.component.html + 62 + + Only Audio checkbox + + + Multi-download Mode + 多下载模式 + + app/main/main.component.html + 67 + + Multi-download Mode checkbox + + + Download + 下载 + + app/main/main.component.html + 76 + + Main download button + + + Cancel + 取消 + + app/main/main.component.html + 81 + + Cancel download button + + + Advanced + 高级 + + app/main/main.component.html + 93 + + Advanced download mode panel + + + Simulated command: + 模拟命令: + + app/main/main.component.html + 99 + + Simulated command label + + + Use custom args + 使用自定义参数 + + app/main/main.component.html + 107 + + Use custom args checkbox + + + Custom args + 自定义参数 + + app/main/main.component.html + 113 + + + app/settings/settings.component.html + 145 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 48 + + Custom args placeholder + + + No need to include URL, just everything after. Args are delimited using two commas like so: ,, + 不必指定URL,仅需指定其后的部分。参数用两个逗号分隔:,, + + app/main/main.component.html + 115 + + Custom Args input hint + + + Use custom output + 使用自定义输出 + + app/main/main.component.html + 123 + + Use custom output checkbox + + + Custom output + 自定义输出 + + app/main/main.component.html + 128 + + Custom output placeholder + + + Documentation + 文档 + + app/main/main.component.html + 130 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 60 + + Youtube-dl output template documentation link + + + Path is relative to the config download path. Don't include extension. + 该路径是相对于配置下载路径的,省略文件扩展名 + + app/main/main.component.html + 131 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 61 + + Custom Output input hint + + + Use authentication + 使用身份验证 + + app/main/main.component.html + 137 + + Use authentication checkbox + + + Username + 用户名 + + app/main/main.component.html + 142 + + YT Username placeholder + + + Password + 密码 + + app/main/main.component.html + 147 + + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 10 + + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 11 + + YT Password placeholder + + + Audio + 音频 + + app/main/main.component.html + 191 + + Audio files title + + + Your audio files are here + 您的音频文件在这里 + + app/main/main.component.html + 196 + + Audio files description + + + Playlists + 播放列表 + + app/main/main.component.html + 211 + + + app/main/main.component.html + 253 + + + app/subscriptions/subscriptions.component.html + 27 + + Playlists title + + + No playlists available. Create one from your downloading audio files by clicking the blue plus button. + 没有可用的播放列表。 通过单击蓝色加号按钮从您下载的音频文件创建一个。 + + app/main/main.component.html + 222 + + No video playlists available text + + + Video + 视频 + + app/main/main.component.html + 232 + + Video files title + + + Your video files are here + 您的视频文件在这里 + + app/main/main.component.html + 237 + + Video files description + + + No playlists available. Create one from your downloading video files by clicking the blue plus button. + 没有可用的播放列表。 通过单击蓝色加号按钮,从下载的视频文件中创建一个。 + + app/main/main.component.html + 266 + + No video playlists available text + + + Name: + 名称: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 5 + + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 6 + + Video name property + + + URL: + URL: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 9 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 9 + + Video URL property + + + Uploader: + 上传者: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 13 + + Video ID property + + + File size: + 文件大小: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 17 + + Video file size property + + + Path: + 路径: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 21 + + Video path property + + + Upload Date: + 上传日期: + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 25 + + Video upload date property + + + Close + 关闭 + + app/dialogs/video-info-dialog/video-info-dialog.component.html + 31 + + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 40 + + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 27 + + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 30 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 23 + + + app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 17 + + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 18 + + + app/components/manage-user/manage-user.component.html + 30 + + + app/components/manage-role/manage-role.component.html + 18 + + Close subscription info button + + + Modify playlist + 修改播放列表 + + app/dialogs/modify-playlist/modify-playlist.component.html + 1 + + Modify playlist dialog title + + + ID: + ID: + + app/file-card/file-card.component.html + 7 + + + app/download-item/download-item.component.html + 4 + + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 13 + + File or playlist ID + + + Count: + 数量: + + app/file-card/file-card.component.html + 8 + + Playlist video count + + + Edit + 编辑 + + app/file-card/file-card.component.html + 19 + + Playlist edit button + + + Delete + 删除 + + app/file-card/file-card.component.html + 20 + + + app/file-card/file-card.component.html + 25 + + Delete playlist + + + Info + 详情 + + app/file-card/file-card.component.html + 24 + + + app/subscription/subscription-file-card/subscription-file-card.component.html + 7 + + Video info button + + + Delete and blacklist + 删除并拉黑 + + app/file-card/file-card.component.html + 26 + + Delete and blacklist video button + + + Upload new cookies + 上传新Cookies + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 1 + + Cookies uploader dialog title + + + Drag and Drop + 拖放 + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 11 + + Drag and Drop + + + NOTE: Uploading new cookies will overrride your previous cookies. Also note that cookies are instance-wide, not per-user. + 注意:加载新的Cookies将覆盖您以前的Cookie。并且Cookies的范围是整个实例,而不是每个用户单独分开的。 + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 + + Cookies upload warning + + + Settings + 设置 + + app/settings/settings.component.html + 1 + + + app/app.component.html + 28 + + Settings title + + + URL + URL + + app/settings/settings.component.html + 18 + + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 8 + + URL input placeholder + + + URL this app will be accessed from, without the port. + 设置访问URL,无需端口。 + + app/settings/settings.component.html + 19 + + URL setting input hint + + + Port + 端口 + + app/settings/settings.component.html + 24 + + Port input placeholder + + + The desired port. Default is 17442. + 设置目标端口。默认为17442。 + + app/settings/settings.component.html + 25 + + Port setting input hint + + + Multi-user mode + 多用户模式 + + app/settings/settings.component.html + 34 + + Multi user mode setting + + + Users base path + 用户文件路径 + + app/settings/settings.component.html + 38 + + Users base path placeholder + + + Base path for users and their downloaded videos. + 用户及其下载视频的文件路径。 + + app/settings/settings.component.html + 39 + + Users base path hint + + + Use encryption + 使用加密(SSL) + + app/settings/settings.component.html + 48 + + Use encryption setting + + + Cert file path + 证书文件路径 + + app/settings/settings.component.html + 53 + + Cert file path input placeholder + + + Key file path + 密钥文件路径 + + app/settings/settings.component.html + 59 + + Key file path input placeholder + + + Allow subscriptions + 允许订阅 + + app/settings/settings.component.html + 68 + + Allow subscriptions setting + + + Subscriptions base path + 订阅文件路径 + + app/settings/settings.component.html + 72 + + Subscriptions base path input setting placeholder + + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + 订阅频道和播放列表中视频的文件路径(相对于根文件夹而言)。 + + app/settings/settings.component.html + 73 + + Subscriptions base path setting input hint + + + Check interval + 检查间隔 + + app/settings/settings.component.html + 78 + + Check interval input setting placeholder + + + Unit is seconds, only include numbers. + 单位是秒,只包含数字。 + + app/settings/settings.component.html + 79 + + Check interval setting input hint + + + Use youtube-dl archive + 使用youtube-dl存档 + + app/settings/settings.component.html + 83 + + + app/settings/settings.component.html + 152 + + Use youtube-dl archive setting + + + With youtube-dl's archive + 根据youtube-dl的存档功能 + + app/settings/settings.component.html + 84 + + youtube-dl archive explanation prefix link + + + feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory. + 从您的订阅下载的视频会记录在订阅存档子目录中的文本文件中。 + + app/settings/settings.component.html + 84 + + youtube-dl archive explanation middle + + + This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss. + 这样一来,您无需取消订阅便可以从订阅中永久删除视频。并且它还可以在数据丢失的情况下记录已经下载了哪些视频。 + + app/settings/settings.component.html + 85 + + youtube-dl archive explanation suffix + + + Theme + 主题 + + app/settings/settings.component.html + 94 + + Theme select label + + + Default + 默认 + + app/settings/settings.component.html + 96 + + Default theme label + + + Dark + 暗黑 + + app/settings/settings.component.html + 97 + + + app/app.component.html + 23 + + Dark theme label + + + Allow theme change + 允许更改主题 + + app/settings/settings.component.html + 102 + + Allow theme change setting + + + Language + 语言 + + app/settings/settings.component.html + 111 + + Language select label + + + Main + 常规 + + app/settings/settings.component.html + 12 + + Main settings label + + + Audio folder path + 音频文件夹路径 + + app/settings/settings.component.html + 131 + + Audio folder path input placeholder + + + Path for audio only downloads. It is relative to YTDL-Material's root folder. + 音频下载的文件路径。相对于YTDL-Material的根文件夹。 + + app/settings/settings.component.html + 132 + + Aduio path setting input hint + + + Video folder path + 视频文件夹路径 + + app/settings/settings.component.html + 138 + + Video folder path input placeholder + + + Path for video downloads. It is relative to YTDL-Material's root folder. + 视频下载的文件路径。相对于YTDL-Material的根文件夹。 + + app/settings/settings.component.html + 139 + + Video path setting input hint + + + Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, + 开始页面上用于下载的全局自定义参数。参数由两个逗号分隔:,, + + app/settings/settings.component.html + 146 + + Custom args setting input hint + + + Safe download override + 安全下载覆盖 + + app/settings/settings.component.html + 157 + + Safe download override setting + + + Downloader + 下载程序 + + app/settings/settings.component.html + 124 + + Downloader settings label + + + Top title + 首页标题 + + app/settings/settings.component.html + 170 + + Top title input placeholder + + + File manager enabled + 启用文件管理 + + app/settings/settings.component.html + 175 + + File manager enabled setting + + + Downloads manager enabled + 启用下载管理 + + app/settings/settings.component.html + 178 + + Downloads manager enabled setting + + + Allow quality select + 允许选择下载质量 + + app/settings/settings.component.html + 181 + + Allow quality seelct setting + + + Download only mode + 仅下载模式 + + app/settings/settings.component.html + 184 + + Download only mode setting + + + Allow multi-download mode + 开启多下载模式 + + app/settings/settings.component.html + 187 + + Allow multi-download mode setting + + + Require pin for settings + 使用PIN码保护设置 + + app/settings/settings.component.html + 190 + + Require pin for settings setting + + + Set New Pin + 设置新PIN码 + + app/settings/settings.component.html + 191 + + Set new pin button + + + Enable Public API + 启用公共API + + app/settings/settings.component.html + 199 + + Enable Public API key setting + + + Public API Key + 公共API密钥 + + app/settings/settings.component.html + 204 + + Public API Key setting placeholder + + + View documentation + 查看文档 + + app/settings/settings.component.html + 205 + + View API docs setting hint + + + Generate + 生成 + + app/settings/settings.component.html + 209 + + Generate key button + + + Use YouTube API + 使用YouTube API + + app/settings/settings.component.html + 218 + + Use YouTube API setting + + + Youtube API Key + Youtube API密钥 + + app/settings/settings.component.html + 222 + + Youtube API Key setting placeholder + + + Generating a key is easy! + 生成密钥很简单! + + app/settings/settings.component.html + 223 + + Youtube API Key setting hint + + + Click here + 点击这里 + + app/settings/settings.component.html + 233 + + + app/settings/settings.component.html + 239 + + + app/dialogs/about-dialog/about-dialog.component.html + 25 + + Chrome ext click here + + + to download the official YoutubeDL-Material Chrome extension manually. + 来手动下载官方的YoutubeDL-Material Chrome扩展程序。 + + app/settings/settings.component.html + 233 + + Chrome click here suffix + + + You must manually load the extension and modify the extension's settings to set the frontend URL. + 您必须手动安装扩展,并且在扩展的设置中输入下载器URL。 + + app/settings/settings.component.html + 234 + + Chrome setup suffix + + + to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. + 直接从Firefox扩展商店安装官方的YoutubeDL-Material Firefox扩展程序。 + + app/settings/settings.component.html + 239 + + Firefox click here suffix + + + Detailed setup instructions. + 详细的扩展说明。 + + app/settings/settings.component.html + 240 + + Firefox setup prefix link + + + Not much is required other than changing the extension's settings to set the frontend URL. + 只需在扩展的设置中输入前端URL。 + + app/settings/settings.component.html + 240 + + Firefox setup suffix + + + Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. + 只需将下面的链接拖放到书签栏中。在YouTube页面上您只需单击书签即可下载视频。 + + app/settings/settings.component.html + 245 + + Bookmarklet instructions + + + Generate 'audio only' bookmarklet + 生成“仅音频”书签 + + app/settings/settings.component.html + 246 + + Generate audio only bookmarklet checkbox + + + Extra + 额外 + + app/settings/settings.component.html + 164 + + Extra settings label + + + Use default downloading agent + 使用默认下载程序 + + app/settings/settings.component.html + 260 + + Use default downloading agent setting + + + Select a downloader + 选择下载器 + + app/settings/settings.component.html + 264 + + Custom downloader select label + + + Select a logger level + 选择日志级别 + + app/settings/settings.component.html + 278 + + Logger level select label + + + Allow advanced download + 开启高级下载选项 + + app/settings/settings.component.html + 289 + + Allow advanced downloading setting + + + Use Cookies + 使用Cookies + + app/settings/settings.component.html + 297 + + Use cookies setting + + + Set Cookies + 设置Cookies + + app/settings/settings.component.html + 298 + + Set cookies button + + + Advanced + 高级 + + app/settings/settings.component.html + 255 + + Host settings label + + + Allow user registration + 允许用户注册 + + app/settings/settings.component.html + 310 + + Allow registration setting + + + Users + 用户 + + app/settings/settings.component.html + 308 + + Users settings label + + + Logs + 日志 + + app/settings/settings.component.html + 314 + + Logs settings label + + + Save + 保存 + + app/settings/settings.component.html + 327 + + Settings save button + + + {VAR_SELECT, select, true {Close} false {Cancel} other {otha}} + {VAR_SELECT, select, true {关} false {取消} other {其他} } + + app/settings/settings.component.html + 330 + + Settings cancel and close button + + + About YoutubeDL-Material + 关于 YoutubeDL-Material + + app/dialogs/about-dialog/about-dialog.component.html + 1 + + About dialog title + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + 是根据Google的Material Design规范构建的开源YouTube下载器。您可以将喜欢的视频下载为视频或音频文件,并且可以订阅喜欢的频道和播放列表,以便及时下载他们的新视频。 + + app/dialogs/about-dialog/about-dialog.component.html + 12 + + About first paragraph + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. + 包含很多很棒的功能!支持API,Docker和本地化。在Github上查找所有受支持的功能。 + + app/dialogs/about-dialog/about-dialog.component.html + 15 + + About second paragraph + + + Installed version: + 安装版本: + + app/dialogs/about-dialog/about-dialog.component.html + 20 + + Version label + + + Checking for updates... + 检查更新... + + app/dialogs/about-dialog/about-dialog.component.html + 20 + + Checking for updates text + + + Update available + 更新可用 + + app/dialogs/about-dialog/about-dialog.component.html + 21 + + View latest update + + + You can update from the settings menu. + 您可以从设置菜单进行更新。 + + app/dialogs/about-dialog/about-dialog.component.html + 21 + + Update through settings menu hint + + + Found a bug or have a suggestion? + 发现了一个错误或有一些建议? + + app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug prefix + + + to create an issue! + 创建新issue! + + app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug suffix + + + Your Profile + 您的个人资料 + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 1 + + User profile dialog title + + + UID: + UID: + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 9 + + UID + + + Created: + 创建日期: + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 12 + + Created + + + You are not logged in. + 您尚未登录。 + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 19 + + Not logged in notification + + + Login + 登录 + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 20 + + + app/app.component.html + 44 + + + app/components/login/login.component.html + 15 + + Login + + + Logout + 注销 + + app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 28 + + Logout + + + Create admin account + 创建管理员帐户 + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 1 + + Create admin account dialog title + + + No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. + 未检测到默认管理员帐户。即将创建一个名为admin的管理员帐户并设置密码。 + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 5 + + No default admin detected explanation + + + Create + 创建 + + app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 17 + + Create + + + Profile + 个人资料 + + app/app.component.html + 19 + + Profile menu label + + + About + 关于 + + app/app.component.html + 32 + + About menu label + + + Home + 首 页 + + app/app.component.html + 43 + + Navigation menu Home Page title + + + Subscriptions + 订 阅 + + app/app.component.html + 45 + + Navigation menu Subscriptions Page title + + + Downloads + 下 载 + + app/app.component.html + 46 + + Navigation menu Downloads Page title + + + Share playlist + 分享播放列表 + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 2 + + Share playlist dialog title + + + Share video + 分享视频 + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 3 + + Share video dialog title + + + Share audio + 分享音频 + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 4 + + Share audio dialog title + + + Enable sharing + 启用共享 + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 10 + + Enable sharing checkbox + + + Use timestamp + 使用时间戳 + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 13 + + Use timestamp + + + Seconds + + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 15 + + Seconds + + + Copy to clipboard + 复制到剪贴板 + + app/dialogs/share-media-dialog/share-media-dialog.component.html + 24 + + + app/components/logs-viewer/logs-viewer.component.html + 7 + + Copy to clipboard button + + + Save changes + 保存更改 + + app/player/player.component.html + 22 + + Playlist save changes button + + + Details + 详细 + + app/download-item/download-item.component.html + 18 + + Details + + + An error has occurred: + 发生错误: + + app/download-item/download-item.component.html + 27 + + Error label + + + Download start: + 下载开始: + + app/download-item/download-item.component.html + 32 + + Download start label + + + Download end: + 下载结束: + + app/download-item/download-item.component.html + 35 + + Download end label + + + File path(s): + 文件路径: + + app/download-item/download-item.component.html + 38 + + File path(s) label + + + Subscribe to playlist or channel + 订阅播放列表或频道 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 1 + + Subscribe dialog title + + + The playlist or channel URL + 播放列表或频道URL + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 9 + + Subscription URL input hint + + + Custom name + 自定义名称 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 19 + + Subscription custom name placeholder + + + Download all uploads + 下载所有音视频 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 23 + + Download all uploads subscription setting + + + Download videos uploaded in the last + 下载最近多久的视频 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 26 + + Download time range prefix + + + Audio-only mode + 仅音频模式 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 38 + + Streaming-only mode + + + Streaming-only mode + 仅视频模式 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 43 + + Streaming-only mode + + + These are added after the standard args. + 这些是在标准参数之后添加的。 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 51 + + Custom args hint + + + Custom file output + 自定义文件输出 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 57 + + Subscription custom file output placeholder + + + Subscribe + 订阅 + + app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 72 + + Subscribe button + + + Type: + 类型: + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 5 + + Subscription type property + + + Archive: + 存档: + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 17 + + Subscription ID property + + + Export Archive + 导出存档 + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 24 + + Export Archive button + + + Unsubscribe + 取消订阅 + + app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 26 + + Unsubscribe button + + + Your subscriptions + 您的订阅 + + app/subscriptions/subscriptions.component.html + 3 + + Subscriptions title + + + Channels + 频道 + + app/subscriptions/subscriptions.component.html + 8 + + Subscriptions channels title + + + Name not available. Channel retrieval in progress. + 名称不可用。正在检索频道... + + app/subscriptions/subscriptions.component.html + 14 + + Subscription playlist not available text + + + You have no channel subscriptions. + 您尚未订阅任何频道。 + + app/subscriptions/subscriptions.component.html + 24 + + No channel subscriptions text + + + Name not available. Playlist retrieval in progress. + 名称不可用。正在检索播放列表... + + app/subscriptions/subscriptions.component.html + 33 + + Subscription playlist not available text + + + You have no playlist subscriptions. + 您尚未订阅任何播放列表。 + + app/subscriptions/subscriptions.component.html + 43 + + No playlist subscriptions text + + + Search + 搜索 + + app/subscription/subscription/subscription.component.html + 32 + + Subscription videos search placeholder + + + Length: + 长度: + + app/subscription/subscription-file-card/subscription-file-card.component.html + 3 + + Video duration label + + + Delete and redownload + 删除并重新下载 + + app/subscription/subscription-file-card/subscription-file-card.component.html + 8 + + Delete and redownload subscription video button + + + Delete forever + 永久删除 + + app/subscription/subscription-file-card/subscription-file-card.component.html + 9 + + Delete forever subscription video button + + + Updater + 更新程序 + + app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 1 + + Update progress dialog title + + + Select a version: + 选择版本: + + app/updater/updater.component.html + 3 + + Select a version + + + Register + 注册 + + app/components/login/login.component.html + 35 + + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 17 + + Register + + + Session ID: + 会话ID: + + app/components/downloads/downloads.component.html + 5 + + Session ID + + + (current) + (当前) + + app/components/downloads/downloads.component.html + 6 + + Current session + + + No downloads available! + 没有下载可用! + + app/components/downloads/downloads.component.html + 25 + + No downloads label + + + Register a user + 注册用户 + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 1 + + Register user dialog title + + + User name + 用户名 + + app/dialogs/add-user-dialog/add-user-dialog.component.html + 6 + + User name placeholder + + + Manage user + 管理用户 + + app/components/manage-user/manage-user.component.html + 1 + + Manage user dialog title + + + User UID: + 用户UID: + + app/components/manage-user/manage-user.component.html + 4 + + User UID + + + New password + 新密码 + + app/components/manage-user/manage-user.component.html + 8 + + New password placeholder + + + Set new password + 设置新密码 + + app/components/manage-user/manage-user.component.html + 10 + + Set new password + + + Use default + 使用默认值 + + app/components/manage-user/manage-user.component.html + 19 + + Use default + + + Yes + + + app/components/manage-user/manage-user.component.html + 20 + + + app/components/manage-role/manage-role.component.html + 9 + + Yes + + + No + + + app/components/manage-user/manage-user.component.html + 21 + + + app/components/manage-role/manage-role.component.html + 10 + + No + + + Manage role + 管理用户 + + app/components/manage-role/manage-role.component.html + 1 + + Manage role dialog title + + + User name + 用户名 + + app/components/modify-users/modify-users.component.html + 17 + + Username users table header + + + Role + 身份 + + app/components/modify-users/modify-users.component.html + 35 + + Role users table header + + + Actions + 动作 + + app/components/modify-users/modify-users.component.html + 55 + + Actions users table header + + + Add Users + 添加用户 + + app/components/modify-users/modify-users.component.html + 90 + + Add users button + + + Edit Role + 编辑用户 + + app/components/modify-users/modify-users.component.html + 95 + + Edit role + + + Logs will appear here + 日志将出现在这里 + + app/components/logs-viewer/logs-viewer.component.html + 5 + + Logs placeholder + + + Lines: + 行: + + app/components/logs-viewer/logs-viewer.component.html + 9 + + Label for lines select in logger view + + + Include thumbnail + 包括缩略图 + + app/settings/settings.component.html + 131 + + Include thumbnail setting + + + Type + 类型 + + app/create-playlist/create-playlist.component.html + 11 + + Type select + + + Video + 视频 + + app/create-playlist/create-playlist.component.html + 13 + + Video + + + Audio + 音频 + + app/create-playlist/create-playlist.component.html + 12 + + Audio + + + Open file + 打开文件 + + app/components/unified-file-card/unified-file-card.component.html + 13 + + Open file button + + + Clear logs + 清空日志 + + app/components/logs-viewer/logs-viewer.component.html + 34 + + Clear logs button + + + Delete user + 删除用户 + + app/components/modify-users/modify-users.component.html + 73 + + delete user action button tooltip + + + Edit user + 编辑用户 + + app/components/modify-users/modify-users.component.html + 66 + + edit user action button tooltip + + + Clear all downloads + 清空所有下载 + + app/components/downloads/downloads.component.html + 18 + + clear all downloads action button + + + Bind Credentials + 绑定凭证 + + app/settings/settings.component.html + 328 + + Bind Credentials + + + Bind DN + 绑定DN + + app/settings/settings.component.html + 323 + + Bind DN + + + LDAP URL + LDAP链接 + + app/settings/settings.component.html + 318 + + LDAP URL + + + Auth method + 认证方式 + + app/settings/settings.component.html + 306 + + Auth method select + + + LDAP + LDAP认证 + + app/settings/settings.component.html + 311 + + LDAP auth method + + + Internal + 内部身份验证 + + app/settings/settings.component.html + 308 + + Internal auth method + + + Login expiration + 登录到期 + + app/settings/settings.component.html + 268 + + Login expiration select label + + + Log Level + 日志等级 + + app/settings/settings.component.html + 256 + + Log Level label + + + This will delete your old API key! + 这将删除您的旧API密钥! + + app/settings/settings.component.html + 187 + + delete api key tooltip + + + Include metadata + 包含元数据 + + app/settings/settings.component.html + 135 + + Include metadata setting + + + NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user. + 注意:加载新的Cookies将覆盖您以前的Cookie。并且Cookies的范围是整个实例,而不是每个用户单独分开的。 + + app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 + + Cookies upload warning + + + Add more content + 添加更多内容 + + app/dialogs/modify-playlist/modify-playlist.component.html + 17 + + Add more content + + + My videos + 我的视频 + + app/components/recent-videos/recent-videos.component.html + 20 + + My videos title + + + Go to subscription + 前往订阅 + + app/components/unified-file-card/unified-file-card.component.html + 20 + + Go to subscription menu item + + + Open file in new tab + 在新标签页打开文件 + + app/components/unified-file-card/unified-file-card.component.html + 14 + + Open file in new tab + + + An error has occurred + 出现错误 + + app/download-item/download-item.component.html + 9 + + download error tooltip + + + The download was successful + 下载成功 + + app/download-item/download-item.component.html + 8 + + download successful tooltip + + + Search Filter + 搜索过滤器 + + app/settings/settings.component.html + 338 + + Search Filter + + + Search Base + 搜索起点 + + app/settings/settings.component.html + 333 + + Search Base + + + Use role default + 使用角色预设 + + app/components/manage-user/manage-user.component.html + 19 + + Use role default + + + Editing + 编辑中 + + app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + Edit subscription dialog title prefix + + + Kill all downloads + 取消所有下载 + + app/settings/settings.component.html + 139 + + Kill all downloads button + + + See less. + 查看更少 + + src/app/components/see-more/see-more.component.html + 8,9 + + See less + + + Select a download agent + 选择一个下载程序 + + src/app/settings/settings.component.html + 299 + + Custom downloader select label + + + Max quality + 最高画质 + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 32 + + Max quality placeholder + + + See more. + 查看更多... + + src/app/components/see-more/see-more.component.html + 5,6 + + See more + + + Auto-download Twitch Chat + 自动下载Twitch弹幕 + + src/app/settings/settings.component.html + 244 + + Auto download Twitch Chat setting + + + Also known as a Client ID. + 也称为客户ID + + src/app/settings/settings.component.html + 249 + + Twitch API Key setting hint AKA preamble + + + Twitch API Key + Twitch API 密钥 + + src/app/settings/settings.component.html + 248 + + Twitch API Key setting placeholder + + + Use Twitch API + 使用Twitch API + + src/app/settings/settings.component.html + 241 + + Use Twitch API setting + + + Categories + 分类 + + src/app/settings/settings.component.html + 144 + + Categories + + + Global custom args + 全局自定义变量 + + src/app/settings/settings.component.html + 133 + + Custom args input placeholder + + + Path is relative to the above download paths. Don't include extension. + 路径相对于上述下载路径,不包括扩展名。 + + src/app/settings/settings.component.html + 126 + + Custom Output input hint + + + Default file output + 默认输出文件夹 + + src/app/settings/settings.component.html + 123 + + Default file output placeholder + + + Redownload fresh uploads + 重新下载新上传的内容 + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads + + + Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day. + 有时新视频会在完全处理前下载。这项设置指新视频会在第二天检查视频是否有更高画质。 + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads tooltip + + + views + 浏览 + + src/app/player/player.component.html + 15 + + View count label + + + Download Twitch Chat + 下载Twitch弹幕 + + src/app/components/twitch-chat/twitch-chat.component.html + 10 + + Download Twitch Chat button + + + Add new rule + 添加新规则 + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 39 + + Add new rule tooltip + + + Rules + 规则 + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 10 + + Rules + + + Editing category + 编辑类别 + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 1 + + Editing category dialog title + + + Paused + 暂停 + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 7 + + Paused subscription setting + + + No videos found. + 找不到视频 + + src/app/components/recent-videos/recent-videos.component.html + 38 + + No videos found + + + Reverse order + 倒序 + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 14 + + Reverse order + + + Normal order + 正序 + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 13 + + Normal order + + + Add content + 添加内容 + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 19 + + Add content + + + Category: + 类别: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 29 + + Category property + + + (Paused) + (暂停) + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 1 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + + src/app/subscriptions/subscriptions.component.html + 12 + + + src/app/subscriptions/subscriptions.component.html + 31 + + + src/app/subscription/subscription/subscription.component.html + 5 + + Paused suffix + + + Auto-generated + 自动生成的 + + src/app/components/unified-file-card/unified-file-card.component.html + 5 + + Auto-generated label + + + + From b978007472aed6650c0f5475c803d72527ce8e20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Jan 2021 10:25:29 +0000 Subject: [PATCH 092/250] Bump axios from 0.21.0 to 0.21.1 in /backend Bumps [axios](https://github.com/axios/axios) from 0.21.0 to 0.21.1. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.21.0...v0.21.1) Signed-off-by: dependabot[bot] --- backend/package-lock.json | 12 ++++++------ backend/package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 27c6ef8..4067dd9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -253,9 +253,9 @@ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, "axios": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", - "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { "follow-redirects": "^1.10.0" } @@ -1081,9 +1081,9 @@ } }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" }, "forever-agent": { "version": "0.6.1", diff --git a/backend/package.json b/backend/package.json index 2c837c4..862a09c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,7 +30,7 @@ "dependencies": { "archiver": "^3.1.1", "async": "^3.1.0", - "axios": "^0.21.0", + "axios": "^0.21.1", "bcryptjs": "^2.4.0", "compression": "^1.7.4", "config": "^3.2.3", From db78e4ad5e5442299d6b16c88e933aa0974ac854 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 9 Jan 2021 14:06:19 -0500 Subject: [PATCH 093/250] Fixed bug where playlist downloads would fail and progress would not show (for playlist downloads) --- backend/app.js | 15 +++++++++++---- backend/utils.js | 28 +++++++++++++++++----------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/backend/app.js b/backend/app.js index 6f51c45..bb55b2d 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1144,7 +1144,12 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } // store info in download for future use - download['_filename'] = info['_filename']; + if (Array.isArray(info)) { + download['fileNames'] = []; + for (let info_obj of info) download['fileNames'].push(info_obj['_filename']); + } else { + download['_filename'] = info['_filename']; + } download['filesize'] = utils.getExpectedFileSize(info); download_checker = setInterval(() => checkDownloadPercent(download), 1000); } @@ -1621,13 +1626,15 @@ function checkDownloadPercent(download) { be divided by the "total expected bytes." */ const file_id = download['file_id']; - const filename = path.format(path.parse(download['_filename'].substring(0, download['_filename'].length-4))); + // assume it's a playlist for logic reasons + const fileNames = Array.isArray(download['fileNames']) ? download['fileNames'] + : [path.format(path.parse(utils.removeFileExtension(download['_filename'])))]; const resulting_file_size = download['filesize']; if (!resulting_file_size) return; - glob(`${filename}*`, (err, files) => { - let sum_size = 0; + let sum_size = 0; + glob(`{${fileNames.join(',')}, }*`, (err, files) => { files.forEach(file => { try { const file_stats = fs.statSync(file); diff --git a/backend/utils.js b/backend/utils.js index 3524041..cd7c23d 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -105,20 +105,26 @@ function getDownloadedThumbnail(name, type, customPath = null) { return null; } -function getExpectedFileSize(info_json) { - if (info_json['filesize']) { - return info_json['filesize']; - } +function getExpectedFileSize(input_info_jsons) { + // treat single videos as arrays to have the file sizes checked/added to. makes the code cleaner + const info_jsons = Array.isArray(input_info_jsons) ? input_info_jsons : [input_info_jsons]; - const formats = info_json['format_id'].split('+'); let expected_filesize = 0; - formats.forEach(format_id => { - if (!info_json.formats) return expected_filesize; - info_json.formats.forEach(available_format => { - if (available_format.format_id === format_id && available_format.filesize) { - expected_filesize += available_format.filesize; - } + info_jsons.forEach(info_json => { + if (info_json['filesize']) { + expected_filesize += info_json['filesize']; + return; + } + const formats = info_json['format_id'].split('+'); + let individual_expected_filesize = 0; + formats.forEach(format_id => { + info_json.formats.forEach(available_format => { + if (available_format.format_id === format_id && available_format.filesize) { + individual_expected_filesize += available_format.filesize; + } + }); }); + expected_filesize += individual_expected_filesize; }); return expected_filesize; From ed1375d40bed41a4777a69160a5a0ba3d11be51c Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 9 Jan 2021 14:07:43 -0500 Subject: [PATCH 094/250] Fixed bug where deleting videos while searching caused them to still show up in the UI --- src/app/components/recent-videos/recent-videos.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 68125cf..d130ccd 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -251,6 +251,9 @@ export class RecentVideosComponent implements OnInit { this.postsService.openSnackBar('Delete success!', 'OK.'); this.files.splice(file.index, 1); for (let i = 0; i < this.files.length; i++) { this.files[i].index = i } + if (this.search_mode) { + this.filterFiles(this.search_text); + } this.filterByProperty(this.filterProperty['property']); } else { this.postsService.openSnackBar('Delete failed!', 'OK.'); From a93aa080b317e4933db4593136dafdf4c03df56e Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 9 Jan 2021 17:25:46 -0500 Subject: [PATCH 095/250] Fixed bug where playlistd could not be made --- backend/app.js | 4 ++-- backend/authentication/auth.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app.js b/backend/app.js index bb55b2d..b49372e 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1954,7 +1954,7 @@ app.get('/api/getMp3s', optionalJwt, async function(req, res) { // get user audio files/playlists auth_api.passport.authenticate('jwt') mp3s = auth_api.getUserVideos(req.user.uid, 'audio'); - playlists = auth_api.getUserPlaylists(req.user.uid, 'audio'); + playlists = auth_api.getUserPlaylists(req.user.uid); } mp3s = JSON.parse(JSON.stringify(mp3s)); @@ -1975,7 +1975,7 @@ app.get('/api/getMp4s', optionalJwt, async function(req, res) { // get user videos/playlists auth_api.passport.authenticate('jwt') mp4s = auth_api.getUserVideos(req.user.uid, 'video'); - playlists = auth_api.getUserPlaylists(req.user.uid, 'video'); + playlists = auth_api.getUserPlaylists(req.user.uid); } mp4s = JSON.parse(JSON.stringify(mp4s)); diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index 442d7fa..e7cf337 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -285,7 +285,7 @@ exports.adminExists = function() { exports.getUserVideos = function(user_uid, type) { const user = users_db.get('users').find({uid: user_uid}).value(); - return type ? user['files'].filter(file => file.isAudio = (type === 'audio')) : user['files']; + return type ? user['files'].filter(file => file.isAudio === (type === 'audio')) : user['files']; } exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) { From 95bb69f16b0e076f52ebe761a7f09a19788f14ae Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 10 Jan 2021 17:14:10 -0500 Subject: [PATCH 096/250] Fixed bug where videos would not delete in single-user mode --- backend/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.js b/backend/app.js index b49372e..6f1b148 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2615,7 +2615,7 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { var wasDeleted = false; if (await fs.pathExists(fullpath)) { - wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode); + 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); From 7835185fe0ffb95271ec389c6c3cb5dc6da2ab87 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 11 Jan 2021 01:18:58 -0500 Subject: [PATCH 097/250] Made file card deletion much more reliable by finding out the index of the file on deletion rather than attempting to maintain a valid index --- .../recent-videos/recent-videos.component.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index d130ccd..f213e12 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -131,7 +131,6 @@ export class RecentVideosComponent implements OnInit { for (let i = 0; i < this.files.length; i++) { const file = this.files[i]; file.duration = typeof file.duration !== 'string' ? file.duration : this.durationStringToNumber(file.duration); - file.index = i; } if (this.search_mode) { this.filterFiles(this.search_text); @@ -239,22 +238,17 @@ export class RecentVideosComponent implements OnInit { const blacklistMode = args.blacklistMode; if (file.sub_id) { - this.deleteSubscriptionFile(file, index, blacklistMode); + this.deleteSubscriptionFile(file, blacklistMode); } else { - this.deleteNormalFile(file, index, blacklistMode); + this.deleteNormalFile(file, blacklistMode); } } - deleteNormalFile(file, index, blacklistMode = false) { + deleteNormalFile(file, blacklistMode = false) { this.postsService.deleteFile(file.uid, file.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => { if (result) { this.postsService.openSnackBar('Delete success!', 'OK.'); - this.files.splice(file.index, 1); - for (let i = 0; i < this.files.length; i++) { this.files[i].index = i } - if (this.search_mode) { - this.filterFiles(this.search_text); - } - this.filterByProperty(this.filterProperty['property']); + this.removeFileCard(file); } else { this.postsService.openSnackBar('Delete failed!', 'OK.'); } @@ -263,30 +257,40 @@ export class RecentVideosComponent implements OnInit { }); } - deleteSubscriptionFile(file, index, blacklistMode = false) { + deleteSubscriptionFile(file, blacklistMode = false) { if (blacklistMode) { - this.deleteForever(file, index); + this.deleteForever(file); } else { - this.deleteAndRedownload(file, index); + this.deleteAndRedownload(file); } } - deleteAndRedownload(file, index) { + deleteAndRedownload(file) { const sub = this.postsService.getSubscriptionByID(file.sub_id); this.postsService.deleteSubscriptionFile(sub, file.id, false, file.uid).subscribe(res => { this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`); - this.files.splice(index, 1); + this.removeFileCard(file); }); } - deleteForever(file, index) { + deleteForever(file) { const sub = this.postsService.getSubscriptionByID(file.sub_id); this.postsService.deleteSubscriptionFile(sub, file.id, true, file.uid).subscribe(res => { this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`); - this.files.splice(index, 1); + this.removeFileCard(file); }); } + removeFileCard(file_to_remove) { + console.log(file_to_remove.uid); + const index = this.files.map(e => e.uid).indexOf(file_to_remove.uid); + this.files.splice(index, 1); + if (this.search_mode) { + this.filterFiles(this.search_text); + } + this.filterByProperty(this.filterProperty['property']); + } + // sorting and filtering sortFiles(a, b) { From f0f2faa39806bbbe31456c6ab356e0426da5ec4b Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 11 Jan 2021 01:19:29 -0500 Subject: [PATCH 098/250] Sub's videos are removed from the post request when deleting a video as it's not needed --- src/app/posts.services.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 4ae90dd..f458494 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -365,6 +365,7 @@ export class PostsService implements CanActivate { } deleteSubscriptionFile(sub, file, deleteForever, file_uid) { + delete sub['videos']; return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever, file_uid: file_uid}, this.httpOptions) } From 539bc5094ac1ec5fbd0cf07296f8865282be809f Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 11 Jan 2021 01:20:07 -0500 Subject: [PATCH 099/250] Fixed bug where sometimes a subscription video's thumbnail would get deleted twice and throw an error --- backend/subscriptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/subscriptions.js b/backend/subscriptions.js index 4bdca93..b3e2f1e 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -211,7 +211,7 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, var jsonPath = path.join(__dirname,filePath,name+'.info.json'); var videoFilePath = path.join(__dirname,filePath,name+ext); var imageFilePath = path.join(__dirname,filePath,name+'.jpg'); - var altImageFilePath = path.join(__dirname,filePath,name+'.jpg'); + var altImageFilePath = path.join(__dirname,filePath,name+'.webp'); const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([ fs.pathExists(jsonPath), From a78f4e99d07db47c088e3aadf3da14aada7b0401 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 11 Jan 2021 01:20:53 -0500 Subject: [PATCH 100/250] Removed trivial browser log that occured at file deletion --- src/app/components/recent-videos/recent-videos.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index f213e12..6aec7f2 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -282,7 +282,6 @@ export class RecentVideosComponent implements OnInit { } removeFileCard(file_to_remove) { - console.log(file_to_remove.uid); const index = this.files.map(e => e.uid).indexOf(file_to_remove.uid); this.files.splice(index, 1); if (this.search_mode) { From 133d84872964f5a79ace499770e31595cf92f9ef Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 11 Jan 2021 13:55:02 -0500 Subject: [PATCH 101/250] Fixed bug where deleting a file card wasn't possible if it was already deleted manually --- backend/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.js b/backend/app.js index 6f1b148..5fab23c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2619,7 +2619,7 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => { db.get('files').remove({uid: uid}).write(); wasDeleted = true; res.send(wasDeleted); - } else if (video_obj) { + } else if (file_obj) { db.get('files').remove({uid: uid}).write(); wasDeleted = true; res.send(wasDeleted); From 28ee77cee0ac3cef25151b84f511649f192906ae Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 12 Jan 2021 16:42:30 -0500 Subject: [PATCH 102/250] Hotfix that allows playlists to be downloaded with categories --- backend/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.js b/backend/app.js index 5fab23c..04edd6a 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1133,7 +1133,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { return; } else if (info) { // check if it fits into a category. If so, then get info again using new downloadConfig - category = await categories_api.categorize(info); + if (!Array.isArray(info)) category = await categories_api.categorize(info); // set custom output if the category has one and re-retrieve info so the download manager has the right file name if (category && category['custom_output']) { From 1d5490c0ff9195bfcb8acf778cee6e17e3509e38 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 12 Jan 2021 22:08:42 -0500 Subject: [PATCH 103/250] Allows playlists to be categorized based on the first video that matches --- backend/app.js | 2 +- backend/appdata/default.json | 3 ++- backend/categories.js | 30 ++++++++++++++---------- backend/config.js | 3 ++- backend/consts.js | 4 ++++ src/app/settings/settings.component.html | 5 +++- src/assets/default.json | 3 ++- 7 files changed, 32 insertions(+), 18 deletions(-) diff --git a/backend/app.js b/backend/app.js index 04edd6a..1a65d47 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1133,7 +1133,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { return; } else if (info) { // check if it fits into a category. If so, then get info again using new downloadConfig - if (!Array.isArray(info)) category = await categories_api.categorize(info); + if (!Array.isArray(info) || config_api.getConfigItem('ytdl_allow_playlist_categorization')) category = await categories_api.categorize(info); // set custom output if the category has one and re-retrieve info so the download manager has the right file name if (category && category['custom_output']) { diff --git a/backend/appdata/default.json b/backend/appdata/default.json index dca9eee..5bd5c54 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -20,7 +20,8 @@ "allow_quality_select": true, "download_only_mode": false, "allow_multi_download_mode": true, - "enable_downloads_manager": true + "enable_downloads_manager": true, + "allow_playlist_categorization": true }, "API": { "use_API_key": false, diff --git a/backend/categories.js b/backend/categories.js index d0b249a..d2af431 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -33,27 +33,31 @@ Rules: */ -async function categorize(file_json) { +async function categorize(file_jsons) { + // to make the logic easier, let's assume the file metadata is an array + if (!Array.isArray(file_jsons)) file_jsons = [file_jsons]; + let selected_category = null; const categories = getCategories(); if (!categories) { logger.warn('Categories could not be found. Initializing categories...'); db.assign({categories: []}).write(); return null; - return; } - for (let i = 0; i < categories.length; i++) { - const category = categories[i]; - const rules = category['rules']; - - // if rules for current category apply, then that is the selected category - if (applyCategoryRules(file_json, rules, category['name'])) { - selected_category = category; - logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`); - return selected_category; - } - } + file_jsons.forEach(file_json => { + categories.forEach(category => { + const rules = category['rules']; + + // if rules for current category apply, then that is the selected category + if (applyCategoryRules(file_json, rules, category['name'])) { + selected_category = category; + logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`); + return selected_category; + } + }); + }); + return selected_category; } diff --git a/backend/config.js b/backend/config.js index 4790e34..cb3e8b3 100644 --- a/backend/config.js +++ b/backend/config.js @@ -197,7 +197,8 @@ DEFAULT_CONFIG = { "allow_quality_select": true, "download_only_mode": false, "allow_multi_download_mode": true, - "enable_downloads_manager": true + "enable_downloads_manager": true, + "allow_playlist_categorization": true }, "API": { "use_API_key": false, diff --git a/backend/consts.js b/backend/consts.js index fa14171..fc29fcf 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -68,6 +68,10 @@ let CONFIG_ITEMS = { 'key': 'ytdl_enable_downloads_manager', 'path': 'YoutubeDLMaterial.Extra.enable_downloads_manager' }, + 'ytdl_allow_playlist_categorization': { + 'key': 'ytdl_allow_playlist_categorization', + 'path': 'YoutubeDLMaterial.Extra.allow_playlist_categorization' + }, // API 'ytdl_use_api_key': { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index bd085b1..a7d69d6 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -140,7 +140,7 @@
-
+
Categories
@@ -154,6 +154,9 @@
+
+ Allow playlist categorization +
diff --git a/src/assets/default.json b/src/assets/default.json index 532b32e..ce2cdb3 100644 --- a/src/assets/default.json +++ b/src/assets/default.json @@ -20,7 +20,8 @@ "download_only_mode": false, "allow_multi_download_mode": true, "settings_pin_required": false, - "enable_downloads_manager": true + "enable_downloads_manager": true, + "allow_playlist_categorization": true }, "API": { "use_API_key": false, From d7d861ef0e504f6dc100fe2bd85f1fd5d1691d3b Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 12 Jan 2021 22:32:27 -0500 Subject: [PATCH 104/250] Fixed typo in default custom output key for categories --- backend/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.js b/backend/app.js index 1a65d47..9dbad30 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2244,7 +2244,7 @@ app.post('/api/createCategory', optionalJwt, async (req, res) => { name: name, uid: uuid(), rules: [], - custom_putput: '' + custom_output: '' }; db.get('categories').push(new_category).write(); From af58854f0e4e53add23a9b9ec2d401b22ad2f5ea Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 13 Jan 2021 12:50:18 -0500 Subject: [PATCH 105/250] Added info button to the player component --- src/app/player/player.component.html | 1 + src/app/player/player.component.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 6aaa7b0..086adf6 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -35,6 +35,7 @@ +
diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 123e2b4..b3660a5 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -8,6 +8,7 @@ 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 { TwitchChatComponent } from 'app/components/twitch-chat/twitch-chat.component'; +import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component'; export interface IMedia { title: string; @@ -478,6 +479,15 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy { } }); } + + openFileInfoDialog() { + this.dialog.open(VideoInfoDialogComponent, { + data: { + file: this.db_file, + }, + minWidth: '50vw' + }) + } // snackbar helper public openSnackBar(message: string, action: string) { From 6481102e01ab96e1326e70a0e9adb77632e74123 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 13 Jan 2021 16:12:11 -0500 Subject: [PATCH 106/250] Changes forEach loops in categorize() to regular for loops to facilitate early breaking --- backend/categories.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/categories.js b/backend/categories.js index d2af431..d4b19f5 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -45,8 +45,10 @@ async function categorize(file_jsons) { return null; } - file_jsons.forEach(file_json => { - categories.forEach(category => { + for (let i = 0; i < file_jsons.length; i++) { + const file_json = file_jsons[i]; + for (let j = 0; j < categories.length; j++) { + const category = categories[i]; const rules = category['rules']; // if rules for current category apply, then that is the selected category @@ -55,8 +57,8 @@ async function categorize(file_jsons) { logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`); return selected_category; } - }); - }); + } + } return selected_category; } From a1b32e2851824b1b644e9ee7a21e5f201b764d42 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 20 Jan 2021 08:32:16 -0500 Subject: [PATCH 107/250] 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 00a0ab460bbf59955c56651d52788cb9e0a1f433 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Wed, 20 Jan 2021 08:37:14 -0500 Subject: [PATCH 108/250] Subscription's videos are now stripped from HTTP requests where they are not needed --- src/app/posts.services.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index f458494..6657f00 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -357,10 +357,12 @@ export class PostsService implements CanActivate { } updateSubscription(subscription) { + delete subscription['videos']; return this.http.post(this.path + 'updateSubscription', {subscription: subscription}, this.httpOptions); } unsubscribe(sub, deleteMode = false) { + delete sub['videos']; return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions) } From 3f9314a0c3e1d707d66b370c2f3e7bea83bb0daf Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Thu, 28 Jan 2021 22:11:04 -0500 Subject: [PATCH 109/250] Fixed bug where categories selection logic had an out of range exception --- backend/categories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/categories.js b/backend/categories.js index d4b19f5..2134373 100644 --- a/backend/categories.js +++ b/backend/categories.js @@ -48,7 +48,7 @@ async function categorize(file_jsons) { for (let i = 0; i < file_jsons.length; i++) { const file_json = file_jsons[i]; for (let j = 0; j < categories.length; j++) { - const category = categories[i]; + const category = categories[j]; const rules = category['rules']; // if rules for current category apply, then that is the selected category From e34aa4d9d6aaea9bb2337f03465513828c16af00 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 31 Jan 2021 19:47:14 -0500 Subject: [PATCH 110/250] Adds Dutch language support --- src/app/settings/locales_list.ts | 2 +- src/app/settings/settings.component.ts | 2 +- src/assets/i18n/messages.nl.json | 248 +++ src/assets/i18n/messages.nl.xlf | 2517 ++++++++++++++++++++++++ 4 files changed, 2767 insertions(+), 2 deletions(-) create mode 100644 src/assets/i18n/messages.nl.json create mode 100644 src/assets/i18n/messages.nl.xlf diff --git a/src/app/settings/locales_list.ts b/src/app/settings/locales_list.ts index 5e88b73..47d398c 100644 --- a/src/app/settings/locales_list.ts +++ b/src/app/settings/locales_list.ts @@ -159,7 +159,7 @@ export const isoLangs = { }, 'nl': { 'name': 'Dutch', - 'nativeName': 'Nederlands, Vlaams' + 'nativeName': 'Nederlands' }, 'en': { 'name': 'English', diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 824b270..2ef36fa 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -20,7 +20,7 @@ import { EditCategoryDialogComponent } from 'app/dialogs/edit-category-dialog/ed }) export class SettingsComponent implements OnInit { all_locales = isoLangs; - supported_locales = ['en', 'es', 'de', 'fr', 'zh', 'nb', 'it', 'en-GB']; + supported_locales = ['en', 'es', 'de', 'fr', 'nl', 'zh', 'nb', 'it', 'en-GB']; initialLocale = localStorage.getItem('locale'); initial_config = null; diff --git a/src/assets/i18n/messages.nl.json b/src/assets/i18n/messages.nl.json new file mode 100644 index 0000000..79226a8 --- /dev/null +++ b/src/assets/i18n/messages.nl.json @@ -0,0 +1,248 @@ +{ + "004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Over", + "994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profiel", + "adb4562d2dbd3584370e44496969d58c511ecb63": "Donker", + "121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Instellingen", + "92eee6be6de0b11c924e3ab27db30257159c0a7c": "Overzicht", + "6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Inloggen", + "357064ca9d9ac859eb618e28e8126fa32be049e2": "Abonnementen", + "822fab38216f64e8166d368b59fe756ca39d301b": "Downloads", + "4a9889d36910edc8323d7bab60858ab3da6d91df": "Alleen audio", + "6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Downloaden", + "a38ae1082fec79ba1f379978337385a539a28e73": "Kwaliteit", + "4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "URL gebruiken", + "d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Bekijken", + "96a01fafe135afc58b0f8071a4ab00234495ce18": "Meerdere video's downloaden", + "6a3777f913cf3f288664f0632b9f24794fdcc24e": "Afbreken", + "322ed150e02666fe2259c5b4614eac7066f4ffa0": "Geavanceerd", + "4e4c721129466be9c3862294dc40241b64045998": "Aanvullende opties toekennen", + "ad2f8ac8b7de7945b80c8e424484da94e597125f": "Aanvullende opties", + "a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Je hoeft alleen de aanvullende opties op te geven, dus niet de url. Je kunt de opties scheiden met twee komma's: ,,", + "3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Aangepaste uitvoer gebruiken", + "d9c02face477f2f9cdaae318ccee5f89856851fb": "Aangepaste uitvoer", + "fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentatie", + "19d1ae64d94d28a29b2c57ae8671aace906b5401": "Het pad is relatief aan het ingestelde downloadpad. Laat de extensie achterwege.", + "b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Geteste opdracht:", + "8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Authenticatie gebruiken", + "08c74dc9762957593b91f6eb5d65efdfc975bf48": "Gebruikersnaam", + "c32ef07f8803a223a83ed17024b38e8d82292407": "Wachtwoord", + "17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Afspeellijst maken", + "cff1428d10d59d14e45edec3c735a27b5482db59": "Naam", + "f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Soort", + "f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio", + "2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video", + "f47e2d56dd8a145b2e9599da9730c049d52962a2": "Audiobestanden", + "a52dae09be10ca3a65da918533ced3d3f4992238": "Video's", + "a9806cf78ce00eb2613eeca11354a97e033377b8": "Abonneren op afspeellijst of kanaal", + "801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL", + "93efc99ae087fc116de708ecd3ace86ca237cf30": "De url van de afspeellijst of het kanaal", + "08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Aangepaste naam", + "ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Alle uploads downloaden", + "d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Maximumkwaliteit", + "c76a955642714b8949ff3e4b4990864a2e2cac95": "Audiomodus", + "408ca4911457e84a348cecf214f02c69289aa8f1": "Streamingmodus", + "f432e1a8d6adb12e612127978ce2e0ced933959c": "Deze worden toegevoegd ná de standaardopties.", + "98b6ec9ec138186d663e64770267b67334353d63": "Aangepaste bestandsuitvoer", + "d7b35c384aecd25a516200d6921836374613dfe7": "Annuleren", + "d0336848b0c375a1c25ba369b3481ee383217a4f": "Abonneren", + "28a678e9cabf86e44c32594c43fa0e890135c20f": "Video's downloaden die geüpload zijn in de afgelopen", + "e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Soort:", + "c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:", + "ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:", + "f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Sluiten", + "8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Archief exporteren", + "3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "De-abonneren", + "303e45ffae995c9817e510e38cb969e6bb3adcbf": "(onderbroken)", + "a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archief:", + "616e206cb4f25bd5885fc35925365e43cf5fb929": "Naam:", + "c6eb45d085384903e53ab001a3513d1de6a1dbac": "Uploader:", + "109c6f4a5e46efb933612ededfaf52a13178b7e0": "Bestandsgrootte:", + "bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Pad:", + "a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Uploaddatum:", + "0cc1dec590ecd74bef71a865fb364779bc42a749": "Categorie:", + "d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "youtube-dl-opties aanpassen", + "7fc1946abe2b40f60059c6cd19975d677095fd19": "Geteste nieuwe aanvullende opties", + "0b71824ae71972f236039bed43f8d2323e8fd570": "Optie toevoegen", + "c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Zoeken op categorie", + "9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Optiewaarde gebruiken", + "7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Optie toevoegen", + "b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Aanpassen", + "25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Optiewaarde", + "91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Updater", + "b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Gebruikersregistratie", + "024886ca34a6f309e3e51c2ed849320592c3faaa": "Gebruikersnaam", + "cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registreren", + "ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Nieuwe cookies uploaden", + "a8b7b9c168fd936a75e500806a8c0d7755ef1198": "Let op: de nieuwe cookies overschrijven de oude. Daarnaast zijn de cookies procesgebonden en niet gebruikersgebonden.", + "98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Slepen-en-neerzetten", + "4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Afspeellijst aanpassen", + "5caadefa4143cf6766a621b0f54f91f373a1f164": "Inhoud toevoegen", + "52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Opslaan", + "33026f57ea65cd9c8a5d917a08083f71a718933a": "Normale volgorde", + "29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Omgekeerde volgorde", + "d02888c485d3aeab6de628508f4a00312a722894": "Mijn video's", + "7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Zoeken", + "73423607944a694ce6f9e55cfee329681bb4d9f9": "Geen video's gevonden.", + "3697f8583ea42868aa269489ad366103d94aece7": "Bewerken", + "07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Onderbroken", + "c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Categorie bewerken", + "2489eefea00931942b91f4a1ae109514b591e2e1": "Regels", + "e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Regel toevoegen", + "792dc6a57f28a1066db283f2e736484f066005fd": "Twitch-chatgesprek downloaden", + "28f86ffd419b869711aa13f5e5ff54be6d70731c": "Aanpassen", + "826b25211922a1b46436589233cb6f1a163d89b7": "Verwijderen", + "321e4419a943044e674beb55b8039f42a9761ca5": "Informatie", + "e684046d73bcee88e82f7ff01e2852789a05fc32": "Aantal:", + "34504b488c24c27e68089be549f0eeae6ebaf30b": "Verwijderen en op zwarte lijst plaatsen", + "dad95154dcef3509b8cc705046061fd24994bbb7": "weergaven", + "5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Aanpassingen opslaan", + "4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Het downloaden is voltooid", + "348cc5d553b18e862eb1c1770e5636f6b05ba130": "Er is een fout opgetreden", + "4f8b2bb476981727ab34ed40fde1218361f92c45": "Details", + "e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Er is een fout opgetreden:", + "77b0c73840665945b25bd128709aa64c8f017e1c": "Gestart om:", + "08ff9375ec078065bcdd7637b7ea65fce2979266": "Afgerond om:", + "ad127117f9471612f47d01eae09709da444a36a4": "Bestandspad(en):", + "e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Mijn abonnementen", + "807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Kanalen", + "47546e45bbb476baaaad38244db444c427ddc502": "Afspeellijsten", + "29b89f751593e1b347eef103891b7a1ff36ec03f": "De naam is niet beschikbaar omdat het kanaal nog wordt opgehaald.", + "4636cd4a1379c50d471e98786098c4d39e1e82ad": "Je hebt geen abonnementen.", + "2e0a410652cb07d069f576b61eab32586a18320d": "De naam is niet beschikbaar omdat de afspeellijst nog wordt opgehaald.", + "587b57ced54965d8874c3fd0e9dfedb987e5df04": "Je hebt geen abonnementen.", + "82421c3e46a0453a70c42900eab51d58d79e6599": "Algemeen", + "0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Downloader", + "d5f69691f9f05711633128b5a3db696783266b58": "Diversen", + "bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Geavanceerd", + "4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Gebruikers", + "eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Logboeken", + "fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Close} false {Cancel} other {otha}}", + "54c512cca1923ab72faf1a0bd98d3d172469629a": "De url waarvan deze app wordt geladen, zonder het poortnummer.", + "cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Poort", + "22e8f1d0423a3b784fe40fab187b92c06541b577": "Het gewenste poortnummer (standaard: 17442).", + "d4477669a560750d2064051a510ef4d7679e2f3e": "Meerdere gebruikers", + "2eb03565fcdce7a7a67abc277a936a32fcf51557": "Gebruikersbasispad", + "a64505c41150663968e277ec9b3ddaa5f4838798": "Het basispad voor gebruikers en hun gedownloade video's.", + "4e3120311801c4acd18de7146add2ee4a4417773": "Abonnementen toestaan", + "4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Abonnementenbasispad", + "bc9892814ee2d119ae94378c905ea440a249b84a": "Het basispad voor video's van afspeellijsten en kanalen uit je abonnementen. Dit is relatief aan YTDL-Material's hoofdmap.", + "5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Controletussenpoos", + "0f56a7449b77630c114615395bbda4cab398efd8": "In seconden (alleen cijfers).", + "13759b09a7f4074ceee8fa2f968f9815fdf63295": "Soms worden nieuwe video's gedownload voordat ze volledig verwerkt zijn. Met deze instelling wordt de volgende dag gecontroleerd of er een hogere kwaliteit beschikbaar is.", + "3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Nieuwe uploads opnieuw downloaden", + "27a56aad79d8b61269ed303f11664cc78bcc2522": "Thema", + "ff7cee38a2259526c519f878e71b964f41db4348": "Standaard", + "7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Themawijziging toestaan", + "fe46ccaae902ce974e2441abe752399288298619": "Taal", + "ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Audiopad", + "c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Het pad voor audiodownloads. Dit is relatief aan YTDL-Material's hoofdmap.", + "46826331da1949bd6fb74624447057099c9d20cd": "Videomap", + "17c92e6d47a213fa95b5aa344b3f258147123f93": "Het pad voor videodownloads. Dit is relatief aan YTDL-Material's hoofdmap.", + "cfe829634b1144bc44b6d38cf5584ea65db9804f": "Standaard bestandsuitvoer", + "1148fd45287ff09955b938756bc302042bcb29c7": "Dit pad is relatief aan bovenstaande downloadpaden. Laat de extensie achterwege.", + "ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Algemene aanvullende opties", + "6b995e7130b4d667eaab6c5f61b362ace486d26d": "Algemene aanvullende opties voor downloads op de overzichtspagina. Scheidt deze met komma's: ,,", + "04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorieën", + "78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "youtube-dl-archief gebruiken", + "ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Miniatuurvoorbeeld opslaan", + "384de8f8f112c9e6092eb2698706d391553f3e8d": "Metagegevens opslaan", + "fb35145bfb84521e21b6385363d59221f436a573": "Alle downloads afbreken", + "61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Boventitel", + "78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Bestandsbeheer ingeschakeld", + "a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Downloadbeheer ingeschakeld", + "c33bd5392b39dbed36b8e5a1145163a15d45835f": "Kwaliteitskeuze toestaan", + "bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Downloadmodus", + "09d31c803a7252658694e1e3176b97f5655a3fe3": "Meerdere downloads toestaan", + "1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Openbare api gebruiken", + "23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Openbare api-sleutel", + "41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Documentatie bekijken", + "00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Let op: hiermee verwijder je je oude api-sleutel!", + "1b258b258b4cc475ceb2871305b61756b0134f4a": "Genereren", + "d5d7c61349f3b0859336066e6d453fc35d334fe5": "YouTube-api gebruiken", + "ce10d31febb3d9d60c160750570310f303a22c22": "YouTube-api-sleutel", + "8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Het genereren van een sleutel is eenvoudig.", + "d162f9fcd6a7187b391e004f072ab3da8377c47d": "Twitch-api gebruiken", + "8ae23bc4302a479f687f4b20a84c276182e2519c": "Twitch-api-sleutel", + "84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Ook wel de client-id.", + "5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Twitch-chatgesprekken automatisch downloaden", + "9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Klik hier", + "7f09776373995003161235c0c8d02b7f91dbc4df": "om de officiële Chrome-extensie van YouTubeDL-Material te downloaden.", + "5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Hiervoor dien je de extensie handmatig te laden en de frontend-url op te geven in de instellingen.", + "9a2ec6da48771128384887525bdcac992632c863": "om de officiële Firefox-extensie van YouTubeDL-Material te installeren.", + "eb81be6b49e195e5307811d1d08a19259d411f37": "Uitgebreide installatiehandleiding.", + "cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Je hoeft alleen de frontend-url op te geven in de instellingen.", + "61b81b11aad0b9d970ece2fce18405f07eac69c2": "Sleep de link naar je bladwijzers en klaar is Kees! Ga vervolgens naar een YouTube-video en klik op de bladwijzer.", + "c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Audio-bookmarklet genereren", + "ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Kies een downloader", + "5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Standaard downloadagent gebruiken", + "c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Kies een downloadagent", + "0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Logniveau", + "db6c192032f4cab809aad35215f0aa4765761897": "Inlogverloopdatum", + "dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Geavanceerd downloaden toestaan", + "431e5f3a0dde88768d1074baedd65266412b3f02": "Cookies gebruiken", + "80651a7ad1229ea6613557d3559f702cfa5aecf5": "Cookies instellen", + "37224420db54d4bc7696f157b779a7225f03ca9d": "Gebruikersregistratie toestaan", + "fa548cee6ea11c160a416cac3e6bdec0363883dc": "Authenticatiemethode", + "4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Intern", + "e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP", + "1db9789b93069861019bd0ccaa5d4706b00afc61": "LDAP-url", + "f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Bind DN", + "080cc6abcba236390fc22e79792d0d3443a3bd2a": "Bind-inloggegevens", + "cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Zoekdatabank", + "e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Zoekfilter", + "cec82c0a545f37420d55a9b6c45c20546e82f94e": "Over YouTubeDL-Material", + "199c17e5d6a419313af3c325f06dcbb9645ca618": "is een opensource YouTube-downloader, gebouwd volgens Google's Material Design-specificaties. Je kunt naadloos je favoriete video's downloaden als audio- of videobestanden of abonneren op je favoriete kanalen of afspeellijsten om altijd de nieuwste video's binnen te halen.", + "bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "bevat een aantal handige functies, zoals een uitgebreide api, Docker-ondersteuning en is volledig vertaalbaar. Meer functies zijn te vinden op onze GitHub-pagina (klik op het GitHub-pictogram).", + "a45e3b05f0529dc5246d70ef62304c94426d4c81": "Geïnstalleerde versie:", + "b33536f59b94ec935a16bd6869d836895dc5300c": "Heb je een bug aangetroffen of een idee?", + "e1f398f38ff1534303d4bb80bd6cece245f24016": "om een 'issue' te openen!", + "e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Bezig met controleren op updates...", + "a16e92385b4fd9677bb830a4b796b8b79c113290": "Update beschikbaar", + "189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Je kunt de update installeren via het instellingenmenu.", + "1372e61c5bd06100844bd43b98b016aabc468f62": "Kies een versie:", + "1f6d14a780a37a97899dc611881e6bc971268285": "Delen toestaan", + "6580b6a950d952df847cb3d8e7176720a740adc8": "Tijdstempel gebruiken", + "4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "seconden", + "3a6e5a6aa78ca864f6542410c5dafb6334538106": "Kopiëren naar klembord", + "a249a5ae13e0835383885aaf697d2890cc3e53e9": "Afspeellijst delen", + "15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Video delen", + "1d540dcd271b316545d070f9d182c372d923aadd": "Audio delen", + "a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Sessie-id:", + "b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Alle downloads wissen", + "eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(huidig)", + "7117fc42f860e86d983bfccfcf2654e5750f3406": "Geen downloads beschikbaar!", + "42ff677ec14f111e88bd6cdd30145378e994d1bf": "Mijn profiel", + "bb694b49d408265c91c62799c2b3a7e3151c824d": "Uitloggen", + "ac9d09de42edca1296371e4d801349c9096ac8de": "UID:", + "a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Aangemaakt:", + "fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Je bent niet ingelogd.", + "a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Beheerdersaccount aanmaken", + "2d2adf3ca26a676bca2269295b7455a26fd26980": "Er zijn geen beheerdersaccounts aangetroffen. Hiermee maak je een beheerdersaccount met wachtwoord aan - de gebruikersnaam is 'admin'.", + "70a67e04629f6d412db0a12d51820b480788d795": "Aanmaken", + "4d92a0395dd66778a931460118626c5794a3fc7a": "Gebruikers toevoegen", + "b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Rol aanpassen", + "746f64ddd9001ac456327cd9a3d5152203a4b93c": "Gebruikersnaam", + "52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Rol", + "59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Acties", + "2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gebruiker beheren", + "95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Gebruiker verwijderen", + "632e8b20c98e8eec4059a605a4b011bb476137af": "Gebruiker bewerken", + "29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Gebruikers-uid:", + "e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nieuw wachtwoord", + "6498fa1b8f563988f769654a75411bb8060134b9": "Nieuw wachtwoord instellen", + "544e09cdc99a8978f48521d45f62db0da6dcf742": "Standaardrol gebruiken", + "4f20f2d5a6882190892e58b85f6ccbedfa737952": "Ja", + "3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Nee", + "57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Rol beheren", + "5009630cdf32ab4f1c78737b9617b8773512c05a": "Aantal regels:", + "8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Logboeken wissen", + "24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Automatisch gegenereerd", + "ccf5ea825526ac490974336cb5c24352886abc07": "Bestand openen", + "5656a06f17c24b2d7eae9c221567b209743829a9": "Bestand openen op nieuw tabblad", + "a0720c36ee1057e5c54a86591b722485c62d7b1a": "Ga naar abonnement", + "94e01842dcee90531caa52e4147f70679bac87fe": "Verwijderen en opnieuw downloaden", + "2031adb51e07a41844e8ba7704b054e98345c9c1": "Permanent verwijderen", + "ddc31f2885b1b33a7651963254b0c197f2a64086": "Meer tonen.", + "56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Minder tonen.", + "2054791b822475aeaea95c0119113de3200f5e1c": "Duur:" +} \ No newline at end of file diff --git a/src/assets/i18n/messages.nl.xlf b/src/assets/i18n/messages.nl.xlf new file mode 100644 index 0000000..0049336 --- /dev/null +++ b/src/assets/i18n/messages.nl.xlf @@ -0,0 +1,2517 @@ + + + + + + About + Over + + src/app/app.component.html + 32 + + About menu label + + + Profile + Profiel + + src/app/app.component.html + 19 + + Profile menu label + + + Dark + Donker + + src/app/app.component.html + 23 + + + src/app/settings/settings.component.html + 75 + + Dark mode toggle label + + + Settings + Instellingen + + src/app/app.component.html + 28 + + + src/app/settings/settings.component.html + 1 + + Settings menu label + + + Home + Overzicht + + src/app/app.component.html + 43 + + Navigation menu Home Page title + + + Login + Inloggen + + src/app/app.component.html + 44 + + + src/app/components/login/login.component.html + 15 + + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 20 + + Navigation menu Login Page title + + + Subscriptions + Abonnementen + + src/app/app.component.html + 45 + + Navigation menu Subscriptions Page title + + + Downloads + Downloads + + src/app/app.component.html + 46 + + Navigation menu Downloads Page title + + + Only Audio + Alleen audio + + src/app/main/main.component.html + 60,61 + + Only Audio checkbox + + + Download + Downloaden + + src/app/main/main.component.html + 74,75 + + Main download button + + + Quality + Kwaliteit + + src/app/main/main.component.html + 19,20 + + Quality select label + + + Use URL + URL gebruiken + + src/app/main/main.component.html + 46 + + YT search Use URL button for searched video + + + View + Bekijken + + src/app/main/main.component.html + 50,51 + + YT search View button for searched video + + + Multi-download Mode + Meerdere video's downloaden + + src/app/main/main.component.html + 65,66 + + Multi-download Mode checkbox + + + Cancel + Afbreken + + src/app/main/main.component.html + 79,80 + + Cancel download button + + + Advanced + Geavanceerd + + src/app/main/main.component.html + 91,92 + + Advanced download mode panel + + + Use custom args + Aanvullende opties toekennen + + src/app/main/main.component.html + 105,106 + + Use custom args checkbox + + + Custom args + Aanvullende opties + + src/app/main/main.component.html + 110 + + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 57 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 44 + + Custom args placeholder + + + No need to include URL, just everything after. Args are delimited using two commas like so: ,, + Je hoeft alleen de aanvullende opties op te geven, dus niet de url. Je kunt de opties scheiden met twee komma's: ,, + + src/app/main/main.component.html + 113,114 + + Custom Args input hint + + + Use custom output + Aangepaste uitvoer gebruiken + + src/app/main/main.component.html + 121,122 + + Use custom output checkbox + + + Custom output + Aangepaste uitvoer + + src/app/main/main.component.html + 125 + + Custom output placeholder + + + Documentation + Documentatie + + src/app/main/main.component.html + 127 + + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 69 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 56 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 47 + + + src/app/settings/settings.component.html + 125 + + Youtube-dl output template documentation link + + + Path is relative to the config download path. Don't include extension. + Het pad is relatief aan het ingestelde downloadpad. Laat de extensie achterwege. + + src/app/main/main.component.html + 128 + + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 70 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 57 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 48 + + Custom Output input hint + + + Simulated command: + Geteste opdracht: + + src/app/main/main.component.html + 97,98 + + Simulated command label + + + Use authentication + Authenticatie gebruiken + + src/app/main/main.component.html + 135,136 + + Use authentication checkbox + + + Username + Gebruikersnaam + + src/app/main/main.component.html + 139 + + YT Username placeholder + + + Password + Wachtwoord + + src/app/main/main.component.html + 144 + + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 11 + + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 10 + + YT Password placeholder + + + Create a playlist + Afspeellijst maken + + src/app/create-playlist/create-playlist.component.html + 1 + + Create a playlist dialog title + + + Name + Naam + + src/app/create-playlist/create-playlist.component.html + 6 + + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 7 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 5 + + Playlist name placeholder + + + Type + Soort + + src/app/create-playlist/create-playlist.component.html + 11 + + Type select + + + Audio + Audio + + src/app/create-playlist/create-playlist.component.html + 12 + + Audio + + + Video + Video + + src/app/create-playlist/create-playlist.component.html + 13 + + Video + + + Audio files + Audiobestanden + + src/app/create-playlist/create-playlist.component.html + 19 + + Audio files title + + + Videos + Video's + + src/app/create-playlist/create-playlist.component.html + 20 + + + src/app/subscription/subscription/subscription.component.html + 29 + + Videos title + + + Subscribe to playlist or channel + Abonneren op afspeellijst of kanaal + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 1 + + Subscribe dialog title + + + URL + URL + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 8 + + + src/app/settings/settings.component.html + 18 + + Subscription URL input placeholder + + + The playlist or channel URL + De url van de afspeellijst of het kanaal + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 9 + + Subscription URL input hint + + + Custom name + Aangepaste naam + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 19 + + Subscription custom name placeholder + + + Download all uploads + Alle uploads downloaden + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 23 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 10 + + Download all uploads subscription setting + + + Max quality + Maximumkwaliteit + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 40 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 32 + + Max quality placeholder + + + Audio-only mode + Audiomodus + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 47 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 27 + + Streaming-only mode + + + Streaming-only mode + Streamingmodus + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 52 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 39 + + Streaming-only mode + + + These are added after the standard args. + Deze worden toegevoegd ná de standaardopties. + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 60 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 47 + + Custom args hint + + + Custom file output + Aangepaste bestandsuitvoer + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 66 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 53 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 44 + + Subscription custom file output placeholder + + + Cancel + Annuleren + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 79 + + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 84 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 66 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 54 + + + src/app/components/modify-users/modify-users.component.html + 61 + + Subscribe cancel button + + + Subscribe + Abonneren + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 81 + + Subscribe button + + + Download videos uploaded in the last + Video's downloaden die geüpload zijn in de afgelopen + + src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html + 26 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 13 + + Download time range prefix + + + Type: + Soort: + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 5 + + Subscription type property + + + URL: + URL: + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 9 + + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 9 + + Subscription URL property + + + ID: + ID: + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 13 + + + src/app/file-card/file-card.component.html + 7 + + + src/app/download-item/download-item.component.html + 4 + + Subscription ID property + + + Close + Sluiten + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 23 + + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 35 + + + src/app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 17 + + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 18 + + + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 40 + + + src/app/dialogs/about-dialog/about-dialog.component.html + 59 + + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 30 + + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 27 + + + src/app/components/manage-user/manage-user.component.html + 30 + + + src/app/components/manage-role/manage-role.component.html + 18 + + Close subscription info button + + + Export Archive + Archief exporteren + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 24 + + Export Archive button + + + Unsubscribe + De-abonneren + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 26 + + Unsubscribe button + + + (Paused) + (onderbroken) + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 1 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + + src/app/subscriptions/subscriptions.component.html + 12 + + + src/app/subscriptions/subscriptions.component.html + 31 + + + src/app/subscription/subscription/subscription.component.html + 5 + + Paused suffix + + + Archive: + Archief: + + src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.html + 17 + + Subscription ID property + + + Name: + Naam: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 5 + + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 6 + + Video name property + + + Uploader: + Uploader: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 13 + + Video ID property + + + File size: + Bestandsgrootte: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 17 + + Video file size property + + + Path: + Pad: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 21 + + Video path property + + + Upload Date: + Uploaddatum: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 25 + + Video upload date property + + + Category: + Categorie: + + src/app/dialogs/video-info-dialog/video-info-dialog.component.html + 29 + + Category property + + + Modify youtube-dl args + youtube-dl-opties aanpassen + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 1 + + Modify args title + + + Simulated new args + Geteste nieuwe aanvullende opties + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 8 + + Simulated args title + + + Add an arg + Optie toevoegen + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 34 + + Add arg card title + + + Search by category + Zoeken op categorie + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 60 + + Search args by category button + + + Use arg value + Optiewaarde gebruiken + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 64 + + Use arg value checkbox + + + Add arg + Optie toevoegen + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 73 + + Search args by category button + + + Modify + Aanpassen + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 85 + + Arg modifier modify button + + + Arg value + Optiewaarde + + src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html + 68 + + Arg value placeholder + + + Updater + Updater + + src/app/dialogs/update-progress-dialog/update-progress-dialog.component.html + 1 + + Update progress dialog title + + + Register a user + Gebruikersregistratie + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 1 + + Register user dialog title + + + User name + Gebruikersnaam + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 6 + + User name placeholder + + + Register + Registreren + + src/app/dialogs/add-user-dialog/add-user-dialog.component.html + 17 + + + src/app/components/login/login.component.html + 35 + + Register user button + + + Upload new cookies + Nieuwe cookies uploaden + + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 1 + + Cookies uploader dialog title + + + NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user. + Let op: de nieuwe cookies overschrijven de oude. Daarnaast zijn de cookies procesgebonden en niet gebruikersgebonden. + + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 20 + + Cookies upload warning + + + Drag and Drop + Slepen-en-neerzetten + + src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html + 11 + + Drag and Drop + + + Modify playlist + Afspeellijst aanpassen + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 1 + + Modify playlist dialog title + + + Add content + Inhoud toevoegen + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 19 + + Add content + + + Save + Opslaan + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 37 + + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 68 + + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 56 + + + src/app/settings/settings.component.html + 416 + + + src/app/components/modify-users/modify-users.component.html + 58 + + Save + + + Normal order + Normale volgorde + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 13 + + Normal order + + + Reverse order + Omgekeerde volgorde + + src/app/dialogs/modify-playlist/modify-playlist.component.html + 14 + + Reverse order + + + My videos + Mijn video's + + src/app/components/recent-videos/recent-videos.component.html + 20 + + My videos title + + + Search + Zoeken + + src/app/components/recent-videos/recent-videos.component.html + 24 + + + src/app/components/modify-users/modify-users.component.html + 7 + + + src/app/subscription/subscription/subscription.component.html + 33 + + Files search placeholder + + + No videos found. + Geen video's gevonden. + + src/app/components/recent-videos/recent-videos.component.html + 38 + + No videos found + + + Editing + Bewerken + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 1 + + Edit subscription dialog title prefix + + + Paused + Onderbroken + + src/app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component.html + 7 + + Paused subscription setting + + + Editing category + Categorie bewerken + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 1 + + Editing category dialog title + + + Rules + Regels + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 10 + + Rules + + + Add new rule + Regel toevoegen + + src/app/dialogs/edit-category-dialog/edit-category-dialog.component.html + 39 + + Add new rule tooltip + + + Download Twitch Chat + Twitch-chatgesprek downloaden + + src/app/components/twitch-chat/twitch-chat.component.html + 10 + + Download Twitch Chat button + + + Edit + Aanpassen + + src/app/file-card/file-card.component.html + 19 + + + src/app/components/unified-file-card/unified-file-card.component.html + 37 + + Playlist edit button + + + Delete + Verwijderen + + src/app/file-card/file-card.component.html + 20 + + + src/app/file-card/file-card.component.html + 25 + + + src/app/components/unified-file-card/unified-file-card.component.html + 33 + + + src/app/components/unified-file-card/unified-file-card.component.html + 39 + + Delete playlist + + + Info + Informatie + + src/app/file-card/file-card.component.html + 24 + + + src/app/components/unified-file-card/unified-file-card.component.html + 24 + + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 7 + + Video info button + + + Count: + Aantal: + + src/app/file-card/file-card.component.html + 8 + + Playlist video count + + + Delete and blacklist + Verwijderen en op zwarte lijst plaatsen + + src/app/file-card/file-card.component.html + 26 + + + src/app/components/unified-file-card/unified-file-card.component.html + 34 + + Delete and blacklist video button + + + views + weergaven + + src/app/player/player.component.html + 15 + + View count label + + + Save changes + Aanpassingen opslaan + + src/app/player/player.component.html + 59 + + Playlist save changes button + + + The download was successful + Het downloaden is voltooid + + src/app/download-item/download-item.component.html + 8 + + + src/app/download-item/download-item.component.html + 8 + + download successful tooltip + + + An error has occurred + Er is een fout opgetreden + + src/app/download-item/download-item.component.html + 9 + + + src/app/download-item/download-item.component.html + 9 + + download error tooltip + + + Details + Details + + src/app/download-item/download-item.component.html + 18 + + Details + + + An error has occurred: + Er is een fout opgetreden: + + src/app/download-item/download-item.component.html + 27 + + Error label + + + Download start: + Gestart om: + + src/app/download-item/download-item.component.html + 32 + + Download start label + + + Download end: + Afgerond om: + + src/app/download-item/download-item.component.html + 35 + + Download end label + + + File path(s): + Bestandspad(en): + + src/app/download-item/download-item.component.html + 38 + + File path(s) label + + + Your subscriptions + Mijn abonnementen + + src/app/subscriptions/subscriptions.component.html + 3 + + Subscriptions title + + + Channels + Kanalen + + src/app/subscriptions/subscriptions.component.html + 8 + + Subscriptions channels title + + + Playlists + Afspeellijsten + + src/app/subscriptions/subscriptions.component.html + 27 + + Subscriptions playlists title + + + Name not available. Channel retrieval in progress. + De naam is niet beschikbaar omdat het kanaal nog wordt opgehaald. + + src/app/subscriptions/subscriptions.component.html + 14 + + Subscription playlist not available text + + + You have no channel subscriptions. + Je hebt geen abonnementen. + + src/app/subscriptions/subscriptions.component.html + 24 + + No channel subscriptions text + + + Name not available. Playlist retrieval in progress. + De naam is niet beschikbaar omdat de afspeellijst nog wordt opgehaald. + + src/app/subscriptions/subscriptions.component.html + 33 + + Subscription playlist not available text + + + You have no playlist subscriptions. + Je hebt geen abonnementen. + + src/app/subscriptions/subscriptions.component.html + 43 + + No playlist subscriptions text + + + Main + Algemeen + + src/app/settings/settings.component.html + 12 + + Main settings label + + + Downloader + Downloader + + src/app/settings/settings.component.html + 102 + + Downloader settings label + + + Extra + Diversen + + src/app/settings/settings.component.html + 182 + + Extra settings label + + + Advanced + Geavanceerd + + src/app/settings/settings.component.html + 281 + + Host settings label + + + Users + Gebruikers + + src/app/settings/settings.component.html + 355 + + + src/app/settings/settings.component.html + 355 + + Users settings label + + + Logs + Logboeken + + src/app/settings/settings.component.html + 403 + + + src/app/settings/settings.component.html + 403 + + Logs settings label + + + {VAR_SELECT, select, true {Close} false {Cancel} other {otha}} + {VAR_SELECT, select, true {Close} false {Cancel} other {otha}} + + src/app/settings/settings.component.html + 419 + + Settings cancel and close button + + + URL this app will be accessed from, without the port. + De url waarvan deze app wordt geladen, zonder het poortnummer. + + src/app/settings/settings.component.html + 19 + + URL setting input hint + + + Port + Poort + + src/app/settings/settings.component.html + 24 + + Port input placeholder + + + The desired port. Default is 17442. + Het gewenste poortnummer (standaard: 17442). + + src/app/settings/settings.component.html + 25 + + Port setting input hint + + + Multi-user mode + Meerdere gebruikers + + src/app/settings/settings.component.html + 34 + + Multi user mode setting + + + Users base path + Gebruikersbasispad + + src/app/settings/settings.component.html + 38 + + Users base path placeholder + + + Base path for users and their downloaded videos. + Het basispad voor gebruikers en hun gedownloade video's. + + src/app/settings/settings.component.html + 39 + + Users base path hint + + + Allow subscriptions + Abonnementen toestaan + + src/app/settings/settings.component.html + 48 + + Allow subscriptions setting + + + Subscriptions base path + Abonnementenbasispad + + src/app/settings/settings.component.html + 52 + + Subscriptions base path input setting placeholder + + + Base path for videos from your subscribed channels and playlists. It is relative to YTDL-Material's root folder. + Het basispad voor video's van afspeellijsten en kanalen uit je abonnementen. Dit is relatief aan YTDL-Material's hoofdmap. + + src/app/settings/settings.component.html + 53 + + Subscriptions base path setting input hint + + + Check interval + Controletussenpoos + + src/app/settings/settings.component.html + 58 + + Check interval input setting placeholder + + + Unit is seconds, only include numbers. + In seconden (alleen cijfers). + + src/app/settings/settings.component.html + 59 + + Check interval setting input hint + + + Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day. + Soms worden nieuwe video's gedownload voordat ze volledig verwerkt zijn. Met deze instelling wordt de volgende dag gecontroleerd of er een hogere kwaliteit beschikbaar is. + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads tooltip + + + Redownload fresh uploads + Nieuwe uploads opnieuw downloaden + + src/app/settings/settings.component.html + 63 + + Redownload fresh uploads + + + Theme + Thema + + src/app/settings/settings.component.html + 72 + + Theme select label + + + Default + Standaard + + src/app/settings/settings.component.html + 74 + + Default theme label + + + Allow theme change + Themawijziging toestaan + + src/app/settings/settings.component.html + 80 + + Allow theme change setting + + + Language + Taal + + src/app/settings/settings.component.html + 89 + + Language select label + + + Audio folder path + Audiopad + + src/app/settings/settings.component.html + 109 + + Audio folder path input placeholder + + + Path for audio only downloads. It is relative to YTDL-Material's root folder. + Het pad voor audiodownloads. Dit is relatief aan YTDL-Material's hoofdmap. + + src/app/settings/settings.component.html + 110 + + Aduio path setting input hint + + + Video folder path + Videomap + + src/app/settings/settings.component.html + 116 + + Video folder path input placeholder + + + Path for video downloads. It is relative to YTDL-Material's root folder. + Het pad voor videodownloads. Dit is relatief aan YTDL-Material's hoofdmap. + + src/app/settings/settings.component.html + 117 + + Video path setting input hint + + + Default file output + Standaard bestandsuitvoer + + src/app/settings/settings.component.html + 123 + + Default file output placeholder + + + Path is relative to the above download paths. Don't include extension. + Dit pad is relatief aan bovenstaande downloadpaden. Laat de extensie achterwege. + + src/app/settings/settings.component.html + 126 + + Custom Output input hint + + + Global custom args + Algemene aanvullende opties + + src/app/settings/settings.component.html + 133 + + Custom args input placeholder + + + Global custom args for downloads on the home page. Args are delimited using two commas like so: ,, + Algemene aanvullende opties voor downloads op de overzichtspagina. Scheidt deze met komma's: ,, + + src/app/settings/settings.component.html + 134 + + Custom args setting input hint + + + Categories + Categorieën + + src/app/settings/settings.component.html + 144 + + Categories + + + Use youtube-dl archive + youtube-dl-archief gebruiken + + src/app/settings/settings.component.html + 163 + + Use youtubedl archive setting + + + Include thumbnail + Miniatuurvoorbeeld opslaan + + src/app/settings/settings.component.html + 167 + + Include thumbnail setting + + + Include metadata + Metagegevens opslaan + + src/app/settings/settings.component.html + 171 + + Include metadata setting + + + Kill all downloads + Alle downloads afbreken + + src/app/settings/settings.component.html + 175 + + Kill all downloads button + + + Top title + Boventitel + + src/app/settings/settings.component.html + 188 + + Top title input placeholder + + + File manager enabled + Bestandsbeheer ingeschakeld + + src/app/settings/settings.component.html + 193 + + File manager enabled setting + + + Downloads manager enabled + Downloadbeheer ingeschakeld + + src/app/settings/settings.component.html + 196 + + Downloads manager enabled setting + + + Allow quality select + Kwaliteitskeuze toestaan + + src/app/settings/settings.component.html + 199 + + Allow quality seelct setting + + + Download only mode + Downloadmodus + + src/app/settings/settings.component.html + 202 + + Download only mode setting + + + Allow multi-download mode + Meerdere downloads toestaan + + src/app/settings/settings.component.html + 205 + + Allow multi-download mode setting + + + Enable Public API + Openbare api gebruiken + + src/app/settings/settings.component.html + 213 + + Enable Public API key setting + + + Public API Key + Openbare api-sleutel + + src/app/settings/settings.component.html + 218 + + Public API Key setting placeholder + + + View documentation + Documentatie bekijken + + src/app/settings/settings.component.html + 219 + + View API docs setting hint + + + This will delete your old API key! + Let op: hiermee verwijder je je oude api-sleutel! + + src/app/settings/settings.component.html + 223 + + delete api key tooltip + + + Generate + Genereren + + src/app/settings/settings.component.html + 223 + + Generate key button + + + Use YouTube API + YouTube-api gebruiken + + src/app/settings/settings.component.html + 232 + + Use YouTube API setting + + + Youtube API Key + YouTube-api-sleutel + + src/app/settings/settings.component.html + 236 + + Youtube API Key setting placeholder + + + Generating a key is easy! + Het genereren van een sleutel is eenvoudig. + + src/app/settings/settings.component.html + 237 + + + src/app/settings/settings.component.html + 249 + + Youtube API Key setting hint + + + Use Twitch API + Twitch-api gebruiken + + src/app/settings/settings.component.html + 241 + + Use Twitch API setting + + + Twitch API Key + Twitch-api-sleutel + + src/app/settings/settings.component.html + 248 + + Twitch API Key setting placeholder + + + Also known as a Client ID. + Ook wel de client-id. + + src/app/settings/settings.component.html + 249 + + Twitch API Key setting hint AKA preamble + + + Auto-download Twitch Chat + Twitch-chatgesprekken automatisch downloaden + + src/app/settings/settings.component.html + 244 + + Auto download Twitch Chat setting + + + Click here + Klik hier + + src/app/settings/settings.component.html + 259 + + + src/app/settings/settings.component.html + 265 + + + src/app/dialogs/about-dialog/about-dialog.component.html + 25 + + Chrome ext click here + + + to download the official YoutubeDL-Material Chrome extension manually. + om de officiële Chrome-extensie van YouTubeDL-Material te downloaden. + + src/app/settings/settings.component.html + 259 + + Chrome click here suffix + + + You must manually load the extension and modify the extension's settings to set the frontend URL. + Hiervoor dien je de extensie handmatig te laden en de frontend-url op te geven in de instellingen. + + src/app/settings/settings.component.html + 260 + + Chrome setup suffix + + + to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page. + om de officiële Firefox-extensie van YouTubeDL-Material te installeren. + + src/app/settings/settings.component.html + 265 + + Firefox click here suffix + + + Detailed setup instructions. + Uitgebreide installatiehandleiding. + + src/app/settings/settings.component.html + 266 + + Firefox setup prefix link + + + Not much is required other than changing the extension's settings to set the frontend URL. + Je hoeft alleen de frontend-url op te geven in de instellingen. + + src/app/settings/settings.component.html + 266 + + Firefox setup suffix + + + Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark. + Sleep de link naar je bladwijzers en klaar is Kees! Ga vervolgens naar een YouTube-video en klik op de bladwijzer. + + src/app/settings/settings.component.html + 271 + + Bookmarklet instructions + + + Generate 'audio only' bookmarklet + Audio-bookmarklet genereren + + src/app/settings/settings.component.html + 272 + + Generate audio only bookmarklet checkbox + + + Select a downloader + Kies een downloader + + src/app/settings/settings.component.html + 287 + + Default downloader select label + + + Use default downloading agent + Standaard downloadagent gebruiken + + src/app/settings/settings.component.html + 295 + + Use default downloading agent setting + + + Select a download agent + Kies een downloadagent + + src/app/settings/settings.component.html + 299 + + Custom downloader select label + + + Log Level + Logniveau + + src/app/settings/settings.component.html + 313 + + Log Level label + + + Login expiration + Inlogverloopdatum + + src/app/settings/settings.component.html + 325 + + Login expiration select label + + + Allow advanced download + Geavanceerd downloaden toestaan + + src/app/settings/settings.component.html + 336 + + Allow advanced downloading setting + + + Use Cookies + Cookies gebruiken + + src/app/settings/settings.component.html + 344 + + Use cookies setting + + + Set Cookies + Cookies instellen + + src/app/settings/settings.component.html + 345 + + Set cookies button + + + Allow user registration + Gebruikersregistratie toestaan + + src/app/settings/settings.component.html + 359 + + Allow registration setting + + + Auth method + Authenticatiemethode + + src/app/settings/settings.component.html + 363 + + Auth method select + + + Internal + Intern + + src/app/settings/settings.component.html + 365 + + Internal auth method + + + LDAP + LDAP + + src/app/settings/settings.component.html + 368 + + LDAP auth method + + + LDAP URL + LDAP-url + + src/app/settings/settings.component.html + 375 + + LDAP URL + + + Bind DN + Bind DN + + src/app/settings/settings.component.html + 380 + + Bind DN + + + Bind Credentials + Bind-inloggegevens + + src/app/settings/settings.component.html + 385 + + Bind Credentials + + + Search Base + Zoekdatabank + + src/app/settings/settings.component.html + 390 + + Search Base + + + Search Filter + Zoekfilter + + src/app/settings/settings.component.html + 395 + + Search Filter + + + About YoutubeDL-Material + Over YouTubeDL-Material + + src/app/dialogs/about-dialog/about-dialog.component.html + 1 + + About dialog title + + + is an open-source YouTube downloader built under Google's Material Design specifications. You can seamlessly download your favorite videos as video or audio files, and even subscribe to your favorite channels and playlists to keep updated with their new videos. + is een opensource YouTube-downloader, gebouwd volgens Google's Material Design-specificaties. Je kunt naadloos je favoriete video's downloaden als audio- of videobestanden of abonneren op je favoriete kanalen of afspeellijsten om altijd de nieuwste video's binnen te halen. + + src/app/dialogs/about-dialog/about-dialog.component.html + 12 + + About first paragraph + + + has some awesome features included! An extensive API, Docker support, and localization (translation) support. Read up on all the supported features by clicking on the GitHub icon above. + bevat een aantal handige functies, zoals een uitgebreide api, Docker-ondersteuning en is volledig vertaalbaar. Meer functies zijn te vinden op onze GitHub-pagina (klik op het GitHub-pictogram). + + src/app/dialogs/about-dialog/about-dialog.component.html + 15 + + About second paragraph + + + Installed version: + Geïnstalleerde versie: + + src/app/dialogs/about-dialog/about-dialog.component.html + 20 + + Version label + + + Found a bug or have a suggestion? + Heb je een bug aangetroffen of een idee? + + src/app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug prefix + + + to create an issue! + om een 'issue' te openen! + + src/app/dialogs/about-dialog/about-dialog.component.html + 25 + + About bug suffix + + + Checking for updates... + Bezig met controleren op updates... + + src/app/dialogs/about-dialog/about-dialog.component.html + 20 + + Checking for updates text + + + Update available + Update beschikbaar + + src/app/dialogs/about-dialog/about-dialog.component.html + 21 + + View latest update + + + You can update from the settings menu. + Je kunt de update installeren via het instellingenmenu. + + src/app/dialogs/about-dialog/about-dialog.component.html + 21 + + Update through settings menu hint + + + Select a version: + Kies een versie: + + src/app/updater/updater.component.html + 3 + + Select a version + + + Enable sharing + Delen toestaan + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 10 + + Enable sharing checkbox + + + Use timestamp + Tijdstempel gebruiken + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 13 + + Use timestamp + + + Seconds + seconden + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 15 + + Seconds + + + Copy to clipboard + Kopiëren naar klembord + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 24 + + Copy to clipboard button + + + Share playlist + Afspeellijst delen + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 2 + + Share playlist dialog title + + + Share video + Video delen + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 3 + + Share video dialog title + + + Share audio + Audio delen + + src/app/dialogs/share-media-dialog/share-media-dialog.component.html + 4 + + Share audio dialog title + + + Session ID: + Sessie-id: + + src/app/components/downloads/downloads.component.html + 5 + + Session ID + + + Clear all downloads + Alle downloads wissen + + src/app/components/downloads/downloads.component.html + 18 + + clear all downloads action button + + + (current) + (huidig) + + src/app/components/downloads/downloads.component.html + 6 + + Current session + + + No downloads available! + Geen downloads beschikbaar! + + src/app/components/downloads/downloads.component.html + 25 + + No downloads label + + + Your Profile + Mijn profiel + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 1 + + User profile dialog title + + + Logout + Uitloggen + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 28 + + Logout + + + UID: + UID: + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 9 + + UID + + + Created: + Aangemaakt: + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 12 + + Created + + + You are not logged in. + Je bent niet ingelogd. + + src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html + 19 + + Not logged in notification + + + Create admin account + Beheerdersaccount aanmaken + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 1 + + Create admin account dialog title + + + No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'. + Er zijn geen beheerdersaccounts aangetroffen. Hiermee maak je een beheerdersaccount met wachtwoord aan - de gebruikersnaam is 'admin'. + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 5 + + No default admin detected explanation + + + Create + Aanmaken + + src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html + 17 + + Create + + + Add Users + Gebruikers toevoegen + + src/app/components/modify-users/modify-users.component.html + 90 + + Add users button + + + Edit Role + Rol aanpassen + + src/app/components/modify-users/modify-users.component.html + 95 + + Edit role + + + User name + Gebruikersnaam + + src/app/components/modify-users/modify-users.component.html + 17 + + Username users table header + + + Role + Rol + + src/app/components/modify-users/modify-users.component.html + 35 + + Role users table header + + + Actions + Acties + + src/app/components/modify-users/modify-users.component.html + 55 + + Actions users table header + + + Manage user + Gebruiker beheren + + src/app/components/modify-users/modify-users.component.html + 70 + + + src/app/components/manage-user/manage-user.component.html + 1 + + manage user action button tooltip + + + Delete user + Gebruiker verwijderen + + src/app/components/modify-users/modify-users.component.html + 73 + + delete user action button tooltip + + + Edit user + Gebruiker bewerken + + src/app/components/modify-users/modify-users.component.html + 66 + + edit user action button tooltip + + + User UID: + Gebruikers-uid: + + src/app/components/manage-user/manage-user.component.html + 4 + + User UID + + + New password + Nieuw wachtwoord + + src/app/components/manage-user/manage-user.component.html + 8 + + New password placeholder + + + Set new password + Nieuw wachtwoord instellen + + src/app/components/manage-user/manage-user.component.html + 10 + + Set new password + + + Use role default + Standaardrol gebruiken + + src/app/components/manage-user/manage-user.component.html + 19 + + Use role default + + + Yes + Ja + + src/app/components/manage-user/manage-user.component.html + 20 + + + src/app/components/manage-role/manage-role.component.html + 9 + + Yes + + + No + Nee + + src/app/components/manage-user/manage-user.component.html + 21 + + + src/app/components/manage-role/manage-role.component.html + 10 + + No + + + Manage role + Rol beheren + + src/app/components/manage-role/manage-role.component.html + 1 + + Manage role dialog title + + + Lines: + Aantal regels: + + src/app/components/logs-viewer/logs-viewer.component.html + 22 + + Label for lines select in logger view + + + Clear logs + Logboeken wissen + + src/app/components/logs-viewer/logs-viewer.component.html + 34 + + Clear logs button + + + Auto-generated + Automatisch gegenereerd + + src/app/components/unified-file-card/unified-file-card.component.html + 5 + + Auto-generated label + + + Open file + Bestand openen + + src/app/components/unified-file-card/unified-file-card.component.html + 18 + + Open file button + + + Open file in new tab + Bestand openen op nieuw tabblad + + src/app/components/unified-file-card/unified-file-card.component.html + 19 + + Open file in new tab + + + Go to subscription + Ga naar abonnement + + src/app/components/unified-file-card/unified-file-card.component.html + 25 + + Go to subscription menu item + + + Delete and redownload + Verwijderen en opnieuw downloaden + + src/app/components/unified-file-card/unified-file-card.component.html + 28 + + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 8 + + Delete and redownload subscription video button + + + Delete forever + Permanent verwijderen + + src/app/components/unified-file-card/unified-file-card.component.html + 31 + + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 9 + + Delete forever subscription video button + + + See more. + Meer tonen. + + src/app/components/see-more/see-more.component.html + 5,6 + + See more + + + See less. + Minder tonen. + + src/app/components/see-more/see-more.component.html + 8,9 + + See less + + + Length: + Duur: + + src/app/subscription/subscription-file-card/subscription-file-card.component.html + 3 + + Video duration label + + + + From 433d08e9dfdb641c111107408f8fba71fb5475ad Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 12 Feb 2021 21:20:48 -0700 Subject: [PATCH 111/250] Added ability to crop files Fixed bug in downloading playlists --- backend/app.js | 44 ++++++++++++++++++++++++++++---- backend/db.js | 7 ++++- src/app/main/main.component.css | 4 +++ src/app/main/main.component.html | 21 +++++++++++++-- src/app/main/main.component.ts | 14 +++++++++- src/app/posts.services.ts | 5 ++-- 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/backend/app.js b/backend/app.js index 9dbad30..dba7f14 100644 --- a/backend/app.js +++ b/backend/app.js @@ -853,7 +853,7 @@ async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvid let zipFolderPath = null; if (!fullPathProvided) { - zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath); + 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')); @@ -1155,7 +1155,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } // download file - youtubedl.exec(url, downloadConfig, {}, function(err, output) { + youtubedl.exec(url, downloadConfig, {}, async function(err, output) { if (download_checker) clearInterval(download_checker); // stops the download checker from running as the download finished (or errored) download['downloading'] = false; @@ -1227,8 +1227,12 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { const file_path = options.noRelativePath ? path.basename(full_file_path) : full_file_path.substring(fileFolderPath.length, full_file_path.length); const customPath = options.noRelativePath ? path.dirname(full_file_path).split(path.sep).pop() : null; + if (options.cropFileSettings) { + await cropFile(full_file_path, options.cropFileSettings.cropFileStart, options.cropFileSettings.cropFileEnd, ext); + } + // registers file in DB - file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category); + file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath, category, options.cropFileSettings); if (file_name) file_names.push(file_name); } @@ -1587,6 +1591,8 @@ async function getUrlInfos(urls) { }); } +// ffmpeg helper functions + async function convertFileToMp3(input_file, output_file) { logger.verbose(`Converting ${input_file} to ${output_file}...`); return new Promise(resolve => { @@ -1604,6 +1610,33 @@ async function convertFileToMp3(input_file, output_file) { }); } +async function cropFile(file_path, start, end, ext) { + return new Promise(resolve => { + const temp_file_path = `${file_path}.cropped${ext}`; + let base_ffmpeg_call = ffmpeg(file_path); + if (start) { + base_ffmpeg_call = base_ffmpeg_call.seekOutput(start); + } + if (end) { + base_ffmpeg_call = base_ffmpeg_call.duration(end - start); + } + base_ffmpeg_call + .on('end', () => { + logger.verbose(`Cropping for '${file_path}' complete.`); + fs.unlinkSync(file_path); + fs.moveSync(temp_file_path, file_path); + resolve(true); + }) + .on('error', (err, test, test2) => { + logger.error(`Failed to crop ${file_path}.`); + logger.error(err); + resolve(false); + }).save(temp_file_path); + }); +} + +// archive helper functions + 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 @@ -1908,7 +1941,8 @@ app.post('/api/tomp4', optionalJwt, async function(req, res) { youtubeUsername: req.body.youtubeUsername, youtubePassword: req.body.youtubePassword, ui_uid: req.body.ui_uid, - user: req.isAuthenticated() ? req.user.uid : null + user: req.isAuthenticated() ? req.user.uid : null, + cropFileSettings: req.body.cropFileSettings } const safeDownloadOverride = config_api.getConfigItem('ytdl_safe_download_override') || config_api.globalArgsRequiresSafeDownload(); @@ -2666,7 +2700,7 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => { for (let i = 0; i < fileNames.length; i++) { fileNames[i] = decodeURIComponent(fileNames[i]); } - file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided, req.body.uuid); + file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided, req.body.uuid || req.user.uid); if (!path.isAbsolute(file)) file = path.join(__dirname, file); } res.sendFile(file, function (err) { diff --git a/backend/db.js b/backend/db.js index 6d5cdb6..16a8d0c 100644 --- a/backend/db.js +++ b/backend/db.js @@ -15,7 +15,7 @@ function initialize(input_db, input_users_db, input_logger) { setLogger(input_logger); } -function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null, category = null) { +function 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); @@ -32,6 +32,11 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null, custo // if category exists, only include essential info if (category) file_object['category'] = {name: category['name'], uid: category['uid']}; + // modify duration + if (cropFileSettings) { + file_object['duration'] = (cropFileSettings.cropFileEnd || file_object.duration) - cropFileSettings.cropFileStart; + } + if (!sub) { if (multiUserMode) { const user_uid = multiUserMode.user; diff --git a/src/app/main/main.component.css b/src/app/main/main.component.css index 4ee7412..8411370 100644 --- a/src/app/main/main.component.css +++ b/src/app/main/main.component.css @@ -124,6 +124,10 @@ mat-form-field.mat-form-field { width: 100%; } +.advanced-input-time { + margin-left: 10px; +} + .edit-button { margin-left: 10px; top: -5px; diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 9cb905e..26b9da3 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -129,7 +129,7 @@
-
+
Use authentication @@ -139,11 +139,28 @@
-
+
+
+ + + Crop file + + + + + Seconds + +
+
+ + + Seconds + +
diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 30694bf..51a90ce 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -54,6 +54,9 @@ export class MainComponent implements OnInit { youtubeAuthEnabled = false; youtubeUsername = null; youtubePassword = null; + cropFile = false; + cropFileStart = null; + cropFileEnd = null; urlError = false; path = ''; url = ''; @@ -521,8 +524,17 @@ export class MainComponent implements OnInit { 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).subscribe(posts => { + customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid, cropFileSettings).subscribe(posts => { // update download object new_download.downloading = false; new_download.percent_complete = 100; diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 6657f00..a6c2954 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -183,7 +183,7 @@ export class PostsService implements CanActivate { } // 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) { + makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null, cropFileSettings = null) { return this.http.post(this.path + 'tomp4', {url: url, selectedHeight: selectedQuality, customQualityConfiguration: customQualityConfiguration, @@ -191,7 +191,8 @@ export class PostsService implements CanActivate { customOutput: customOutput, youtubeUsername: youtubeUsername, youtubePassword: youtubePassword, - ui_uid: ui_uid}, this.httpOptions); + ui_uid: ui_uid, + cropFileSettings: cropFileSettings}, this.httpOptions); } killAllDownloads() { From 023df9c29d673234d10796a80f4b099f86caaac5 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 12 Feb 2021 21:21:09 -0700 Subject: [PATCH 112/250] Fixed issue where playlists couldn't be favorited after downloading --- src/app/player/player.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html index 086adf6..7f8d928 100644 --- a/src/app/player/player.component.html +++ b/src/app/player/player.component.html @@ -8,7 +8,7 @@
-
+
@@ -29,7 +29,7 @@
- + From 669c87dd1bb375c586c26db5a00222cd6e73bbb7 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Fri, 12 Feb 2021 21:21:45 -0700 Subject: [PATCH 113/250] Removed unecessary suffix in crop file inputs --- src/app/main/main.component.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 26b9da3..f3a1090 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -152,13 +152,11 @@ - Seconds
- Seconds
From c660c284229be2c6f0132aa9c2ca8439d6871a17 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 22 Feb 2021 12:53:21 -0700 Subject: [PATCH 114/250] 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 9d09eeffe313a0c872fc78d89bffc57e1800c6bb Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 22 Feb 2021 12:54:28 -0700 Subject: [PATCH 115/250] Added maxbuffer option to subscriptions --- backend/subscriptions.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/subscriptions.js b/backend/subscriptions.js index b3e2f1e..3a6e0a3 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -92,7 +92,7 @@ async function getSubscriptionInfo(sub, user_uid = null) { } return new Promise(resolve => { - youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) { + youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, function(err, output) { if (debugMode) { logger.info('Subscribe: got info for subscription ' + sub.id); } @@ -292,7 +292,7 @@ async function getVideosForSub(sub, user_uid = null) { logger.verbose('Subscription: getting videos for subscription ' + sub.name); return new Promise(resolve => { - youtubedl.exec(sub.url, downloadConfig, {}, async function(err, output) { + youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, async function(err, output) { updateSubscriptionProperty(sub, {downloading: false}, user_uid); logger.verbose('Subscription: finished check for ' + sub.name); if (err && !output) { @@ -565,7 +565,7 @@ async function checkVideoIfBetterExists(file_obj, sub, user_uid) { const metric_to_compare = sub.type === 'audio' ? 'abr' : 'height'; if (output[metric_to_compare] > file_obj[metric_to_compare]) { // download new video as the simulated one is better - youtubedl.exec(file_obj['url'], downloadConfig, async (err, output) => { + youtubedl.exec(file_obj['url'], downloadConfig, {maxBuffer: Infinity}, async (err, output) => { if (err) { logger.verbose(`Failed to download better version of video ${file_obj['id']}`); } else if (output) { From f32b3947150f577e214d05bd9d67026612b22978 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Mon, 22 Feb 2021 12:55:30 -0700 Subject: [PATCH 116/250] Added maxBuffer option to all downloads --- backend/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index dba7f14..888ccf7 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1155,7 +1155,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { } // download file - youtubedl.exec(url, downloadConfig, {}, async function(err, output) { + youtubedl.exec(url, downloadConfig, {maxBuffer: Infinity}, async function(err, output) { if (download_checker) clearInterval(download_checker); // stops the download checker from running as the download finished (or errored) download['downloading'] = false; @@ -1569,7 +1569,7 @@ async function getUrlInfos(urls) { let startDate = Date.now(); let result = []; return new Promise(resolve => { - youtubedl.exec(urls.join(' '), ['--dump-json'], {}, (err, output) => { + youtubedl.exec(urls.join(' '), ['--dump-json'], {maxBuffer: Infinity}, (err, output) => { let new_date = Date.now(); let difference = (new_date - startDate)/1000; logger.debug(`URL info retrieval delay: ${difference} seconds.`); From 1f0153b17edae9000cb0b0f58d12d082853b8291 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Tue, 16 Mar 2021 20:06:05 -0600 Subject: [PATCH 117/250] 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 118/250] 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 119/250] 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 120/250] 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 121/250] 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 122/250] 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 123/250] 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 124/250] 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 125/250] 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 126/250] 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 127/250] 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 128/250] 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 129/250] 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 130/250] 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 131/250] 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 132/250] 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 133/250] 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 134/250] 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 135/250] 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 136/250] 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 137/250] 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 138/250] 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 139/250] 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 @@
-
+
-
- -