├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .prettierrc ├── .whitesource ├── LICENSE ├── README.md ├── assets ├── defaultsettings.png ├── english-uk-1.png ├── english-uk-2.png └── youtube.png ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── index.ts ├── types.ts └── upload.ts └── tsconfig.json /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: current 17 | - run: npm ci 18 | - name: Build 19 | run: npm run build 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | .dccache 106 | *.mp4 107 | test.js 108 | thumbnail.png 109 | yt-auth/ 110 | test 111 | 112 | # IDE 113 | .idea 114 | thumbnail.jpg 115 | script.js 116 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 4 7 | } -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fawaz Ahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Youtube Videos Uploader

2 | 3 |

4 | 5 | 6 | [![npm version](https://img.shields.io/npm/v/youtube-videos-uploader.svg?style=flat)](https://www.npmjs.com/package/youtube-videos-uploader) 7 | 8 | ------------ 9 | 10 | **In the name of God, who has guided me to do this work** 11 | 12 | **Note1:** In case you find any issue, please consider creating a PR to fix the issue.
13 | 14 | ### Features: 15 | - No upload Limits (50+ videos/day limit set by youtube for every channel) 16 | - Free & Easy to use 17 | 18 | ### Prerequisite: 19 | - Install [Nodejs Current Version](https://nodejs.org/en/#:~:text=Current) 20 | 21 | ### Installation: 22 | ```js 23 | npm i youtube-videos-uploader 24 | ``` 25 | 26 | ### Youtube Setup: 27 | 1. Go to your [Google Security settings](https://myaccount.google.com/security) and note down your recovery email and delete recovery phone from your google settings 28 | 2. Go to your [Youtube settings](https://studio.youtube.com/) and Setup your upload defaults Settings: 29 | 30 | 31 | ![Upload Defaults Settings](assets/defaultsettings.png) 32 | 33 | 34 | 35 | 36 | ### Usage: 37 | - #### Uploading a Video: 38 | 39 | ```js 40 | 41 | import { upload } from 'youtube-videos-uploader' //Typescript 42 | //OR 43 | const { upload } = require('youtube-videos-uploader'); //vanilla javascript 44 | 45 | // recoveryemail is optional, only required to bypass login with recovery email if prompted for confirmation 46 | const credentials = { email: 'Your Email', pass: 'Your Password', recoveryemail: 'Your Recovery Email' } 47 | 48 | // minimum required options to upload video 49 | const video1 = { path: 'video1.mp4', title: 'title 1', description: 'description 1' } 50 | 51 | const onVideoUploadSuccess = (videoUrl) => { 52 | // ..do something.. 53 | } 54 | // Extra options like tags, thumbnail, language, playlist etc 55 | const video2 = { path: 'video2.mp4', title: 'title 2', description: 'description 2', thumbnail:'thumbnail.png', language: 'english', tags: ['video', 'github'], playlist: 'playlist name', channelName: 'Channel Name', onSuccess:onVideoUploadSuccess, skipProcessingWait: true, onProgress: (progress) => { console.log('progress', progress) }, uploadAsDraft: false, isAgeRestriction: false, isNotForKid: false, publishType: 'PUBLIC', isChannelMonetized: false } 56 | 57 | 58 | // Returns uploaded video links in array 59 | upload (credentials, [video1, video2]).then(console.log) 60 | 61 | // OR 62 | // This package uses Puppeteer, you can also pass Puppeteer launch configuration 63 | upload (credentials, [video1, video2], {headless:false}).then(console.log) 64 | 65 | // Refer Puppeteer documentation for more launch configurations like proxy etc 66 | // https://pptr.dev/#?product=Puppeteer&version=main&show=api-puppeteerlaunchoptions 67 | ``` 68 | 69 |        **Output:** 70 | ```js 71 | [ 'https://youtu.be/fh2Kreex5Eg', 'https://youtu.be/fh2Krefx5Eg' ] 72 | ``` 73 | 74 | - #### Updating Metadata of a Youtube Video: 75 | 76 | ```js 77 | 78 | import { update } from 'youtube-videos-uploader' //Typescript 79 | //OR 80 | const { update } = require('youtube-videos-uploader'); //vanilla javascript 81 | 82 | const credentials = { email: 'Your Email', pass: 'Your Password', recoveryemail: 'Your Recovery Email' } 83 | 84 | const videoUpdate1 = { link: 'https://www.youtube.com/watch?v=w3jLJU7DT5E', title: 'Your New Title' } 85 | 86 | const onVideoUpdateSuccess = (videoUrl) => { 87 | // ..do something.. 88 | } 89 | // Extra options like tags, thumbnail, language, playlist etc 90 | const videoUpdate2 = { link: 'https://www.youtube.com/watch?v=w3jLJU7DT5E', title: 'title 2', description: 'description 2', thumbnail: 'thumbnail.png', language: 'english', tags: ['video', 'github'], replaceTags: ['mytag'], playlist: 'playlist name', channelName: 'Channel Name', publishType: 'unlisted', publishToSubscriptionFeedAndNotifySubscribers: false , onSuccess: onVideoUpdateSuccess } 91 | 92 | update(credentials, [videoUpdate1, videoUpdate2]).then(console.log) 93 | // OR 94 | update(credentials, [videoUpdate1, videoUpdate2], { headless: false }).then(console.log) 95 | 96 | ``` 97 | 98 | - #### Making a comment to youtube video: 99 | 100 | ```js 101 | 102 | import { comment } from 'youtube-videos-uploader' //Typescript 103 | //OR 104 | const { comment } = require('youtube-videos-uploader'); //vanilla javascript 105 | 106 | const credentials = { email: 'Your Email', pass: 'Your Password', recoveryemail: 'Your Recovery Email' } 107 | 108 | const comment1 = { link: 'https://www.youtube.com/watch?v=jEevRjRglFY', comment: 'Your comment', pin: false } 109 | 110 | comment(credentials, [comment1]).then(console.log) 111 | //OR 112 | comment(credentials, [comment1], {headless:false}).then(console.log) 113 | 114 | ``` 115 | 116 | ### Contributors 🎉: 117 | - [Pierre Miniggio( @pierreminiggio )](https://ggio.link/twitter) - For Adding [Youtube UI English Language Support](https://github.com/fawazahmed0/youtube-uploader/pull/16), [JSDoc](https://github.com/fawazahmed0/youtube-uploader/pull/18), [debug message](https://github.com/fawazahmed0/youtube-uploader/pull/34), [Cleanup](https://github.com/fawazahmed0/youtube-uploader/pull/67), [shorts link refactor](https://github.com/fawazahmed0/youtube-uploader/pull/140), [log handling](https://github.com/fawazahmed0/youtube-uploader/pull/144) and [close browser on error](https://github.com/fawazahmed0/youtube-uploader/pull/155) 118 | - [TentacleSama4254 ](https://github.com/TentacleSama4254) - For Adding [Thumbnail option](https://github.com/fawazahmed0/youtube-uploader/pull/22), fixing [tags error](https://github.com/fawazahmed0/youtube-uploader/pull/23), [TypeScript Rewrite, storing login session](https://github.com/fawazahmed0/youtube-uploader/pull/51), [video metadata update feature](https://github.com/fawazahmed0/youtube-uploader/pull/53) and [comments option](https://github.com/fawazahmed0/youtube-uploader/pull/58) 119 | - [Sai Charan](https://github.com/charan0017) - For [onSuccess Option](https://github.com/fawazahmed0/youtube-uploader/pull/32) 120 | - [Tue Nguyen](https://github.com/TueeNguyen) - For [Better error messages](https://github.com/fawazahmed0/youtube-uploader/pull/46) 121 | - [weizhiqimail](https://github.com/weizhiqimail) - For [Extra Debug messages](https://github.com/fawazahmed0/youtube-uploader/pull/47) 122 | - [DaddyFrosty](https://github.com/DaddyFrosty) - For [Path Escaping](https://github.com/fawazahmed0/youtube-uploader/pull/55), [Skip Processing wait](https://github.com/fawazahmed0/youtube-uploader/pull/57), [onProgress event](https://github.com/fawazahmed0/youtube-uploader/pull/60), [Show More being toggled incorrectly…](https://github.com/fawazahmed0/youtube-uploader/pull/180) and [Added support for selecting "Gaming" category aswell as game.](https://github.com/fawazahmed0/youtube-uploader/pull/182) 123 | - [Owl Burger](https://github.com/Zebraslive) - For [Create Channel](https://github.com/fawazahmed0/youtube-uploader/pull/66) 124 | - [Ítalo Andrade](https://github.com/italodeandra) - For [Channel Switcher](https://github.com/fawazahmed0/youtube-uploader/pull/73) 125 | - [Dominic Findlay](https://github.com/DominicFindlay) - For Supporting [Single Quotes in playlist name](https://github.com/fawazahmed0/youtube-uploader/pull/82), [skip processing bug fix](https://github.com/fawazahmed0/youtube-uploader/pull/162) and [wait for upload complete](https://github.com/fawazahmed0/youtube-uploader/pull/225) 126 | - [Dement6d](https://github.com/dement6d) - For [Better error messages](https://github.com/fawazahmed0/youtube-uploader/pull/99) & [handling 2FA](https://github.com/fawazahmed0/youtube-uploader/pull/101) 127 | - [coooo77](https://github.com/coooo77) - For [uploading videos as draft option](https://github.com/fawazahmed0/youtube-uploader/pull/105) 128 | - [L1teleau](https://github.com/L1teleau) - For [upload limit reached message](https://github.com/fawazahmed0/youtube-uploader/pull/115) 129 | - [Sacredrel1c](https://github.com/sacredrel1c) - For [channel stuck due to no drop down menu](https://github.com/fawazahmed0/youtube-uploader/pull/118), [upload endless wait fix](https://github.com/fawazahmed0/youtube-uploader/pull/125), [click fix](https://github.com/fawazahmed0/youtube-uploader/pull/133), [Recursive lang change](https://github.com/fawazahmed0/youtube-uploader/pull/137), [publish short comment](https://github.com/fawazahmed0/youtube-uploader/pull/219) and [Fix youtube login](https://github.com/fawazahmed0/youtube-uploader/pull/228) 130 | - [Chipped1](https://github.com/Chipped1) - For [Google login fix](https://github.com/fawazahmed0/youtube-uploader/pull/127) 131 | - [Jared L](https://github.com/lhjt) - For [customSelectorUrl config option](https://github.com/fawazahmed0/youtube-uploader/pull/139) 132 | - [Tiago Severino](https://github.com/TiagoSeverino) - For [Replacing readline](https://github.com/fawazahmed0/youtube-uploader/pull/157) and [Daily limit issue](https://github.com/fawazahmed0/youtube-uploader/pull/160) 133 | - [PeterStegarus](https://github.com/PeterStegarus) - For [Daily upload error message](https://github.com/fawazahmed0/youtube-uploader/pull/163) 134 | - [Kudou Sterain](https://github.com/hotrungnhan) - For [isAgeRestriction, isNotForKid option and other fixes](https://github.com/fawazahmed0/youtube-uploader/pull/175) 135 | - [Andrew Glago](https://github.com/a11rew) - For [pinning option for comments](https://github.com/fawazahmed0/youtube-uploader/pull/186) 136 | - [nhantamz](https://github.com/nhantamz) - For [setting english as default language](https://github.com/fawazahmed0/youtube-uploader/pull/191) 137 | - [Damian](https://github.com/ClassAxion) - For [publish type support](https://github.com/fawazahmed0/youtube-uploader/pull/198), [Close browser before error](https://github.com/fawazahmed0/youtube-uploader/pull/201), [isChannelMonetized](https://github.com/fawazahmed0/youtube-uploader/pull/203) option, [PR build status check](https://github.com/fawazahmed0/youtube-uploader/pull/205), [improve monetization code](https://github.com/fawazahmed0/youtube-uploader/pull/206) and [userDataDir option](https://github.com/fawazahmed0/youtube-uploader/pull/213) 138 | - [Soubhik Biswas](https://github.com/s0ubhik) - For [fixing home page selector](https://github.com/fawazahmed0/youtube-uploader/pull/229) 139 | - [pm96](https://github.com/pm96) - For [publishToSubscriptionFeedAndNotifySubscribers](https://github.com/fawazahmed0/youtube-uploader/pull/232) option 140 | - [bmarotta](https://github.com/bmarotta) - For [Enhanced logs and workarounds](https://github.com/fawazahmed0/youtube-uploader/pull/236) 141 | 142 | 143 |
144 |
145 | -------------------------------------------------------------------------------- /assets/defaultsettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawazahmed0/youtube-uploader/88d54ae470c780376c517b442d01472933cf40b0/assets/defaultsettings.png -------------------------------------------------------------------------------- /assets/english-uk-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawazahmed0/youtube-uploader/88d54ae470c780376c517b442d01472933cf40b0/assets/english-uk-1.png -------------------------------------------------------------------------------- /assets/english-uk-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawazahmed0/youtube-uploader/88d54ae470c780376c517b442d01472933cf40b0/assets/english-uk-2.png -------------------------------------------------------------------------------- /assets/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawazahmed0/youtube-uploader/88d54ae470c780376c517b442d01472933cf40b0/assets/youtube.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-videos-uploader", 3 | "version": "2.0.26", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "youtube-videos-uploader", 9 | "version": "2.0.26", 10 | "license": "MIT", 11 | "dependencies": { 12 | "prettier": "^2.4.1", 13 | "puppeteer": "^14.4.1", 14 | "puppeteer-extra": "^3.3.0", 15 | "puppeteer-extra-plugin-stealth": "^2.10.1" 16 | }, 17 | "devDependencies": { 18 | "@types/fs-extra": "^9.0.13", 19 | "@types/node": "^18.7.2", 20 | "typescript": "^4.7.4" 21 | }, 22 | "funding": { 23 | "url": "https://github.com/sponsors/fawazahmed0" 24 | } 25 | }, 26 | "node_modules/@types/debug": { 27 | "version": "4.1.7", 28 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", 29 | "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", 30 | "dependencies": { 31 | "@types/ms": "*" 32 | } 33 | }, 34 | "node_modules/@types/fs-extra": { 35 | "version": "9.0.13", 36 | "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", 37 | "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", 38 | "dev": true, 39 | "dependencies": { 40 | "@types/node": "*" 41 | } 42 | }, 43 | "node_modules/@types/ms": { 44 | "version": "0.7.31", 45 | "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", 46 | "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" 47 | }, 48 | "node_modules/@types/node": { 49 | "version": "18.7.5", 50 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.5.tgz", 51 | "integrity": "sha512-NcKK6Ts+9LqdHJaW6HQmgr7dT/i3GOHG+pt6BiWv++5SnjtRd4NXeiuN2kA153SjhXPR/AhHIPHPbrsbpUVOww==" 52 | }, 53 | "node_modules/@types/puppeteer": { 54 | "version": "5.4.6", 55 | "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.6.tgz", 56 | "integrity": "sha512-98Kghehs7+/GD9b56qryhqdqVCXUTbetTv3PlvDnmFRTHQH0j9DIp1f7rkAW3BAj4U3yoeSEQnKgdW8bDq0Y0Q==", 57 | "dependencies": { 58 | "@types/node": "*" 59 | } 60 | }, 61 | "node_modules/@types/yauzl": { 62 | "version": "2.10.0", 63 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", 64 | "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", 65 | "optional": true, 66 | "dependencies": { 67 | "@types/node": "*" 68 | } 69 | }, 70 | "node_modules/agent-base": { 71 | "version": "6.0.2", 72 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 73 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 74 | "dependencies": { 75 | "debug": "4" 76 | }, 77 | "engines": { 78 | "node": ">= 6.0.0" 79 | } 80 | }, 81 | "node_modules/arr-union": { 82 | "version": "3.1.0", 83 | "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", 84 | "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", 85 | "engines": { 86 | "node": ">=0.10.0" 87 | } 88 | }, 89 | "node_modules/balanced-match": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 92 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 93 | }, 94 | "node_modules/base64-js": { 95 | "version": "1.5.1", 96 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 97 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 98 | "funding": [ 99 | { 100 | "type": "github", 101 | "url": "https://github.com/sponsors/feross" 102 | }, 103 | { 104 | "type": "patreon", 105 | "url": "https://www.patreon.com/feross" 106 | }, 107 | { 108 | "type": "consulting", 109 | "url": "https://feross.org/support" 110 | } 111 | ] 112 | }, 113 | "node_modules/bl": { 114 | "version": "4.1.0", 115 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 116 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 117 | "dependencies": { 118 | "buffer": "^5.5.0", 119 | "inherits": "^2.0.4", 120 | "readable-stream": "^3.4.0" 121 | } 122 | }, 123 | "node_modules/brace-expansion": { 124 | "version": "1.1.11", 125 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 126 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 127 | "dependencies": { 128 | "balanced-match": "^1.0.0", 129 | "concat-map": "0.0.1" 130 | } 131 | }, 132 | "node_modules/buffer": { 133 | "version": "5.7.1", 134 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 135 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 136 | "funding": [ 137 | { 138 | "type": "github", 139 | "url": "https://github.com/sponsors/feross" 140 | }, 141 | { 142 | "type": "patreon", 143 | "url": "https://www.patreon.com/feross" 144 | }, 145 | { 146 | "type": "consulting", 147 | "url": "https://feross.org/support" 148 | } 149 | ], 150 | "dependencies": { 151 | "base64-js": "^1.3.1", 152 | "ieee754": "^1.1.13" 153 | } 154 | }, 155 | "node_modules/buffer-crc32": { 156 | "version": "0.2.13", 157 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 158 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", 159 | "engines": { 160 | "node": "*" 161 | } 162 | }, 163 | "node_modules/chownr": { 164 | "version": "1.1.4", 165 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 166 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 167 | }, 168 | "node_modules/clone-deep": { 169 | "version": "0.2.4", 170 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", 171 | "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", 172 | "dependencies": { 173 | "for-own": "^0.1.3", 174 | "is-plain-object": "^2.0.1", 175 | "kind-of": "^3.0.2", 176 | "lazy-cache": "^1.0.3", 177 | "shallow-clone": "^0.1.2" 178 | }, 179 | "engines": { 180 | "node": ">=0.10.0" 181 | } 182 | }, 183 | "node_modules/concat-map": { 184 | "version": "0.0.1", 185 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 186 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 187 | }, 188 | "node_modules/cross-fetch": { 189 | "version": "3.1.5", 190 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", 191 | "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", 192 | "dependencies": { 193 | "node-fetch": "2.6.7" 194 | } 195 | }, 196 | "node_modules/debug": { 197 | "version": "4.3.4", 198 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 199 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 200 | "dependencies": { 201 | "ms": "2.1.2" 202 | }, 203 | "engines": { 204 | "node": ">=6.0" 205 | }, 206 | "peerDependenciesMeta": { 207 | "supports-color": { 208 | "optional": true 209 | } 210 | } 211 | }, 212 | "node_modules/deepmerge": { 213 | "version": "4.2.2", 214 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 215 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 216 | "engines": { 217 | "node": ">=0.10.0" 218 | } 219 | }, 220 | "node_modules/devtools-protocol": { 221 | "version": "0.0.1001819", 222 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1001819.tgz", 223 | "integrity": "sha512-G6OsIFnv/rDyxSqBa2lDLR6thp9oJioLsb2Gl+LbQlyoA9/OBAkrTU9jiCcQ8Pnh7z4d6slDiLaogR5hzgJLmQ==" 224 | }, 225 | "node_modules/end-of-stream": { 226 | "version": "1.4.4", 227 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 228 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 229 | "dependencies": { 230 | "once": "^1.4.0" 231 | } 232 | }, 233 | "node_modules/extract-zip": { 234 | "version": "2.0.1", 235 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", 236 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", 237 | "dependencies": { 238 | "debug": "^4.1.1", 239 | "get-stream": "^5.1.0", 240 | "yauzl": "^2.10.0" 241 | }, 242 | "bin": { 243 | "extract-zip": "cli.js" 244 | }, 245 | "engines": { 246 | "node": ">= 10.17.0" 247 | }, 248 | "optionalDependencies": { 249 | "@types/yauzl": "^2.9.1" 250 | } 251 | }, 252 | "node_modules/fd-slicer": { 253 | "version": "1.1.0", 254 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 255 | "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", 256 | "dependencies": { 257 | "pend": "~1.2.0" 258 | } 259 | }, 260 | "node_modules/find-up": { 261 | "version": "4.1.0", 262 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 263 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 264 | "dependencies": { 265 | "locate-path": "^5.0.0", 266 | "path-exists": "^4.0.0" 267 | }, 268 | "engines": { 269 | "node": ">=8" 270 | } 271 | }, 272 | "node_modules/for-in": { 273 | "version": "1.0.2", 274 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 275 | "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", 276 | "engines": { 277 | "node": ">=0.10.0" 278 | } 279 | }, 280 | "node_modules/for-own": { 281 | "version": "0.1.5", 282 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 283 | "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", 284 | "dependencies": { 285 | "for-in": "^1.0.1" 286 | }, 287 | "engines": { 288 | "node": ">=0.10.0" 289 | } 290 | }, 291 | "node_modules/fs-constants": { 292 | "version": "1.0.0", 293 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 294 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 295 | }, 296 | "node_modules/fs-extra": { 297 | "version": "10.1.0", 298 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", 299 | "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 300 | "dependencies": { 301 | "graceful-fs": "^4.2.0", 302 | "jsonfile": "^6.0.1", 303 | "universalify": "^2.0.0" 304 | }, 305 | "engines": { 306 | "node": ">=12" 307 | } 308 | }, 309 | "node_modules/fs.realpath": { 310 | "version": "1.0.0", 311 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 312 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 313 | }, 314 | "node_modules/get-stream": { 315 | "version": "5.2.0", 316 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 317 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 318 | "dependencies": { 319 | "pump": "^3.0.0" 320 | }, 321 | "engines": { 322 | "node": ">=8" 323 | }, 324 | "funding": { 325 | "url": "https://github.com/sponsors/sindresorhus" 326 | } 327 | }, 328 | "node_modules/glob": { 329 | "version": "7.2.3", 330 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 331 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 332 | "dependencies": { 333 | "fs.realpath": "^1.0.0", 334 | "inflight": "^1.0.4", 335 | "inherits": "2", 336 | "minimatch": "^3.1.1", 337 | "once": "^1.3.0", 338 | "path-is-absolute": "^1.0.0" 339 | }, 340 | "engines": { 341 | "node": "*" 342 | }, 343 | "funding": { 344 | "url": "https://github.com/sponsors/isaacs" 345 | } 346 | }, 347 | "node_modules/graceful-fs": { 348 | "version": "4.2.10", 349 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 350 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" 351 | }, 352 | "node_modules/https-proxy-agent": { 353 | "version": "5.0.1", 354 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 355 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 356 | "dependencies": { 357 | "agent-base": "6", 358 | "debug": "4" 359 | }, 360 | "engines": { 361 | "node": ">= 6" 362 | } 363 | }, 364 | "node_modules/ieee754": { 365 | "version": "1.2.1", 366 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 367 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 368 | "funding": [ 369 | { 370 | "type": "github", 371 | "url": "https://github.com/sponsors/feross" 372 | }, 373 | { 374 | "type": "patreon", 375 | "url": "https://www.patreon.com/feross" 376 | }, 377 | { 378 | "type": "consulting", 379 | "url": "https://feross.org/support" 380 | } 381 | ] 382 | }, 383 | "node_modules/inflight": { 384 | "version": "1.0.6", 385 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 386 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 387 | "dependencies": { 388 | "once": "^1.3.0", 389 | "wrappy": "1" 390 | } 391 | }, 392 | "node_modules/inherits": { 393 | "version": "2.0.4", 394 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 395 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 396 | }, 397 | "node_modules/is-buffer": { 398 | "version": "1.1.6", 399 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 400 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 401 | }, 402 | "node_modules/is-extendable": { 403 | "version": "0.1.1", 404 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 405 | "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", 406 | "engines": { 407 | "node": ">=0.10.0" 408 | } 409 | }, 410 | "node_modules/is-plain-object": { 411 | "version": "2.0.4", 412 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 413 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 414 | "dependencies": { 415 | "isobject": "^3.0.1" 416 | }, 417 | "engines": { 418 | "node": ">=0.10.0" 419 | } 420 | }, 421 | "node_modules/isobject": { 422 | "version": "3.0.1", 423 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 424 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", 425 | "engines": { 426 | "node": ">=0.10.0" 427 | } 428 | }, 429 | "node_modules/jsonfile": { 430 | "version": "6.1.0", 431 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 432 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 433 | "dependencies": { 434 | "universalify": "^2.0.0" 435 | }, 436 | "optionalDependencies": { 437 | "graceful-fs": "^4.1.6" 438 | } 439 | }, 440 | "node_modules/kind-of": { 441 | "version": "3.2.2", 442 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 443 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", 444 | "dependencies": { 445 | "is-buffer": "^1.1.5" 446 | }, 447 | "engines": { 448 | "node": ">=0.10.0" 449 | } 450 | }, 451 | "node_modules/lazy-cache": { 452 | "version": "1.0.4", 453 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 454 | "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", 455 | "engines": { 456 | "node": ">=0.10.0" 457 | } 458 | }, 459 | "node_modules/locate-path": { 460 | "version": "5.0.0", 461 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 462 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 463 | "dependencies": { 464 | "p-locate": "^4.1.0" 465 | }, 466 | "engines": { 467 | "node": ">=8" 468 | } 469 | }, 470 | "node_modules/merge-deep": { 471 | "version": "3.0.3", 472 | "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", 473 | "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", 474 | "dependencies": { 475 | "arr-union": "^3.1.0", 476 | "clone-deep": "^0.2.4", 477 | "kind-of": "^3.0.2" 478 | }, 479 | "engines": { 480 | "node": ">=0.10.0" 481 | } 482 | }, 483 | "node_modules/minimatch": { 484 | "version": "3.1.2", 485 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 486 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 487 | "dependencies": { 488 | "brace-expansion": "^1.1.7" 489 | }, 490 | "engines": { 491 | "node": "*" 492 | } 493 | }, 494 | "node_modules/mixin-object": { 495 | "version": "2.0.1", 496 | "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", 497 | "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", 498 | "dependencies": { 499 | "for-in": "^0.1.3", 500 | "is-extendable": "^0.1.1" 501 | }, 502 | "engines": { 503 | "node": ">=0.10.0" 504 | } 505 | }, 506 | "node_modules/mixin-object/node_modules/for-in": { 507 | "version": "0.1.8", 508 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", 509 | "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", 510 | "engines": { 511 | "node": ">=0.10.0" 512 | } 513 | }, 514 | "node_modules/mkdirp-classic": { 515 | "version": "0.5.3", 516 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 517 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 518 | }, 519 | "node_modules/ms": { 520 | "version": "2.1.2", 521 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 522 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 523 | }, 524 | "node_modules/node-fetch": { 525 | "version": "2.6.7", 526 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 527 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 528 | "dependencies": { 529 | "whatwg-url": "^5.0.0" 530 | }, 531 | "engines": { 532 | "node": "4.x || >=6.0.0" 533 | }, 534 | "peerDependencies": { 535 | "encoding": "^0.1.0" 536 | }, 537 | "peerDependenciesMeta": { 538 | "encoding": { 539 | "optional": true 540 | } 541 | } 542 | }, 543 | "node_modules/once": { 544 | "version": "1.4.0", 545 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 546 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 547 | "dependencies": { 548 | "wrappy": "1" 549 | } 550 | }, 551 | "node_modules/p-limit": { 552 | "version": "2.3.0", 553 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 554 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 555 | "dependencies": { 556 | "p-try": "^2.0.0" 557 | }, 558 | "engines": { 559 | "node": ">=6" 560 | }, 561 | "funding": { 562 | "url": "https://github.com/sponsors/sindresorhus" 563 | } 564 | }, 565 | "node_modules/p-locate": { 566 | "version": "4.1.0", 567 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 568 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 569 | "dependencies": { 570 | "p-limit": "^2.2.0" 571 | }, 572 | "engines": { 573 | "node": ">=8" 574 | } 575 | }, 576 | "node_modules/p-try": { 577 | "version": "2.2.0", 578 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 579 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 580 | "engines": { 581 | "node": ">=6" 582 | } 583 | }, 584 | "node_modules/path-exists": { 585 | "version": "4.0.0", 586 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 587 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 588 | "engines": { 589 | "node": ">=8" 590 | } 591 | }, 592 | "node_modules/path-is-absolute": { 593 | "version": "1.0.1", 594 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 595 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 596 | "engines": { 597 | "node": ">=0.10.0" 598 | } 599 | }, 600 | "node_modules/pend": { 601 | "version": "1.2.0", 602 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 603 | "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" 604 | }, 605 | "node_modules/pkg-dir": { 606 | "version": "4.2.0", 607 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 608 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 609 | "dependencies": { 610 | "find-up": "^4.0.0" 611 | }, 612 | "engines": { 613 | "node": ">=8" 614 | } 615 | }, 616 | "node_modules/prettier": { 617 | "version": "2.7.1", 618 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", 619 | "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", 620 | "bin": { 621 | "prettier": "bin-prettier.js" 622 | }, 623 | "engines": { 624 | "node": ">=10.13.0" 625 | }, 626 | "funding": { 627 | "url": "https://github.com/prettier/prettier?sponsor=1" 628 | } 629 | }, 630 | "node_modules/progress": { 631 | "version": "2.0.3", 632 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 633 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 634 | "engines": { 635 | "node": ">=0.4.0" 636 | } 637 | }, 638 | "node_modules/proxy-from-env": { 639 | "version": "1.1.0", 640 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 641 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 642 | }, 643 | "node_modules/pump": { 644 | "version": "3.0.0", 645 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 646 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 647 | "dependencies": { 648 | "end-of-stream": "^1.1.0", 649 | "once": "^1.3.1" 650 | } 651 | }, 652 | "node_modules/puppeteer": { 653 | "version": "14.4.1", 654 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-14.4.1.tgz", 655 | "integrity": "sha512-+H0Gm84aXUvSLdSiDROtLlOofftClgw2TdceMvvCU9UvMryappoeS3+eOLfKvoy4sm8B8MWnYmPhWxVFudAOFQ==", 656 | "hasInstallScript": true, 657 | "dependencies": { 658 | "cross-fetch": "3.1.5", 659 | "debug": "4.3.4", 660 | "devtools-protocol": "0.0.1001819", 661 | "extract-zip": "2.0.1", 662 | "https-proxy-agent": "5.0.1", 663 | "pkg-dir": "4.2.0", 664 | "progress": "2.0.3", 665 | "proxy-from-env": "1.1.0", 666 | "rimraf": "3.0.2", 667 | "tar-fs": "2.1.1", 668 | "unbzip2-stream": "1.4.3", 669 | "ws": "8.7.0" 670 | }, 671 | "engines": { 672 | "node": ">=14.1.0" 673 | } 674 | }, 675 | "node_modules/puppeteer-extra": { 676 | "version": "3.3.0", 677 | "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.0.tgz", 678 | "integrity": "sha512-JVYbLkbo3/gNnMs4LapqNFvOeODpx87XM5Q9FX3v+nyjQ2BtD/Gk+0MDXh9einKmq11Nbn2r2cZPfpK5G47x9w==", 679 | "dependencies": { 680 | "@types/debug": "^4.1.0", 681 | "@types/puppeteer": "*", 682 | "debug": "^4.1.1", 683 | "deepmerge": "^4.2.2" 684 | }, 685 | "engines": { 686 | "node": ">=8" 687 | }, 688 | "peerDependencies": { 689 | "puppeteer": "*" 690 | } 691 | }, 692 | "node_modules/puppeteer-extra-plugin": { 693 | "version": "3.2.0", 694 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.0.tgz", 695 | "integrity": "sha512-wbiw12USE3b+maMk/IMaroYsz7rusVI9G+ml6pCFCnFFh91Z9BAEiVzhCpOHuquVXEiCCsDTWhDUgvdNxQHOyw==", 696 | "dependencies": { 697 | "@types/debug": "^4.1.0", 698 | "debug": "^4.1.1", 699 | "merge-deep": "^3.0.1" 700 | }, 701 | "engines": { 702 | "node": ">=9.11.2" 703 | }, 704 | "peerDependencies": { 705 | "puppeteer-extra": "*" 706 | } 707 | }, 708 | "node_modules/puppeteer-extra-plugin-stealth": { 709 | "version": "2.10.1", 710 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.10.1.tgz", 711 | "integrity": "sha512-QbFPuzmP1DvG8DqFuJZmBaf3+iMQZUa/ffZCcdItF+T0ya2wdOgeVKhRLl1bLmw+Zag457UouYN/Thwh6vMmHg==", 712 | "dependencies": { 713 | "debug": "^4.1.1", 714 | "puppeteer-extra-plugin": "^3.2.0", 715 | "puppeteer-extra-plugin-user-preferences": "^2.3.1" 716 | }, 717 | "engines": { 718 | "node": ">=8" 719 | } 720 | }, 721 | "node_modules/puppeteer-extra-plugin-user-data-dir": { 722 | "version": "2.3.1", 723 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.3.1.tgz", 724 | "integrity": "sha512-yhaYMaNFdfQ1LbA94ZElW1zU8rh+MFmO+GZA0gtQ8BXc+UZ6aRrWS9flIZvlXDzk+ZsXhCbTEohEwZ8lEDLRVA==", 725 | "dependencies": { 726 | "debug": "^4.1.1", 727 | "fs-extra": "^10.0.0", 728 | "puppeteer-extra-plugin": "^3.2.0" 729 | }, 730 | "engines": { 731 | "node": ">=8" 732 | } 733 | }, 734 | "node_modules/puppeteer-extra-plugin-user-preferences": { 735 | "version": "2.3.1", 736 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.3.1.tgz", 737 | "integrity": "sha512-t/FyGQj2aqtHOROqL02z+k2kNQe0cjT0Hd9pG5FJ7x0JXx1722PhOuK7FeJLQMJ+BLl2YvCUgaWSC8Zohjts5A==", 738 | "dependencies": { 739 | "debug": "^4.1.1", 740 | "deepmerge": "^4.2.2", 741 | "puppeteer-extra-plugin": "^3.2.0", 742 | "puppeteer-extra-plugin-user-data-dir": "^2.3.1" 743 | }, 744 | "engines": { 745 | "node": ">=8" 746 | } 747 | }, 748 | "node_modules/readable-stream": { 749 | "version": "3.6.0", 750 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 751 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 752 | "dependencies": { 753 | "inherits": "^2.0.3", 754 | "string_decoder": "^1.1.1", 755 | "util-deprecate": "^1.0.1" 756 | }, 757 | "engines": { 758 | "node": ">= 6" 759 | } 760 | }, 761 | "node_modules/rimraf": { 762 | "version": "3.0.2", 763 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 764 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 765 | "dependencies": { 766 | "glob": "^7.1.3" 767 | }, 768 | "bin": { 769 | "rimraf": "bin.js" 770 | }, 771 | "funding": { 772 | "url": "https://github.com/sponsors/isaacs" 773 | } 774 | }, 775 | "node_modules/safe-buffer": { 776 | "version": "5.2.1", 777 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 778 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 779 | "funding": [ 780 | { 781 | "type": "github", 782 | "url": "https://github.com/sponsors/feross" 783 | }, 784 | { 785 | "type": "patreon", 786 | "url": "https://www.patreon.com/feross" 787 | }, 788 | { 789 | "type": "consulting", 790 | "url": "https://feross.org/support" 791 | } 792 | ] 793 | }, 794 | "node_modules/shallow-clone": { 795 | "version": "0.1.2", 796 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", 797 | "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", 798 | "dependencies": { 799 | "is-extendable": "^0.1.1", 800 | "kind-of": "^2.0.1", 801 | "lazy-cache": "^0.2.3", 802 | "mixin-object": "^2.0.1" 803 | }, 804 | "engines": { 805 | "node": ">=0.10.0" 806 | } 807 | }, 808 | "node_modules/shallow-clone/node_modules/kind-of": { 809 | "version": "2.0.1", 810 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", 811 | "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", 812 | "dependencies": { 813 | "is-buffer": "^1.0.2" 814 | }, 815 | "engines": { 816 | "node": ">=0.10.0" 817 | } 818 | }, 819 | "node_modules/shallow-clone/node_modules/lazy-cache": { 820 | "version": "0.2.7", 821 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", 822 | "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", 823 | "engines": { 824 | "node": ">=0.10.0" 825 | } 826 | }, 827 | "node_modules/string_decoder": { 828 | "version": "1.3.0", 829 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 830 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 831 | "dependencies": { 832 | "safe-buffer": "~5.2.0" 833 | } 834 | }, 835 | "node_modules/tar-fs": { 836 | "version": "2.1.1", 837 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 838 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 839 | "dependencies": { 840 | "chownr": "^1.1.1", 841 | "mkdirp-classic": "^0.5.2", 842 | "pump": "^3.0.0", 843 | "tar-stream": "^2.1.4" 844 | } 845 | }, 846 | "node_modules/tar-stream": { 847 | "version": "2.2.0", 848 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 849 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 850 | "dependencies": { 851 | "bl": "^4.0.3", 852 | "end-of-stream": "^1.4.1", 853 | "fs-constants": "^1.0.0", 854 | "inherits": "^2.0.3", 855 | "readable-stream": "^3.1.1" 856 | }, 857 | "engines": { 858 | "node": ">=6" 859 | } 860 | }, 861 | "node_modules/through": { 862 | "version": "2.3.8", 863 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 864 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" 865 | }, 866 | "node_modules/tr46": { 867 | "version": "0.0.3", 868 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 869 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 870 | }, 871 | "node_modules/typescript": { 872 | "version": "4.7.4", 873 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", 874 | "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", 875 | "dev": true, 876 | "bin": { 877 | "tsc": "bin/tsc", 878 | "tsserver": "bin/tsserver" 879 | }, 880 | "engines": { 881 | "node": ">=4.2.0" 882 | } 883 | }, 884 | "node_modules/unbzip2-stream": { 885 | "version": "1.4.3", 886 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", 887 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", 888 | "dependencies": { 889 | "buffer": "^5.2.1", 890 | "through": "^2.3.8" 891 | } 892 | }, 893 | "node_modules/universalify": { 894 | "version": "2.0.0", 895 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 896 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", 897 | "engines": { 898 | "node": ">= 10.0.0" 899 | } 900 | }, 901 | "node_modules/util-deprecate": { 902 | "version": "1.0.2", 903 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 904 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 905 | }, 906 | "node_modules/webidl-conversions": { 907 | "version": "3.0.1", 908 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 909 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 910 | }, 911 | "node_modules/whatwg-url": { 912 | "version": "5.0.0", 913 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 914 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 915 | "dependencies": { 916 | "tr46": "~0.0.3", 917 | "webidl-conversions": "^3.0.0" 918 | } 919 | }, 920 | "node_modules/wrappy": { 921 | "version": "1.0.2", 922 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 923 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 924 | }, 925 | "node_modules/ws": { 926 | "version": "8.7.0", 927 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz", 928 | "integrity": "sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==", 929 | "engines": { 930 | "node": ">=10.0.0" 931 | }, 932 | "peerDependencies": { 933 | "bufferutil": "^4.0.1", 934 | "utf-8-validate": "^5.0.2" 935 | }, 936 | "peerDependenciesMeta": { 937 | "bufferutil": { 938 | "optional": true 939 | }, 940 | "utf-8-validate": { 941 | "optional": true 942 | } 943 | } 944 | }, 945 | "node_modules/yauzl": { 946 | "version": "2.10.0", 947 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 948 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", 949 | "dependencies": { 950 | "buffer-crc32": "~0.2.3", 951 | "fd-slicer": "~1.1.0" 952 | } 953 | } 954 | }, 955 | "dependencies": { 956 | "@types/debug": { 957 | "version": "4.1.7", 958 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", 959 | "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", 960 | "requires": { 961 | "@types/ms": "*" 962 | } 963 | }, 964 | "@types/fs-extra": { 965 | "version": "9.0.13", 966 | "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", 967 | "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", 968 | "dev": true, 969 | "requires": { 970 | "@types/node": "*" 971 | } 972 | }, 973 | "@types/ms": { 974 | "version": "0.7.31", 975 | "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", 976 | "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" 977 | }, 978 | "@types/node": { 979 | "version": "18.7.5", 980 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.5.tgz", 981 | "integrity": "sha512-NcKK6Ts+9LqdHJaW6HQmgr7dT/i3GOHG+pt6BiWv++5SnjtRd4NXeiuN2kA153SjhXPR/AhHIPHPbrsbpUVOww==" 982 | }, 983 | "@types/puppeteer": { 984 | "version": "5.4.6", 985 | "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.6.tgz", 986 | "integrity": "sha512-98Kghehs7+/GD9b56qryhqdqVCXUTbetTv3PlvDnmFRTHQH0j9DIp1f7rkAW3BAj4U3yoeSEQnKgdW8bDq0Y0Q==", 987 | "requires": { 988 | "@types/node": "*" 989 | } 990 | }, 991 | "@types/yauzl": { 992 | "version": "2.10.0", 993 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", 994 | "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", 995 | "optional": true, 996 | "requires": { 997 | "@types/node": "*" 998 | } 999 | }, 1000 | "agent-base": { 1001 | "version": "6.0.2", 1002 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 1003 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 1004 | "requires": { 1005 | "debug": "4" 1006 | } 1007 | }, 1008 | "arr-union": { 1009 | "version": "3.1.0", 1010 | "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", 1011 | "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" 1012 | }, 1013 | "balanced-match": { 1014 | "version": "1.0.2", 1015 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1016 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 1017 | }, 1018 | "base64-js": { 1019 | "version": "1.5.1", 1020 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 1021 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 1022 | }, 1023 | "bl": { 1024 | "version": "4.1.0", 1025 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 1026 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 1027 | "requires": { 1028 | "buffer": "^5.5.0", 1029 | "inherits": "^2.0.4", 1030 | "readable-stream": "^3.4.0" 1031 | } 1032 | }, 1033 | "brace-expansion": { 1034 | "version": "1.1.11", 1035 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1036 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1037 | "requires": { 1038 | "balanced-match": "^1.0.0", 1039 | "concat-map": "0.0.1" 1040 | } 1041 | }, 1042 | "buffer": { 1043 | "version": "5.7.1", 1044 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 1045 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 1046 | "requires": { 1047 | "base64-js": "^1.3.1", 1048 | "ieee754": "^1.1.13" 1049 | } 1050 | }, 1051 | "buffer-crc32": { 1052 | "version": "0.2.13", 1053 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 1054 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" 1055 | }, 1056 | "chownr": { 1057 | "version": "1.1.4", 1058 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 1059 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 1060 | }, 1061 | "clone-deep": { 1062 | "version": "0.2.4", 1063 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", 1064 | "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", 1065 | "requires": { 1066 | "for-own": "^0.1.3", 1067 | "is-plain-object": "^2.0.1", 1068 | "kind-of": "^3.0.2", 1069 | "lazy-cache": "^1.0.3", 1070 | "shallow-clone": "^0.1.2" 1071 | } 1072 | }, 1073 | "concat-map": { 1074 | "version": "0.0.1", 1075 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1076 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 1077 | }, 1078 | "cross-fetch": { 1079 | "version": "3.1.5", 1080 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", 1081 | "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", 1082 | "requires": { 1083 | "node-fetch": "2.6.7" 1084 | } 1085 | }, 1086 | "debug": { 1087 | "version": "4.3.4", 1088 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1089 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1090 | "requires": { 1091 | "ms": "2.1.2" 1092 | } 1093 | }, 1094 | "deepmerge": { 1095 | "version": "4.2.2", 1096 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 1097 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" 1098 | }, 1099 | "devtools-protocol": { 1100 | "version": "0.0.1001819", 1101 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1001819.tgz", 1102 | "integrity": "sha512-G6OsIFnv/rDyxSqBa2lDLR6thp9oJioLsb2Gl+LbQlyoA9/OBAkrTU9jiCcQ8Pnh7z4d6slDiLaogR5hzgJLmQ==" 1103 | }, 1104 | "end-of-stream": { 1105 | "version": "1.4.4", 1106 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 1107 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 1108 | "requires": { 1109 | "once": "^1.4.0" 1110 | } 1111 | }, 1112 | "extract-zip": { 1113 | "version": "2.0.1", 1114 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", 1115 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", 1116 | "requires": { 1117 | "@types/yauzl": "^2.9.1", 1118 | "debug": "^4.1.1", 1119 | "get-stream": "^5.1.0", 1120 | "yauzl": "^2.10.0" 1121 | } 1122 | }, 1123 | "fd-slicer": { 1124 | "version": "1.1.0", 1125 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 1126 | "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", 1127 | "requires": { 1128 | "pend": "~1.2.0" 1129 | } 1130 | }, 1131 | "find-up": { 1132 | "version": "4.1.0", 1133 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 1134 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 1135 | "requires": { 1136 | "locate-path": "^5.0.0", 1137 | "path-exists": "^4.0.0" 1138 | } 1139 | }, 1140 | "for-in": { 1141 | "version": "1.0.2", 1142 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 1143 | "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" 1144 | }, 1145 | "for-own": { 1146 | "version": "0.1.5", 1147 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 1148 | "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", 1149 | "requires": { 1150 | "for-in": "^1.0.1" 1151 | } 1152 | }, 1153 | "fs-constants": { 1154 | "version": "1.0.0", 1155 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 1156 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 1157 | }, 1158 | "fs-extra": { 1159 | "version": "10.1.0", 1160 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", 1161 | "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 1162 | "requires": { 1163 | "graceful-fs": "^4.2.0", 1164 | "jsonfile": "^6.0.1", 1165 | "universalify": "^2.0.0" 1166 | } 1167 | }, 1168 | "fs.realpath": { 1169 | "version": "1.0.0", 1170 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1171 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 1172 | }, 1173 | "get-stream": { 1174 | "version": "5.2.0", 1175 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 1176 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 1177 | "requires": { 1178 | "pump": "^3.0.0" 1179 | } 1180 | }, 1181 | "glob": { 1182 | "version": "7.2.3", 1183 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1184 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1185 | "requires": { 1186 | "fs.realpath": "^1.0.0", 1187 | "inflight": "^1.0.4", 1188 | "inherits": "2", 1189 | "minimatch": "^3.1.1", 1190 | "once": "^1.3.0", 1191 | "path-is-absolute": "^1.0.0" 1192 | } 1193 | }, 1194 | "graceful-fs": { 1195 | "version": "4.2.10", 1196 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 1197 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" 1198 | }, 1199 | "https-proxy-agent": { 1200 | "version": "5.0.1", 1201 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 1202 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 1203 | "requires": { 1204 | "agent-base": "6", 1205 | "debug": "4" 1206 | } 1207 | }, 1208 | "ieee754": { 1209 | "version": "1.2.1", 1210 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1211 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 1212 | }, 1213 | "inflight": { 1214 | "version": "1.0.6", 1215 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1216 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1217 | "requires": { 1218 | "once": "^1.3.0", 1219 | "wrappy": "1" 1220 | } 1221 | }, 1222 | "inherits": { 1223 | "version": "2.0.4", 1224 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1225 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1226 | }, 1227 | "is-buffer": { 1228 | "version": "1.1.6", 1229 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 1230 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 1231 | }, 1232 | "is-extendable": { 1233 | "version": "0.1.1", 1234 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 1235 | "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" 1236 | }, 1237 | "is-plain-object": { 1238 | "version": "2.0.4", 1239 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 1240 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 1241 | "requires": { 1242 | "isobject": "^3.0.1" 1243 | } 1244 | }, 1245 | "isobject": { 1246 | "version": "3.0.1", 1247 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 1248 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" 1249 | }, 1250 | "jsonfile": { 1251 | "version": "6.1.0", 1252 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 1253 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 1254 | "requires": { 1255 | "graceful-fs": "^4.1.6", 1256 | "universalify": "^2.0.0" 1257 | } 1258 | }, 1259 | "kind-of": { 1260 | "version": "3.2.2", 1261 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1262 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", 1263 | "requires": { 1264 | "is-buffer": "^1.1.5" 1265 | } 1266 | }, 1267 | "lazy-cache": { 1268 | "version": "1.0.4", 1269 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 1270 | "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" 1271 | }, 1272 | "locate-path": { 1273 | "version": "5.0.0", 1274 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 1275 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 1276 | "requires": { 1277 | "p-locate": "^4.1.0" 1278 | } 1279 | }, 1280 | "merge-deep": { 1281 | "version": "3.0.3", 1282 | "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", 1283 | "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", 1284 | "requires": { 1285 | "arr-union": "^3.1.0", 1286 | "clone-deep": "^0.2.4", 1287 | "kind-of": "^3.0.2" 1288 | } 1289 | }, 1290 | "minimatch": { 1291 | "version": "3.1.2", 1292 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1293 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1294 | "requires": { 1295 | "brace-expansion": "^1.1.7" 1296 | } 1297 | }, 1298 | "mixin-object": { 1299 | "version": "2.0.1", 1300 | "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", 1301 | "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", 1302 | "requires": { 1303 | "for-in": "^0.1.3", 1304 | "is-extendable": "^0.1.1" 1305 | }, 1306 | "dependencies": { 1307 | "for-in": { 1308 | "version": "0.1.8", 1309 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", 1310 | "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==" 1311 | } 1312 | } 1313 | }, 1314 | "mkdirp-classic": { 1315 | "version": "0.5.3", 1316 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 1317 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 1318 | }, 1319 | "ms": { 1320 | "version": "2.1.2", 1321 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1322 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1323 | }, 1324 | "node-fetch": { 1325 | "version": "2.6.7", 1326 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 1327 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 1328 | "requires": { 1329 | "whatwg-url": "^5.0.0" 1330 | } 1331 | }, 1332 | "once": { 1333 | "version": "1.4.0", 1334 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1335 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1336 | "requires": { 1337 | "wrappy": "1" 1338 | } 1339 | }, 1340 | "p-limit": { 1341 | "version": "2.3.0", 1342 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1343 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1344 | "requires": { 1345 | "p-try": "^2.0.0" 1346 | } 1347 | }, 1348 | "p-locate": { 1349 | "version": "4.1.0", 1350 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1351 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1352 | "requires": { 1353 | "p-limit": "^2.2.0" 1354 | } 1355 | }, 1356 | "p-try": { 1357 | "version": "2.2.0", 1358 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1359 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 1360 | }, 1361 | "path-exists": { 1362 | "version": "4.0.0", 1363 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1364 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 1365 | }, 1366 | "path-is-absolute": { 1367 | "version": "1.0.1", 1368 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1369 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 1370 | }, 1371 | "pend": { 1372 | "version": "1.2.0", 1373 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 1374 | "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" 1375 | }, 1376 | "pkg-dir": { 1377 | "version": "4.2.0", 1378 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 1379 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 1380 | "requires": { 1381 | "find-up": "^4.0.0" 1382 | } 1383 | }, 1384 | "prettier": { 1385 | "version": "2.7.1", 1386 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", 1387 | "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" 1388 | }, 1389 | "progress": { 1390 | "version": "2.0.3", 1391 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 1392 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 1393 | }, 1394 | "proxy-from-env": { 1395 | "version": "1.1.0", 1396 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1397 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1398 | }, 1399 | "pump": { 1400 | "version": "3.0.0", 1401 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1402 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1403 | "requires": { 1404 | "end-of-stream": "^1.1.0", 1405 | "once": "^1.3.1" 1406 | } 1407 | }, 1408 | "puppeteer": { 1409 | "version": "14.4.1", 1410 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-14.4.1.tgz", 1411 | "integrity": "sha512-+H0Gm84aXUvSLdSiDROtLlOofftClgw2TdceMvvCU9UvMryappoeS3+eOLfKvoy4sm8B8MWnYmPhWxVFudAOFQ==", 1412 | "requires": { 1413 | "cross-fetch": "3.1.5", 1414 | "debug": "4.3.4", 1415 | "devtools-protocol": "0.0.1001819", 1416 | "extract-zip": "2.0.1", 1417 | "https-proxy-agent": "5.0.1", 1418 | "pkg-dir": "4.2.0", 1419 | "progress": "2.0.3", 1420 | "proxy-from-env": "1.1.0", 1421 | "rimraf": "3.0.2", 1422 | "tar-fs": "2.1.1", 1423 | "unbzip2-stream": "1.4.3", 1424 | "ws": "8.7.0" 1425 | } 1426 | }, 1427 | "puppeteer-extra": { 1428 | "version": "3.3.0", 1429 | "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.0.tgz", 1430 | "integrity": "sha512-JVYbLkbo3/gNnMs4LapqNFvOeODpx87XM5Q9FX3v+nyjQ2BtD/Gk+0MDXh9einKmq11Nbn2r2cZPfpK5G47x9w==", 1431 | "requires": { 1432 | "@types/debug": "^4.1.0", 1433 | "@types/puppeteer": "*", 1434 | "debug": "^4.1.1", 1435 | "deepmerge": "^4.2.2" 1436 | } 1437 | }, 1438 | "puppeteer-extra-plugin": { 1439 | "version": "3.2.0", 1440 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.0.tgz", 1441 | "integrity": "sha512-wbiw12USE3b+maMk/IMaroYsz7rusVI9G+ml6pCFCnFFh91Z9BAEiVzhCpOHuquVXEiCCsDTWhDUgvdNxQHOyw==", 1442 | "requires": { 1443 | "@types/debug": "^4.1.0", 1444 | "debug": "^4.1.1", 1445 | "merge-deep": "^3.0.1" 1446 | } 1447 | }, 1448 | "puppeteer-extra-plugin-stealth": { 1449 | "version": "2.10.1", 1450 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.10.1.tgz", 1451 | "integrity": "sha512-QbFPuzmP1DvG8DqFuJZmBaf3+iMQZUa/ffZCcdItF+T0ya2wdOgeVKhRLl1bLmw+Zag457UouYN/Thwh6vMmHg==", 1452 | "requires": { 1453 | "debug": "^4.1.1", 1454 | "puppeteer-extra-plugin": "^3.2.0", 1455 | "puppeteer-extra-plugin-user-preferences": "^2.3.1" 1456 | } 1457 | }, 1458 | "puppeteer-extra-plugin-user-data-dir": { 1459 | "version": "2.3.1", 1460 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.3.1.tgz", 1461 | "integrity": "sha512-yhaYMaNFdfQ1LbA94ZElW1zU8rh+MFmO+GZA0gtQ8BXc+UZ6aRrWS9flIZvlXDzk+ZsXhCbTEohEwZ8lEDLRVA==", 1462 | "requires": { 1463 | "debug": "^4.1.1", 1464 | "fs-extra": "^10.0.0", 1465 | "puppeteer-extra-plugin": "^3.2.0" 1466 | } 1467 | }, 1468 | "puppeteer-extra-plugin-user-preferences": { 1469 | "version": "2.3.1", 1470 | "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.3.1.tgz", 1471 | "integrity": "sha512-t/FyGQj2aqtHOROqL02z+k2kNQe0cjT0Hd9pG5FJ7x0JXx1722PhOuK7FeJLQMJ+BLl2YvCUgaWSC8Zohjts5A==", 1472 | "requires": { 1473 | "debug": "^4.1.1", 1474 | "deepmerge": "^4.2.2", 1475 | "puppeteer-extra-plugin": "^3.2.0", 1476 | "puppeteer-extra-plugin-user-data-dir": "^2.3.1" 1477 | } 1478 | }, 1479 | "readable-stream": { 1480 | "version": "3.6.0", 1481 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1482 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1483 | "requires": { 1484 | "inherits": "^2.0.3", 1485 | "string_decoder": "^1.1.1", 1486 | "util-deprecate": "^1.0.1" 1487 | } 1488 | }, 1489 | "rimraf": { 1490 | "version": "3.0.2", 1491 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1492 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1493 | "requires": { 1494 | "glob": "^7.1.3" 1495 | } 1496 | }, 1497 | "safe-buffer": { 1498 | "version": "5.2.1", 1499 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1500 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1501 | }, 1502 | "shallow-clone": { 1503 | "version": "0.1.2", 1504 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", 1505 | "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", 1506 | "requires": { 1507 | "is-extendable": "^0.1.1", 1508 | "kind-of": "^2.0.1", 1509 | "lazy-cache": "^0.2.3", 1510 | "mixin-object": "^2.0.1" 1511 | }, 1512 | "dependencies": { 1513 | "kind-of": { 1514 | "version": "2.0.1", 1515 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", 1516 | "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", 1517 | "requires": { 1518 | "is-buffer": "^1.0.2" 1519 | } 1520 | }, 1521 | "lazy-cache": { 1522 | "version": "0.2.7", 1523 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", 1524 | "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==" 1525 | } 1526 | } 1527 | }, 1528 | "string_decoder": { 1529 | "version": "1.3.0", 1530 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1531 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1532 | "requires": { 1533 | "safe-buffer": "~5.2.0" 1534 | } 1535 | }, 1536 | "tar-fs": { 1537 | "version": "2.1.1", 1538 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 1539 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 1540 | "requires": { 1541 | "chownr": "^1.1.1", 1542 | "mkdirp-classic": "^0.5.2", 1543 | "pump": "^3.0.0", 1544 | "tar-stream": "^2.1.4" 1545 | } 1546 | }, 1547 | "tar-stream": { 1548 | "version": "2.2.0", 1549 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 1550 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 1551 | "requires": { 1552 | "bl": "^4.0.3", 1553 | "end-of-stream": "^1.4.1", 1554 | "fs-constants": "^1.0.0", 1555 | "inherits": "^2.0.3", 1556 | "readable-stream": "^3.1.1" 1557 | } 1558 | }, 1559 | "through": { 1560 | "version": "2.3.8", 1561 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1562 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" 1563 | }, 1564 | "tr46": { 1565 | "version": "0.0.3", 1566 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1567 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1568 | }, 1569 | "typescript": { 1570 | "version": "4.7.4", 1571 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", 1572 | "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", 1573 | "dev": true 1574 | }, 1575 | "unbzip2-stream": { 1576 | "version": "1.4.3", 1577 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", 1578 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", 1579 | "requires": { 1580 | "buffer": "^5.2.1", 1581 | "through": "^2.3.8" 1582 | } 1583 | }, 1584 | "universalify": { 1585 | "version": "2.0.0", 1586 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 1587 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" 1588 | }, 1589 | "util-deprecate": { 1590 | "version": "1.0.2", 1591 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1592 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1593 | }, 1594 | "webidl-conversions": { 1595 | "version": "3.0.1", 1596 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1597 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1598 | }, 1599 | "whatwg-url": { 1600 | "version": "5.0.0", 1601 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1602 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1603 | "requires": { 1604 | "tr46": "~0.0.3", 1605 | "webidl-conversions": "^3.0.0" 1606 | } 1607 | }, 1608 | "wrappy": { 1609 | "version": "1.0.2", 1610 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1611 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1612 | }, 1613 | "ws": { 1614 | "version": "8.7.0", 1615 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz", 1616 | "integrity": "sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==", 1617 | "requires": {} 1618 | }, 1619 | "yauzl": { 1620 | "version": "2.10.0", 1621 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 1622 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", 1623 | "requires": { 1624 | "buffer-crc32": "~0.2.3", 1625 | "fd-slicer": "~1.1.0" 1626 | } 1627 | } 1628 | } 1629 | } 1630 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-videos-uploader", 3 | "version": "2.0.26", 4 | "description": "Uploads videos to youtube without any limits", 5 | "main": "dist/index.js", 6 | "types": "dist", 7 | "files": [ 8 | "dist", 9 | "auth", 10 | "test" 11 | ], 12 | "directories": { 13 | "test": "test" 14 | }, 15 | "dependencies": { 16 | "prettier": "^2.4.1", 17 | "puppeteer": "^14.4.1", 18 | "puppeteer-extra": "^3.3.0", 19 | "puppeteer-extra-plugin-stealth": "^2.10.1" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/fawazahmed0/youtube-uploader.git" 24 | }, 25 | "scripts": { 26 | "build": "tsc -p .", 27 | "prepare": "npm run build", 28 | "format": "prettier --config .prettierrc \"src/**/*.ts\" --write" 29 | }, 30 | "keywords": [ 31 | "youtube uploader", 32 | "No limits", 33 | "youtube upload", 34 | "videos", 35 | "youtube video upload", 36 | "youtube videos upload", 37 | "youtube", 38 | "video uploader", 39 | "video upload" 40 | ], 41 | "author": "Fawaz Ahmed", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/fawazahmed0/youtube-uploader/issues" 45 | }, 46 | "homepage": "https://github.com/fawazahmed0/youtube-uploader#readme", 47 | "devDependencies": { 48 | "@types/fs-extra": "^9.0.13", 49 | "@types/node": "^18.7.2", 50 | "typescript": "^4.7.4" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './upload' 2 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Video { 2 | path: string 3 | title: string 4 | description: string 5 | tags?: string[] 6 | language?: string 7 | playlist?: string 8 | function?: any 9 | thumbnail?: string 10 | publishType?: 'PRIVATE' | 'UNLISTED' | 'PUBLIC' 11 | onSuccess?: (url: string, video: Video) => void 12 | skipProcessingWait?: boolean 13 | onProgress?: (arg0: VideoProgress) => void 14 | channelName?: string 15 | uploadAsDraft?: boolean 16 | isAgeRestriction?: boolean 17 | isNotForKid?: boolean 18 | isChannelMonetized?: boolean 19 | gameTitleSearch?: string 20 | publishToSubscriptionFeedAndNotifySubscribers?: boolean 21 | gameSelector?: ( arg0: GameData ) => Promise | null 22 | } 23 | 24 | export interface MessageTransport { 25 | error: (message: any) => void, 26 | warn: (message: any) => void, 27 | log: (message: any) => void, 28 | debug: (message: any) => void, 29 | userAction: (message: string) => void, 30 | onSmsVerificationCodeSent?: () => Promise 31 | } 32 | 33 | export enum ProgressEnum { 34 | Uploading, 35 | Processing, 36 | Done 37 | } 38 | 39 | export interface VideoProgress { 40 | progress: number 41 | stage: ProgressEnum 42 | } 43 | 44 | export interface VideoToEdit { 45 | link: string 46 | title?: string 47 | description?: string 48 | tags?: string[] 49 | replaceTags?: string[] 50 | language?: string 51 | playlist?: string 52 | function?: any 53 | thumbnail?: string 54 | publishType?: 'private' | 'unlisted' | 'public' | 'public&premiere' 55 | onSuccess?: Function 56 | channelName: string 57 | isAgeRestriction?: boolean 58 | isNotForKid?: boolean 59 | gameTitleSearch?: string 60 | gameSelector?: ( arg0: GameData ) => Promise | null 61 | } 62 | 63 | export interface Comment { 64 | link: string 65 | comment: string 66 | live?: boolean 67 | onSuccess?: Function 68 | pin?: boolean 69 | } 70 | 71 | export interface Credentials { 72 | email: string 73 | pass: string 74 | recoveryemail?: string | undefined 75 | } 76 | 77 | export interface GameData { 78 | title: string 79 | year?: string 80 | mid?: string 81 | } -------------------------------------------------------------------------------- /src/upload.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Credentials, 3 | Video, 4 | VideoToEdit, 5 | Comment, 6 | VideoProgress, 7 | ProgressEnum, 8 | MessageTransport, 9 | GameData 10 | } from './types' 11 | import puppeteer from 'puppeteer-extra' 12 | import { PuppeteerNodeLaunchOptions, Browser, Page } from 'puppeteer' 13 | import fs from 'fs-extra' 14 | import path from 'path' 15 | 16 | const StealthPlugin = require('puppeteer-extra-plugin-stealth')() 17 | StealthPlugin.enabledEvasions.delete('iframe.contentWindow') 18 | StealthPlugin.enabledEvasions.delete('navigator.plugins') 19 | puppeteer.use(StealthPlugin) 20 | 21 | const maxTitleLen = 100 22 | const maxDescLen = 5000 23 | 24 | const timeout = 60000 25 | const height = 900 26 | const width = 900 27 | 28 | let browser: Browser, page: Page 29 | let cookiesDirPath: string 30 | let cookiesFilePath: string 31 | 32 | const invalidCharacters = ['<', '>'] 33 | 34 | const uploadURL = 'https://www.youtube.com/upload?persist_gl=1&gl=US&persist_hl=1&hl=en' 35 | const homePageURL = 'https://www.youtube.com/?persist_gl=1&gl=US&persist_hl=1&hl=en' 36 | 37 | const defaultMessageTransport: MessageTransport = { 38 | log: console.log, 39 | userAction: console.log, 40 | debug: console.debug, 41 | error: console.error, 42 | warn: console.warn 43 | } 44 | 45 | let lastSelectedChannel = ""; 46 | 47 | /** 48 | * import { upload } from 'youtube-videos-uploader' 49 | * or 50 | * const { upload } = require('youtube-videos-uploader'); 51 | */ 52 | export const upload = async ( 53 | credentials: Credentials, 54 | videos: Video[], 55 | puppeteerLaunch?: PuppeteerNodeLaunchOptions, 56 | messageTransport: MessageTransport = defaultMessageTransport 57 | ) => { 58 | cookiesDirPath = path.join('.', 'yt-auth') 59 | cookiesFilePath = path.join( 60 | cookiesDirPath, 61 | `cookies-${credentials.email.split('@')[0].replace(/\./g, '_')}-${credentials.email 62 | .split('@')[1] 63 | .replace(/\./g, '_')}.json` 64 | ) 65 | 66 | const useCookieStore = !puppeteerLaunch?.userDataDir 67 | 68 | if (!useCookieStore) { 69 | messageTransport.log(`UserDataDir detected in options. Disabling cookie store.`) 70 | } 71 | 72 | messageTransport.debug("Launching browser..."); 73 | await launchBrowser(puppeteerLaunch, useCookieStore) 74 | messageTransport.debug("Browser successfully launched"); 75 | 76 | try { 77 | await loadAccount(credentials, messageTransport, useCookieStore) 78 | messageTransport.debug("Account loaded"); 79 | 80 | const uploadedYTLink: string[] = []; 81 | lastSelectedChannel = ""; 82 | 83 | for (const video of videos) { 84 | try { 85 | messageTransport.log(`Uploading video ${video.title} [${video.path}]`); 86 | const link = await uploadVideo(video, messageTransport) 87 | messageTransport.log(`Video ${video.title} [${video.path}] successfully uploaded`); 88 | 89 | const { onSuccess } = video 90 | if (typeof onSuccess === 'function') { 91 | try { 92 | onSuccess(link, video) 93 | } 94 | catch (err) { 95 | messageTransport.warn(`Error calling onSuccess function. Will proceed with other videos. Error: ${err}`); 96 | } 97 | } 98 | 99 | uploadedYTLink.push(link) 100 | } 101 | catch (err) { 102 | messageTransport.error(`Error uploading video ${video.title} [${video.path}]: ${err}`); 103 | throw err; 104 | } 105 | 106 | } 107 | 108 | await browser.close() 109 | 110 | return uploadedYTLink 111 | } catch (err) { 112 | messageTransport.error(err); 113 | if (browser) await browser.close() 114 | 115 | throw err 116 | } 117 | } 118 | 119 | // `videoJSON = {}`, avoid `videoJSON = undefined` throw error. 120 | async function uploadVideo(videoJSON: Video, messageTransport: MessageTransport) { 121 | const pathToFile = videoJSON.path 122 | if (!pathToFile) { 123 | throw new Error("function `upload`'s second param `videos`'s item `video` must include `path` property.") 124 | } 125 | for (let i in invalidCharacters) 126 | if (videoJSON.title.includes(invalidCharacters[i])) 127 | throw new Error( 128 | `"${videoJSON.title}" includes a character not allowed in youtube titles (${invalidCharacters[i]})` 129 | ) 130 | 131 | if (videoJSON.channelName && videoJSON.channelName !== lastSelectedChannel) { 132 | await changeChannel(videoJSON.channelName); 133 | messageTransport.debug(`Channel set to ${videoJSON.channelName}`); 134 | lastSelectedChannel = videoJSON.channelName; 135 | } 136 | 137 | const title = videoJSON.title 138 | const description = videoJSON.description 139 | const tags = videoJSON.tags 140 | // For backward compatablility playlist.name is checked first 141 | const playlistName = videoJSON.playlist 142 | const videoLang = videoJSON.language 143 | const gameTitleSearch = videoJSON.gameTitleSearch 144 | const thumb = videoJSON.thumbnail 145 | const uploadAsDraft = videoJSON.uploadAsDraft 146 | await page.evaluate(() => { 147 | window.onbeforeunload = null 148 | }) 149 | await page.goto(uploadURL) 150 | 151 | messageTransport.debug(` >> ${videoJSON.title} - Upload URL opened`); 152 | 153 | const closeBtnXPath = "//*[normalize-space(text())='Close']" 154 | const selectBtnXPath = "//*[normalize-space(text())='Select files']" 155 | const saveCloseBtnXPath = '//*[@aria-label="Save and close"]/tp-yt-iron-icon' 156 | const createBtnXPath = '//*[@id="create-icon"]/tp-yt-iron-icon' 157 | const addVideoBtnXPath = '//*[@id="text-item-0"]/ytcp-ve/div/div/yt-formatted-string' 158 | if (await page.waitForXPath(createBtnXPath, { timeout: 5000 }).catch(() => null)) { 159 | const createBtn = await page.$x(createBtnXPath) 160 | await createBtn[0].click() 161 | } 162 | if (await page.waitForXPath(addVideoBtnXPath, { timeout: 5000 }).catch(() => null)) { 163 | const addVideoBtn = await page.$x(addVideoBtnXPath) 164 | await addVideoBtn[0].click() 165 | } 166 | for (let i = 0; i < 2; i++) { 167 | try { 168 | await page.waitForXPath(selectBtnXPath) 169 | await page.waitForXPath(closeBtnXPath) 170 | break 171 | } catch (error) { 172 | const nextText = i === 0 ? ' trying again' : ' failed again' 173 | messageTransport.log('Failed to find the select files button' + nextText) 174 | messageTransport.log(error) 175 | await page.evaluate(() => { 176 | window.onbeforeunload = null 177 | }) 178 | await page.goto(uploadURL) 179 | } 180 | } 181 | // Remove hidden closebtn text 182 | const closeBtn = await page.$x(closeBtnXPath) 183 | await page.evaluate((el) => { 184 | el.textContent = 'oldclosse' 185 | }, closeBtn[0]) 186 | 187 | const selectBtn = await page.$x(selectBtnXPath) 188 | const [fileChooser] = await Promise.all([ 189 | page.waitForFileChooser(), 190 | selectBtn[0].click() // button that triggers file selection 191 | ]) 192 | await fileChooser.accept([pathToFile]); 193 | messageTransport.debug(` >> ${videoJSON.title} - File chooser accepted`); 194 | 195 | // Setup onProgress 196 | let progressChecker: any 197 | let progress: VideoProgress = { progress: 0, stage: ProgressEnum.Uploading } 198 | if (videoJSON.onProgress) { 199 | videoJSON.onProgress(progress) 200 | progressChecker = setInterval(async () => { 201 | let curProgress = await page.evaluate(() => { 202 | let items = document.querySelectorAll('span.progress-label.ytcp-video-upload-progress') 203 | for (let i = 0; i < items.length; i++) { 204 | if (items.item(i).textContent!.indexOf('%') === -1) continue 205 | return items.item(i).textContent 206 | } 207 | }) 208 | if (progressChecker == undefined || !curProgress) return 209 | curProgress = curProgress.split(' ').find((txt: string) => txt.indexOf('%') != -1) 210 | let newProgress = curProgress ? parseInt(curProgress.slice(0, -1)) : 0 211 | if (progress.progress == newProgress) return 212 | progress.progress = newProgress 213 | videoJSON.onProgress!(progress) 214 | }, 500) 215 | } 216 | 217 | const errorMessage = await page.evaluate(() => 218 | (document.querySelector('.error-area.style-scope.ytcp-uploads-dialog') as HTMLElement)?.innerText.trim() 219 | ) 220 | if (errorMessage) { 221 | await browser.close() 222 | throw new Error('Youtube returned an error : ' + errorMessage) 223 | } 224 | 225 | // Wait for upload to complete, but not checks 226 | const uploadCompletePromise = page 227 | .waitForXPath('//ytcp-video-upload-progress/span[contains(@class,"progress-label") and contains(text(),"Upload complete")]', { 228 | timeout: 0 229 | }) 230 | .then(() => 'uploadComplete'); 231 | 232 | // Check if daily upload limit is reached 233 | const dailyUploadPromise = page 234 | .waitForXPath('//div[contains(text(),"Daily upload limit reached")]', { timeout: 0 }) 235 | .then(() => 'dailyUploadReached') 236 | const uploadResult = await Promise.any([uploadCompletePromise, dailyUploadPromise]) 237 | if (uploadResult === 'dailyUploadReached') { 238 | browser.close() 239 | throw new Error('Daily upload limit reached') 240 | } 241 | 242 | // Wait for upload to go away and processing to start, skip the wait if the user doesn't want it. 243 | if (!videoJSON.skipProcessingWait) { 244 | // waits for checks to be complete (upload should be complete already) 245 | await page.waitForXPath('//*[contains(text(),"Video upload complete")]', { hidden: true, timeout: 0 }); 246 | messageTransport.debug(` >> ${videoJSON.title} - Video upload finished`); 247 | } else { 248 | await sleep(5000) 249 | } 250 | 251 | if (videoJSON.onProgress) { 252 | progress = { progress: 0, stage: ProgressEnum.Processing } 253 | videoJSON.onProgress(progress) 254 | } 255 | if (videoJSON.onProgress) { 256 | clearInterval(progressChecker) 257 | progressChecker = undefined 258 | progress = { progress: 100, stage: ProgressEnum.Done } 259 | videoJSON.onProgress(progress) 260 | } 261 | 262 | // Wait until title & description box pops up 263 | if (thumb) { 264 | let thumbnailChooserXpath = xpathTextSelector('upload thumbnail') 265 | await page.waitForXPath(thumbnailChooserXpath) 266 | const thumbBtn = await page.$x(thumbnailChooserXpath) 267 | const [thumbChooser] = await Promise.all([ 268 | page.waitForFileChooser(), 269 | thumbBtn[0].click() // button that triggers file selection 270 | ]) 271 | await thumbChooser.accept([thumb]) 272 | } 273 | await page.waitForFunction('document.querySelectorAll(\'[id="textbox"]\').length > 1') 274 | const textBoxes = await page.$x('//*[@id="textbox"]') 275 | await page.bringToFront() 276 | // Add the title value 277 | await textBoxes[0].focus() 278 | await page.waitForTimeout(1000) 279 | await textBoxes[0].evaluate((e) => ((e as any).__shady_native_textContent = '')) 280 | await textBoxes[0].type(title.substring(0, maxTitleLen)) 281 | // Add the Description content 282 | await textBoxes[0].evaluate((e) => ((e as any).__shady_native_textContent = '')) 283 | await textBoxes[1].type(description.substring(0, maxDescLen)); 284 | 285 | messageTransport.debug(` >> ${videoJSON.title} - Title and description set`); 286 | 287 | const childOption = await page.$x('//*[contains(text(),"No, it\'s")]') 288 | await childOption[0].click() 289 | 290 | // There is no reason for this to be called. Also you should be using #toggle-button not going by the text... 291 | // const moreOption = await page.$x("//*[normalize-space(text())='Show more']") 292 | // await moreOption[0]?.click() 293 | 294 | const playlist = await page.$x("//*[normalize-space(text())='Select']") 295 | let createplaylistdone 296 | if (playlistName) { 297 | let playlistSet = false; 298 | // Selecting playlist 299 | for (let i = 0; i < 2; i++) { 300 | try { 301 | await page.evaluate((el) => el.click(), playlist[0]) 302 | // Type the playlist name to filter out 303 | await page.waitForSelector('#search-input') 304 | await page.focus(`#search-input`) 305 | await page.type(`#search-input`, playlistName) 306 | 307 | const escapedPlaylistName = escapeQuotesForXPath(playlistName) 308 | const playlistToSelectXPath = '//*[normalize-space(text())=' + escapedPlaylistName + ']' 309 | await page.waitForXPath(playlistToSelectXPath, { timeout: 10000 }) 310 | const playlistNameSelector = await page.$x(playlistToSelectXPath) 311 | await page.evaluate((el) => el.click(), playlistNameSelector[0]) 312 | createplaylistdone = await page.$x("//*[normalize-space(text())='Done']") 313 | await page.evaluate((el) => el.click(), createplaylistdone[0]); 314 | playlistSet = true; 315 | break 316 | } catch (error) { 317 | messageTransport.log(` >> ${videoJSON.title} - ${playlistName} not found. Creating...`); 318 | // Creating new playlist 319 | // click on playlist dropdown 320 | await page.evaluate((el) => el.click(), playlist[0]) 321 | // click New playlist button 322 | const newPlaylistXPath = 323 | "//*[normalize-space(text())='New playlist'] | //*[normalize-space(text())='Create playlist']" 324 | await page.waitForXPath(newPlaylistXPath) 325 | const createplaylist = await page.$x(newPlaylistXPath) 326 | await page.evaluate((el) => el.click(), createplaylist[0]) 327 | // Enter new playlist name 328 | await page.keyboard.type(' ' + playlistName.substring(0, 148)) 329 | // click create & then done button 330 | const createplaylistbtn = await page.$x("//*[normalize-space(text())='Create']") 331 | await page.evaluate((el) => el.click(), createplaylistbtn[1]) 332 | createplaylistdone = await page.$x("//*[normalize-space(text())='Done']") 333 | await page.evaluate((el) => el.click(), createplaylistdone[0]); 334 | playlistSet = true; 335 | } 336 | } 337 | if (playlistSet) { 338 | messageTransport.debug(` >> ${videoJSON.title} - Playlist set to ${playlistName}`); 339 | } else { 340 | messageTransport.warn(` >> ${videoJSON.title} - Failed setting playlist`); 341 | } 342 | 343 | } 344 | 345 | if (!videoJSON.isNotForKid) { 346 | await page.click("tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_MFK']").catch(() => {}) 347 | } else if (videoJSON.isAgeRestriction) { 348 | await page.$eval(`tp-yt-paper-radio-button[name='VIDEO_AGE_RESTRICTION_SELF']`, (e: any) => e.click()) 349 | } else { 350 | await page.click("tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']").catch(() => {}) 351 | } 352 | messageTransport.debug(` >> ${videoJSON.title} - Kid restriction set`); 353 | // await page.waitForXPath('//ytcp-badge[contains(@class,"draft-badge")]//div[contains(text(),"Saved as private")]', { timeout: 0}) 354 | 355 | // await page.click("#toggle-button") 356 | // Was having issues because of await page.$x("//*[normalize-space(text())='Show more']").click(), so I started messing with the line above. 357 | // The issue was obviously not the line above but I either way created code to ensure that Show more has been pressed before proceeding. 358 | let showMoreButton = await page.$('#toggle-button') 359 | if (showMoreButton == undefined) throw `uploadVideo - Toggle button not found.` 360 | else { 361 | // console.log( "Show more start." ) 362 | while ((await page.$('ytcp-video-metadata-editor-advanced')) == undefined) { 363 | // console.log( "Show more while." ) 364 | await showMoreButton.click() 365 | await sleep(1000) 366 | } 367 | // console.log( "Show more finished." ) 368 | } 369 | 370 | // Add tags 371 | if (tags) { 372 | //show more 373 | try { 374 | await page.focus(`[aria-label="Tags"]`) 375 | await page.type(`[aria-label="Tags"]`, tags.join(', ').substring(0, 495) + ', ') 376 | } catch (err) {} 377 | messageTransport.debug(` >> ${videoJSON.title} - Tags set to ${tags.join(', ')}`); 378 | } 379 | 380 | // Set pusblish to subscription feed and notify subscribers to false 381 | if(videoJSON.publishToSubscriptionFeedAndNotifySubscribers === false) { 382 | await page.waitForSelector("#notify-subscribers > div:nth-child(1) > div:nth-child(1)") 383 | await page.click("#notify-subscribers > div:nth-child(1) > div:nth-child(1)"); 384 | } 385 | // Selecting video language 386 | if (videoLang) { 387 | const langHandler = await page.$x("//*[normalize-space(text())='Video language']") 388 | await page.evaluate((el) => el.click(), langHandler[0]) 389 | // translate(text(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') 390 | const langName = await page.$x( 391 | '//*[normalize-space(translate(text(),"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"))=\'' + 392 | videoLang.toLowerCase() + 393 | "']" 394 | ) 395 | await page.evaluate((el) => el.click(), langName[langName.length - 1]); 396 | messageTransport.debug(` >> ${videoJSON.title} - Video language set to ${videoLang}`); 397 | } 398 | 399 | // Setting Game Title ( Will also set Category to gaming ) 400 | if (gameTitleSearch) { 401 | const resultSelectGame = await selectGame(page, gameTitleSearch, messageTransport, videoJSON.gameSelector); 402 | if (resultSelectGame) { 403 | messageTransport.debug(` >> ${videoJSON.title} - Game title set to ${gameTitleSearch}`); 404 | } else { 405 | messageTransport.warn(` >> ${videoJSON.title} - Failed setting game title`); 406 | } 407 | 408 | } 409 | 410 | const nextBtnXPath = "//*[normalize-space(text())='Next']/parent::*[not(@disabled)]" 411 | let next 412 | 413 | await page.waitForXPath(nextBtnXPath) 414 | next = await page.$x(nextBtnXPath) 415 | await next[0].click() 416 | 417 | if (videoJSON.isChannelMonetized) { 418 | try { 419 | await page.waitForSelector('#child-input ytcp-video-monetization', { visible: true, timeout: 10000 }) 420 | 421 | await page.waitForTimeout(1500) 422 | 423 | await page.click('#child-input ytcp-video-monetization') 424 | 425 | await page.waitForSelector( 426 | 'ytcp-video-monetization-edit-dialog.cancel-button-hidden .ytcp-video-monetization-edit-dialog #radioContainer #onRadio' 427 | ) 428 | await page.evaluate(() => 429 | ( 430 | document.querySelector( 431 | 'ytcp-video-monetization-edit-dialog.cancel-button-hidden .ytcp-video-monetization-edit-dialog #radioContainer #onRadio' 432 | ) as HTMLInputElement 433 | ).click() 434 | ) 435 | 436 | await page.waitForTimeout(1500) 437 | 438 | await page.waitForSelector( 439 | 'ytcp-video-monetization-edit-dialog.cancel-button-hidden .ytcp-video-monetization-edit-dialog #save-button', 440 | { visible: true } 441 | ) 442 | await page.click( 443 | 'ytcp-video-monetization-edit-dialog.cancel-button-hidden .ytcp-video-monetization-edit-dialog #save-button' 444 | ) 445 | 446 | await page.waitForTimeout(1500) 447 | 448 | await page.waitForXPath(nextBtnXPath) 449 | next = await page.$x(nextBtnXPath) 450 | await next[0].click() 451 | } catch {} 452 | 453 | try { 454 | await page.waitForSelector( 455 | '.ytpp-self-certification-questionnaire .ytpp-self-certification-questionnaire #checkbox-container', 456 | { visible: true, timeout: 10000 } 457 | ) 458 | await page.evaluate(() => 459 | ( 460 | document.querySelector( 461 | '.ytpp-self-certification-questionnaire .ytpp-self-certification-questionnaire #checkbox-container' 462 | ) as HTMLInputElement 463 | ).click() 464 | ) 465 | 466 | await page.waitForTimeout(1500) 467 | 468 | await page.waitForSelector( 469 | '.ytpp-self-certification-questionnaire .ytpp-self-certification-questionnaire #submit-questionnaire-button', 470 | { visible: true } 471 | ) 472 | await page.evaluate(() => 473 | ( 474 | document.querySelector( 475 | '.ytpp-self-certification-questionnaire .ytpp-self-certification-questionnaire #submit-questionnaire-button' 476 | ) as HTMLButtonElement 477 | ).click() 478 | ) 479 | 480 | await page.waitForXPath(nextBtnXPath) 481 | next = await page.$x(nextBtnXPath) 482 | await next[0].click() 483 | 484 | await page.waitForTimeout(1500) 485 | } catch {} 486 | messageTransport.debug(` >> ${videoJSON.title} - Channel monetization set`); 487 | } 488 | 489 | await sleep(100); 490 | await page.waitForXPath(nextBtnXPath) 491 | // click next button 492 | await sleep(100); 493 | next = await page.$x(nextBtnXPath) 494 | await next[0].click() 495 | await page.waitForXPath(nextBtnXPath) 496 | // click next button 497 | await sleep(100); 498 | next = await page.$x(nextBtnXPath) 499 | await next[0].click() 500 | 501 | if (videoJSON.publishType) { 502 | await page.waitForSelector('#privacy-radios *[name="' + videoJSON.publishType + '"]', { visible: true }) 503 | 504 | await page.waitForTimeout(1000) 505 | 506 | await page.click('#privacy-radios *[name="' + videoJSON.publishType + '"]'); 507 | messageTransport.debug(` >> ${videoJSON.title} - Publish type set`); 508 | } 509 | 510 | // Get publish button 511 | const publishXPath = 512 | "//*[normalize-space(text())='Publish']/parent::*[not(@disabled)] | //*[normalize-space(text())='Save']/parent::*[not(@disabled)]" 513 | await page.waitForXPath(publishXPath) 514 | // save youtube upload link 515 | const videoBaseLink = 'https://youtu.be' 516 | const shortVideoBaseLink = 'https://youtube.com/shorts' 517 | const uploadLinkSelector = `[href^="${videoBaseLink}"], [href^="${shortVideoBaseLink}"]` 518 | await page.waitForSelector(uploadLinkSelector) 519 | const uploadedLinkHandle = await page.$(uploadLinkSelector) 520 | 521 | let uploadedLink 522 | do { 523 | await page.waitForTimeout(500) 524 | uploadedLink = await page.evaluate((e) => e.getAttribute('href'), uploadedLinkHandle) 525 | } while (uploadedLink === videoBaseLink || uploadedLink === shortVideoBaseLink) 526 | 527 | const closeDialogXPath = uploadAsDraft ? saveCloseBtnXPath : publishXPath 528 | let closeDialog 529 | for (let i = 0; i < 10; i++) { 530 | try { 531 | closeDialog = await page.$x(closeDialogXPath) 532 | await closeDialog[0].click() 533 | break 534 | } catch (error) { 535 | await page.waitForTimeout(5000) 536 | } 537 | } 538 | 539 | if (videoJSON.isChannelMonetized) { 540 | try { 541 | await page.waitForSelector('#dialog-buttons #secondary-action-button', { visible: true }) 542 | 543 | await page.click('#dialog-buttons #secondary-action-button') 544 | } catch {} 545 | } 546 | 547 | // await page.waitForXPath('//*[contains(text(),"Finished processing")]', { timeout: 0}) 548 | 549 | // no closeBtn will show up if keeps video as draft 550 | if (uploadAsDraft) return uploadedLink 551 | 552 | // Wait for closebtn to show up 553 | try { 554 | await page.waitForXPath(closeBtnXPath) 555 | } catch (e) { 556 | await browser.close() 557 | throw new Error( 558 | 'Please make sure you set up your default video visibility correctly, you might have forgotten. More infos : https://github.com/fawazahmed0/youtube-uploader#youtube-setup' 559 | ) 560 | } 561 | 562 | return uploadedLink 563 | } 564 | 565 | export const update = async ( 566 | credentials: Credentials, 567 | videos: VideoToEdit[], 568 | puppeteerLaunch?: PuppeteerNodeLaunchOptions, 569 | messageTransport: MessageTransport = defaultMessageTransport 570 | ) => { 571 | cookiesDirPath = path.join('.', 'yt-auth') 572 | cookiesFilePath = path.join( 573 | cookiesDirPath, 574 | `cookies-${credentials.email.split('@')[0].replace(/\./g, '_')}-${credentials.email 575 | .split('@')[1] 576 | .replace(/\./g, '_')}.json` 577 | ) 578 | 579 | await launchBrowser(puppeteerLaunch) 580 | if (!fs.existsSync(cookiesFilePath)) await loadAccount(credentials, messageTransport) 581 | const updatedYTLink = [] 582 | 583 | for (const video of videos) { 584 | messageTransport.log(video) 585 | const link = await updateVideoInfo(video, messageTransport) 586 | 587 | const { onSuccess } = video 588 | if (typeof onSuccess === 'function') { 589 | onSuccess(link) 590 | } 591 | 592 | updatedYTLink.push(link) 593 | } 594 | await browser.close() 595 | return updatedYTLink 596 | } 597 | 598 | export const comment = async ( 599 | credentials: Credentials, 600 | comments: Comment[], 601 | puppeteerLaunch?: PuppeteerNodeLaunchOptions, 602 | messageTransport: MessageTransport = defaultMessageTransport 603 | ) => { 604 | cookiesDirPath = path.join('.', 'yt-auth') 605 | cookiesFilePath = path.join( 606 | cookiesDirPath, 607 | `cookies-${credentials.email.split('@')[0].replace(/\./g, '_')}-${credentials.email 608 | .split('@')[1] 609 | .replace(/\./g, '_')}.json` 610 | ) 611 | 612 | await launchBrowser(puppeteerLaunch) 613 | if (!fs.existsSync(cookiesFilePath)) await loadAccount(credentials, messageTransport) 614 | const commentsS = [] 615 | 616 | for (const comment of comments) { 617 | let result 618 | messageTransport.log(comment) 619 | if (comment.live) result = await publishLiveComment(comment, messageTransport) 620 | else 621 | result = comment.link.includes('/shorts') 622 | ? await publishShortComment(comment) 623 | : await publishComment(comment) 624 | 625 | const { onSuccess } = comment 626 | if (typeof onSuccess === 'function') { 627 | onSuccess(result) 628 | } 629 | 630 | commentsS.push(result) 631 | } 632 | await browser.close() 633 | return commentsS 634 | } 635 | 636 | const publishShortComment = async (comment: Comment) => { 637 | const videoUrl = comment.link 638 | const cmt = comment.comment 639 | const cmtBtnXPath = '//*[@id="comments-button"]' 640 | const cmtInputXPath = '//*[@id="placeholder-area"]' 641 | if (!videoUrl) { 642 | throw new Error('The link of the video is a required parameter') 643 | } 644 | await page.goto(videoUrl) 645 | await sleep(3000) 646 | try { 647 | await page.waitForXPath(cmtBtnXPath) 648 | const cmtBtn = await page.$x(cmtBtnXPath) 649 | await cmtBtn[0].click() 650 | await page.waitForXPath(cmtInputXPath) 651 | const inputArea = await page.$x(cmtInputXPath) 652 | await inputArea[0].focus() 653 | await inputArea[0].click() 654 | await inputArea[0].type(cmt.substring(0, 10000)) 655 | await page.click('#submit-button') 656 | return { err: false, data: 'sucess' } 657 | } catch (err) { 658 | return { err: true, data: err } 659 | } 660 | } 661 | 662 | const publishComment = (comment: Comment) => { 663 | const videoUrl = comment.link 664 | if (!videoUrl) { 665 | throw new Error('The link of the video is a required parameter') 666 | } 667 | return new Promise(async (resolve) => { 668 | try { 669 | const cmt = comment.comment 670 | await page.goto(videoUrl) 671 | await sleep(2000) 672 | await scrollTillVeiw(page, `#placeholder-area`) 673 | 674 | await page.focus(`#placeholder-area`) 675 | const commentBox = await page.$x('//*[@id="placeholder-area"]') 676 | await commentBox[0].focus() 677 | await commentBox[0].click() 678 | await commentBox[0].type(cmt.substring(0, 10000)) 679 | 680 | page.exposeFunction('commentResolve', resolve) 681 | 682 | if (comment.pin) { 683 | // Select the comment list 684 | const [commentList] = await page.$x( 685 | `//ytd-comments[@id="comments"]//ytd-item-section-renderer[@section-identifier="comment-item-section"]/div[@id="contents"]` 686 | ) 687 | 688 | // Register mutation observer for comment list 689 | await commentList.evaluateHandle((commentList) => { 690 | const observer = new MutationObserver((mutations) => { 691 | mutations.forEach(async (mutation) => { 692 | if (mutation.addedNodes.length > 0) { 693 | try { 694 | // Get the recently added comment node 695 | const comment = mutation.addedNodes[0] 696 | 697 | // Finds three dot menu inside comment 698 | // Evaluate XPath relative to the added comment. 699 | // It'd be nice to use Puppeteer's helpers but the MutationObserver returns DOM nodes 700 | const menu = document.evaluate( 701 | `.//div[@id="action-menu"]/ytd-menu-renderer/yt-icon-button`, 702 | comment, 703 | null, 704 | XPathResult.FIRST_ORDERED_NODE_TYPE 705 | ).singleNodeValue 706 | // Expand three dot menu 707 | menu && (menu as HTMLButtonElement).click() 708 | 709 | // Wait for menu to expand 710 | await new Promise((resolve) => setTimeout(resolve, 100)) 711 | 712 | // Select pin button 713 | const pinButton = document.evaluate( 714 | `.//tp-yt-paper-item//*[text()="Pin"]/ancestor::tp-yt-paper-item`, 715 | document, 716 | null, 717 | XPathResult.FIRST_ORDERED_NODE_TYPE 718 | ).singleNodeValue 719 | // Click pin button 720 | pinButton && (pinButton as HTMLButtonElement).click() 721 | 722 | // Wait for confirmation dialog 723 | await new Promise((resolve) => setTimeout(resolve, 100)) 724 | 725 | // Confirm pin 726 | const confirmButton = document.querySelector( 727 | '#confirm-button>yt-button-shape>button' 728 | ) 729 | confirmButton && (confirmButton as HTMLButtonElement).click() 730 | 731 | // Disconnect observer 732 | observer.disconnect() 733 | 734 | // Resolve promise 735 | // @ts-expect-error - commentResolve is exposed to the page 736 | window.commentResolve({ err: false, data: 'sucess' }) 737 | } catch (err) { 738 | // @ts-expect-error - commentResolve is exposed to the page 739 | window.commentResolve({ err: true, data: err }) 740 | } 741 | } 742 | }) 743 | }) 744 | 745 | observer.observe(commentList, { childList: true }) 746 | }) 747 | } 748 | 749 | await page.click('#submit-button') 750 | 751 | if (comment.pin) { 752 | // Let mutation observer resolve promise after pinning comment 753 | } else { 754 | resolve({ err: false, data: 'sucess' }) 755 | } 756 | } catch (err) { 757 | resolve({ err: true, data: err }) 758 | } 759 | }) 760 | } 761 | 762 | const publishLiveComment = async (comment: Comment, messageTransport: MessageTransport) => { 763 | const videoUrl = comment.link 764 | const cmt = comment.comment 765 | if (!videoUrl) { 766 | throw new Error('The link of the video is a required parameter') 767 | } 768 | await page.goto(videoUrl) 769 | await sleep(3000) 770 | await scrollTillVeiw(page, `#label`) 771 | try { 772 | await page.focus(`#label`) 773 | } catch (err) { 774 | messageTransport.log(err) 775 | throw new Error('Video may not be Live') 776 | } 777 | 778 | for (let i = 0; i < 6; i++) { 779 | await autoScroll(page) 780 | } 781 | try { 782 | await page.focus('#input') 783 | await page.mouse.click(450, 480) 784 | await page.keyboard.type(cmt.substring(0, 200)) 785 | await sleep(200) 786 | await page.mouse.click(841, 495) 787 | return { err: false, data: 'sucess' } 788 | } catch (err) { 789 | return { err: true, data: err } 790 | } 791 | } 792 | 793 | const updateVideoInfo = async (videoJSON: VideoToEdit, messageTransport: MessageTransport) => { 794 | const videoUrl = videoJSON.link 795 | if (!videoUrl) { 796 | throw new Error('The link of the video is a required parameter') 797 | } 798 | 799 | if (videoJSON.channelName) { 800 | await changeChannel(videoJSON.channelName) 801 | } 802 | 803 | const title = videoJSON.title 804 | const description = videoJSON.description 805 | const tags = videoJSON.tags 806 | const Rtags = videoJSON.replaceTags 807 | const playlistName = videoJSON.playlist 808 | const videoLang = videoJSON.language 809 | const gameTitleSearch = videoJSON.gameTitleSearch 810 | const thumb = videoJSON.thumbnail 811 | const publish = videoJSON.publishType 812 | await page.goto(videoUrl) 813 | const editXpath = '//*[@id="subscribe-button"]/ytd-button-renderer' 814 | try { 815 | await page.waitForXPath(editXpath, { timeout: 7000 }) 816 | } catch (err) { 817 | throw new Error('The video provided may not be yours') 818 | } 819 | 820 | let edit = await page.$x(editXpath) 821 | await edit[0].click() 822 | const titleE = '//*[@id="textbox"]' 823 | await page.waitForXPath(titleE, { timeout: 70000 }) 824 | await page.waitForFunction('document.querySelectorAll(\'[id="textbox"]\').length > 1') 825 | const textBoxes = await page.$x('//*[@id="textbox"]') 826 | await page.bringToFront() 827 | // Edit the title value (if) 828 | await textBoxes[0].focus() 829 | await page.waitForTimeout(1000) 830 | if (!videoJSON.isNotForKid) { 831 | await page.click("tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_MFK']").catch(() => {}) 832 | } else if (videoJSON.isAgeRestriction) { 833 | await page.$eval(`tp-yt-paper-radio-button[name='VIDEO_AGE_RESTRICTION_SELF']`, (e: any) => e.click()) 834 | } else { 835 | await page.click("tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']").catch(() => {}) 836 | } 837 | if (title) { 838 | // await page.keyboard.down('Control') 839 | // await page.keyboard.press('A') 840 | // await page.keyboard.up('Control') 841 | // await page.keyboard.press('Backspace') 842 | await textBoxes[0].evaluate((e) => ((e as any).__shady_native_textContent = '')) 843 | await textBoxes[0].type(title.substring(0, maxTitleLen)) 844 | } 845 | // Edit the Description content (if) 846 | if (description) { 847 | // await textBoxes[1].focus() 848 | // await page.keyboard.down('Control') 849 | // await page.keyboard.press('A') 850 | // await page.keyboard.up('Control') 851 | // await page.keyboard.press('Backspace') 852 | await textBoxes[1].evaluate((e) => ((e as any).__shady_native_textContent = '')) 853 | await textBoxes[1].type(description.substring(0, maxDescLen)) 854 | } 855 | if (thumb) { 856 | const [thumbChooser] = await Promise.all([ 857 | page.waitForFileChooser({ timeout: 500 }).catch(async () => { 858 | messageTransport.log('replacing previous thumbanail') 859 | await page.click('#still-1 > button') 860 | await page.waitForSelector('#save > div') 861 | await page.click(`#save > div`) 862 | await page.waitForXPath("//*[normalize-space(text())='Save']/parent::*[@disabled]") 863 | await sleep(500) 864 | return await page.waitForFileChooser() 865 | }), 866 | await page.waitForSelector( 867 | `[class="remove-default-style style-scope ytcp-thumbnails-compact-editor-uploader"]` 868 | ), 869 | await page.click(`[class="remove-default-style style-scope ytcp-thumbnails-compact-editor-uploader"]`) 870 | ]) 871 | await thumbChooser.accept([thumb]) 872 | } 873 | // await sleep( 10000000) 874 | const playlist = await page.$x( 875 | `//*[@id="basics"]/div[4]/div[3]/div[1]/ytcp-video-metadata-playlists/ytcp-text-dropdown-trigger/ytcp-dropdown-trigger/div/div[3]` 876 | ) 877 | let createplaylistdone 878 | if (playlistName) { 879 | for (let i = 0; i < 2; i++) { 880 | try { 881 | await page.evaluate((el) => el.click(), playlist[0]) 882 | await page.waitForSelector('#search-input') 883 | await page.focus(`#search-input`) 884 | await page.type(`#search-input`, playlistName) 885 | 886 | const escapedPlaylistName = escapeQuotesForXPath(playlistName) 887 | const playlistToSelectXPath = '//*[normalize-space(text())=' + escapedPlaylistName + ']' 888 | 889 | await page.waitForXPath(playlistToSelectXPath, { timeout: 10000 }) 890 | const playlistNameSelector = await page.$x(playlistToSelectXPath) 891 | await page.evaluate((el) => el.click(), playlistNameSelector[0]) 892 | createplaylistdone = await page.$x("//*[normalize-space(text())='Done']") 893 | await page.evaluate((el) => el.click(), createplaylistdone[0]) 894 | break 895 | } catch (error) { 896 | await page.evaluate((el) => el.click(), playlist[0]) 897 | const newPlaylistXPath = 898 | "//*[normalize-space(text())='New playlist'] | //*[normalize-space(text())='Create playlist']" 899 | await page.waitForXPath(newPlaylistXPath) 900 | const createplaylist = await page.$x(newPlaylistXPath) 901 | await page.evaluate((el) => el.click(), createplaylist[0]) 902 | await page.keyboard.type(' ' + playlistName.substring(0, 148)) 903 | const createplaylistbtn = await page.$x("//*[normalize-space(text())='Create']") 904 | await page.evaluate((el) => el.click(), createplaylistbtn[1]) 905 | createplaylistdone = await page.$x("//*[normalize-space(text())='Done']") 906 | await page.evaluate((el) => el.click(), createplaylistdone[0]) 907 | } 908 | } 909 | } 910 | const moreOption = await page.$x("//*[normalize-space(text())='Show more']") 911 | await moreOption[0].click() 912 | if (tags) { 913 | await page.focus(`[aria-label="Tags"]`) 914 | await page.type(`[aria-label="Tags"]`, tags.join(', ').substring(0, 495) + ', ') 915 | } 916 | if (Rtags) { 917 | await page.click('//*[@id="clear-button"]/tp-yt-iron-icon') 918 | await page.focus(`[aria-label="Tags"]`) 919 | await page.type(`[aria-label="Tags"]`, Rtags.join(', ').substring(0, 495) + ', ') 920 | } 921 | if (videoLang) { 922 | const langHandler = await page.$x("//*[normalize-space(text())='Video language']") 923 | await page.evaluate((el) => el.click(), langHandler[0]) 924 | const langName = await page.$x( 925 | '//*[normalize-space(translate(text(),"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"))=\'' + 926 | videoLang.toLowerCase() + 927 | "']" 928 | ) 929 | await page.evaluate((el) => el.click(), langName[langName.length - 1]) 930 | } 931 | // Setting Game Title ( Will also set Category to gaming ) 932 | if (gameTitleSearch) { 933 | await selectGame(page, gameTitleSearch, messageTransport, videoJSON.gameSelector) 934 | } 935 | 936 | await page.focus(`#content`) 937 | if (publish) { 938 | await page.click(`#content`) 939 | // await page.click(`#onRadio`); 940 | const publishBtn = await page.$x('//*[@id="first-container"]') 941 | await sleep(2000) 942 | // publishBtn[0].click() 943 | try { 944 | switch (publish) { 945 | case 'private': 946 | await page 947 | .click(`#privacy-radios > tp-yt-paper-radio-button:nth-child(2)`) 948 | .catch( 949 | async (err) => 950 | await page.click( 951 | `#privacy-radios > tp-yt-paper-radio-button.style-scope.ytcp-video-visibility-select.iron-selected` 952 | ) 953 | ) 954 | break 955 | case 'unlisted': 956 | await page 957 | .click( 958 | `#privacy-radios > tp-yt-paper-radio-button.style-scope.ytcp-video-visibility-select.iron-selected` 959 | ) 960 | .catch( 961 | async (err) => await page.click(`#privacy-radios > tp-yt-paper-radio-button:nth-child(11)`) 962 | ) 963 | break 964 | case 'public': 965 | await page 966 | .click(`#privacy-radios > tp-yt-paper-radio-button:nth-child(15)`) 967 | .catch( 968 | async (err) => await page.click(`#privacy-radios > tp-yt-paper-radio-button:nth-child(16)`) 969 | ) 970 | break 971 | case 'public&premiere': 972 | await page.click(`#privacy-radios > tp-yt-paper-radio-button:nth-child(15)`) 973 | await page.click(`#enable-premiere-checkbox`) 974 | break 975 | } 976 | } catch (err) { 977 | messageTransport.log('already selected') 978 | await page.keyboard.press('Escape') 979 | } 980 | await page.click(`#save-button`) 981 | await sleep(1200) 982 | } 983 | try { 984 | await page.focus(`#content`) 985 | await page.focus(`#save > div`) 986 | 987 | await page.waitForSelector('#save > div') 988 | await page.click(`#save > div`) 989 | await page.waitForXPath("//*[normalize-space(text())='Save']/parent::*[@disabled]") 990 | } catch (err) { 991 | messageTransport.log(err) 992 | throw new Error('Probably nothing was changed ...') 993 | } 994 | //#overflow-menu-button 995 | return messageTransport.log('successfully edited') 996 | } 997 | 998 | async function loadAccount( 999 | credentials: Credentials, 1000 | messageTransport: MessageTransport, 1001 | useCookieStore: boolean = true 1002 | ) { 1003 | try { 1004 | if (!fs.existsSync(cookiesFilePath) || !useCookieStore) 1005 | await login(page, credentials, messageTransport, useCookieStore) 1006 | } catch (error: any) { 1007 | if (error.message === 'Recapcha found') { 1008 | if (browser) { 1009 | await browser.close() 1010 | } 1011 | throw error 1012 | } 1013 | 1014 | // Login failed trying again to login 1015 | try { 1016 | await login(page, credentials, messageTransport, useCookieStore) 1017 | } catch (error) { 1018 | if (browser) { 1019 | await browser.close() 1020 | } 1021 | throw error 1022 | } 1023 | } 1024 | try { 1025 | await changeHomePageLangIfNeeded(page) 1026 | } catch (error) { 1027 | messageTransport.log(error) 1028 | await login(page, credentials, messageTransport, useCookieStore) 1029 | } 1030 | } 1031 | 1032 | async function changeLoginPageLangIfNeeded(localPage: Page) { 1033 | const selectedLangSelector = '[aria-selected="true"]' 1034 | try { 1035 | await localPage.waitForSelector(selectedLangSelector) 1036 | } catch (e: any) { 1037 | throw new Error('Failed to find selected language : ' + e.name) 1038 | } 1039 | 1040 | const selectedLang = await localPage.evaluate( 1041 | (selectedLangSelector: any) => document.querySelector(selectedLangSelector).innerText, 1042 | selectedLangSelector 1043 | ) 1044 | 1045 | if (!selectedLang) { 1046 | throw new Error('Failed to find selected language : Empty text') 1047 | } 1048 | 1049 | if (selectedLang.includes('English')) { 1050 | return 1051 | } 1052 | 1053 | await localPage.click(selectedLangSelector) 1054 | 1055 | await localPage.waitForTimeout(1000) 1056 | 1057 | const englishLangItemSelector = '[role="presentation"]:not([aria-hidden="true"])>[data-value="en-GB"]' 1058 | 1059 | try { 1060 | await localPage.waitForSelector(englishLangItemSelector) 1061 | } catch (e: any) { 1062 | throw new Error('Failed to find english language item : ' + e.name) 1063 | } 1064 | 1065 | await localPage.click(englishLangItemSelector) 1066 | 1067 | await localPage.waitForTimeout(1000) 1068 | } 1069 | 1070 | async function changeHomePageLangIfNeeded(localPage: Page) { 1071 | await localPage.goto(homePageURL) 1072 | 1073 | const avatarButtonSelector = 'button#avatar-btn' 1074 | 1075 | try { 1076 | await localPage.waitForSelector(avatarButtonSelector) 1077 | } catch (e: any) { 1078 | throw new Error('Avatar/Profile picture button not found : ' + e.name) 1079 | } 1080 | 1081 | await localPage.click(avatarButtonSelector) 1082 | 1083 | const langMenuItemSelector = 1084 | '#sections>yt-multi-page-menu-section-renderer:nth-child(3)>#items>ytd-compact-link-renderer:nth-of-type(2)>a' 1085 | try { 1086 | await localPage.waitForSelector(langMenuItemSelector) 1087 | } catch (e: any) { 1088 | throw new Error('Language menu item selector/button(">") not found : ' + e.name) 1089 | } 1090 | 1091 | const selectedLang = await localPage.evaluate( 1092 | (langMenuItemSelector) => document.querySelector(langMenuItemSelector).innerText, 1093 | langMenuItemSelector 1094 | ) 1095 | 1096 | if (!selectedLang) { 1097 | throw new Error('Failed to find selected language : Empty text') 1098 | } 1099 | 1100 | if (selectedLang.includes('English')) { 1101 | await localPage.goto(uploadURL) 1102 | 1103 | return 1104 | } 1105 | 1106 | await localPage.click(langMenuItemSelector) 1107 | 1108 | const englishItemXPath = "//*[normalize-space(text())='English (UK)']" 1109 | 1110 | try { 1111 | await localPage.waitForXPath(englishItemXPath) 1112 | } catch (e: any) { 1113 | throw new Error('English(UK) item selector not found : ' + e.name) 1114 | } 1115 | 1116 | await localPage.waitForTimeout(3000) 1117 | 1118 | await localPage.evaluate((englishItemXPath: any) => { 1119 | let element: HTMLElement = document?.evaluate( 1120 | englishItemXPath, 1121 | document, 1122 | null, 1123 | XPathResult.FIRST_ORDERED_NODE_TYPE, 1124 | null 1125 | ).singleNodeValue as HTMLElement 1126 | element.click() 1127 | }, englishItemXPath) 1128 | //Recursive language change, if YouTube, for some reason, did not change the language the first time, although the English (UK) button was pressed, the exit from the recursion occurs when the selectedLang selector is tested for the set language 1129 | await changeHomePageLangIfNeeded(localPage) 1130 | } 1131 | 1132 | async function launchBrowser(puppeteerLaunch?: PuppeteerNodeLaunchOptions, loadCookies: boolean = true) { 1133 | browser = await puppeteer.launch(puppeteerLaunch) 1134 | page = await browser.newPage() 1135 | await page.setDefaultTimeout(timeout) 1136 | 1137 | if (loadCookies) { 1138 | const previousSession = fs.existsSync(cookiesFilePath) 1139 | 1140 | if (previousSession) { 1141 | // If file exist load the cookies 1142 | const cookiesString = fs.readFileSync(cookiesFilePath, { encoding: 'utf-8' }) 1143 | const parsedCookies = JSON.parse(cookiesString) 1144 | if (parsedCookies.length !== 0) { 1145 | for (let cookie of parsedCookies) { 1146 | await page.setCookie(cookie) 1147 | } 1148 | } 1149 | } 1150 | } 1151 | 1152 | await page.setViewport({ width: width, height: height }) 1153 | } 1154 | 1155 | async function login( 1156 | localPage: Page, 1157 | credentials: Credentials, 1158 | messageTransport: MessageTransport, 1159 | useCookieStore: boolean = true 1160 | ) { 1161 | await localPage.goto(uploadURL) 1162 | 1163 | if (!useCookieStore) { 1164 | try { 1165 | // Check if already logged in if we don't use normal cookie store 1166 | await localPage.waitForSelector('button#avatar-btn', { 1167 | timeout: 15 * 1000 1168 | }) 1169 | 1170 | messageTransport.log(`Account already logged in`) 1171 | 1172 | return 1173 | } catch {} 1174 | } 1175 | 1176 | await changeLoginPageLangIfNeeded(localPage) 1177 | 1178 | const emailInputSelector = 'input[type="email"]' 1179 | await localPage.waitForSelector(emailInputSelector) 1180 | 1181 | await localPage.type(emailInputSelector, credentials.email, { delay: 50 }) 1182 | await localPage.keyboard.press('Enter') 1183 | 1184 | // check if 2fa code was sent to phone 1185 | await localPage.waitForNavigation() 1186 | await localPage.waitForTimeout(1000) 1187 | const googleAppAuthSelector = 'samp' 1188 | const isOnGoogleAppAuthPage = await localPage.evaluate( 1189 | (authCodeSelector) => document.querySelector(authCodeSelector) !== null, 1190 | googleAppAuthSelector 1191 | ) 1192 | 1193 | if (isOnGoogleAppAuthPage) { 1194 | const codeElement = await localPage.$('samp') 1195 | const code = (await codeElement?.getProperty('textContent'))?.toString().replace('JSHandle:', '') 1196 | code && messageTransport.userAction('Press ' + code + ' on your phone to login') 1197 | } 1198 | // password isnt required in the case that a code was sent via google auth 1199 | else { 1200 | const passwordInputSelector = 'input[type="password"]:not([aria-hidden="true"])' 1201 | await localPage.waitForSelector(passwordInputSelector) 1202 | await localPage.waitForTimeout(3000) 1203 | await localPage.type(passwordInputSelector, credentials.pass, { delay: 50 }) 1204 | 1205 | await localPage.keyboard.press('Enter') 1206 | } 1207 | 1208 | try { 1209 | await localPage.waitForNavigation() 1210 | await localPage.waitForTimeout(1000) 1211 | 1212 | // check if sms code was sent 1213 | const smsAuthSelector = '#idvPin' 1214 | const isOnSmsAuthPage = await localPage.evaluate( 1215 | (smsAuthSelector) => document.querySelector(smsAuthSelector) !== null, 1216 | smsAuthSelector 1217 | ) 1218 | if (isOnSmsAuthPage) { 1219 | try { 1220 | if (!messageTransport.onSmsVerificationCodeSent) 1221 | throw new Error('onSmsVerificationCodeSent not implemented') 1222 | 1223 | let code = await messageTransport.onSmsVerificationCodeSent() 1224 | 1225 | if (!code) throw new Error('Invalid SMS Code') 1226 | 1227 | await localPage.type(smsAuthSelector, code.trim()) 1228 | await localPage.keyboard.press('Enter') 1229 | } catch (error) { 1230 | await browser.close() 1231 | throw error 1232 | } 1233 | } 1234 | } catch (error: any) { 1235 | const recaptchaInputSelector = 'input[aria-label="Type the text you hear or see"]' 1236 | 1237 | const isOnRecaptchaPage = await localPage.evaluate( 1238 | (recaptchaInputSelector) => document.querySelector(recaptchaInputSelector) !== null, 1239 | recaptchaInputSelector 1240 | ) 1241 | 1242 | if (isOnRecaptchaPage) { 1243 | throw new Error('Recaptcha found') 1244 | } 1245 | 1246 | throw new Error(error) 1247 | } 1248 | //create channel if not already created. 1249 | try { 1250 | await localPage.click('#create-channel-button') 1251 | await localPage.waitForTimeout(3000) 1252 | } catch (error) { 1253 | messageTransport.log('Channel already exists or there was an error creating the channel.') 1254 | } 1255 | try { 1256 | const uploadPopupSelector = 'ytcp-uploads-dialog' 1257 | await localPage.waitForSelector(uploadPopupSelector, { timeout: 70000 }) 1258 | } catch (error) { 1259 | if (credentials.recoveryemail) await securityBypass(localPage, credentials.recoveryemail, messageTransport) 1260 | } 1261 | 1262 | if (useCookieStore) { 1263 | const cookiesObject = await localPage.cookies() 1264 | await fs.mkdirSync(cookiesDirPath, { recursive: true }) 1265 | // Write cookies to temp file to be used in other profile pages 1266 | await fs.writeFile(cookiesFilePath, JSON.stringify(cookiesObject), function (err) { 1267 | if (err) { 1268 | messageTransport.log('The file could not be written. ' + err.message) 1269 | } 1270 | messageTransport.log('Session has been successfully saved') 1271 | }) 1272 | } else { 1273 | messageTransport.log('Account logged in successfully') 1274 | } 1275 | } 1276 | 1277 | // Login bypass with recovery email 1278 | async function securityBypass(localPage: Page, recoveryemail: string, messageTransport: MessageTransport) { 1279 | try { 1280 | const confirmRecoveryXPath = "//*[normalize-space(text())='Confirm your recovery email']" 1281 | await localPage.waitForXPath(confirmRecoveryXPath) 1282 | 1283 | const confirmRecoveryBtn = await localPage.$x(confirmRecoveryXPath) 1284 | await localPage.evaluate((el: any) => el.click(), confirmRecoveryBtn[0]) 1285 | } catch (error) { 1286 | messageTransport.log(error) 1287 | } 1288 | 1289 | await localPage.waitForNavigation({ 1290 | waitUntil: 'networkidle0' 1291 | }) 1292 | const enterRecoveryXPath = "//*[normalize-space(text())='Enter recovery email address']" 1293 | await localPage.waitForXPath(enterRecoveryXPath) 1294 | await localPage.waitForTimeout(5000) 1295 | await localPage.focus('input[type="email"]') 1296 | await localPage.waitForTimeout(3000) 1297 | await localPage.type('input[type="email"]', recoveryemail, { delay: 100 }) 1298 | await localPage.keyboard.press('Enter') 1299 | await localPage.waitForNavigation({ 1300 | waitUntil: 'networkidle0' 1301 | }) 1302 | const uploadPopupSelector = 'ytcp-uploads-dialog' 1303 | await localPage.waitForSelector(uploadPopupSelector, { timeout: 60000 }) 1304 | } 1305 | 1306 | async function sleep(ms: number) { 1307 | return new Promise((sendMessage) => setTimeout(sendMessage, ms)) 1308 | } 1309 | 1310 | async function autoScroll(page: Page) { 1311 | await page.evaluate(`(async () => { 1312 | await new Promise((resolve, reject) => { 1313 | var totalHeight = 0; 1314 | var distance = 100; 1315 | var timer = setInterval(() => { 1316 | var scrollHeight = document.body.scrollHeight; 1317 | window.scrollBy(0, distance); 1318 | totalHeight += distance; 1319 | 1320 | if(totalHeight >= scrollHeight){ 1321 | clearInterval(timer); 1322 | resolve(0); 1323 | } 1324 | }, 100); 1325 | }); 1326 | })()`) 1327 | } 1328 | 1329 | async function scrollTillVeiw(page: Page, element: string) { 1330 | let sc = true 1331 | while (sc) { 1332 | try { 1333 | await page.focus(element) 1334 | sc = false 1335 | } catch (err) { 1336 | await autoScroll(page) 1337 | sc = true 1338 | } 1339 | } 1340 | return 1341 | } 1342 | 1343 | async function changeChannel(channelName: string) { 1344 | await page.goto('https://www.youtube.com/channel_switcher') 1345 | 1346 | const channelNameXPath = `//*[normalize-space(text())='${channelName}']` 1347 | const element = await page.waitForXPath(channelNameXPath) 1348 | 1349 | await element!.click() 1350 | 1351 | await page.waitForNavigation({ 1352 | waitUntil: 'networkidle0' 1353 | }) 1354 | } 1355 | 1356 | function escapeQuotesForXPath(str: string) { 1357 | // If the value contains only single or double quotes, construct 1358 | // an XPath literal 1359 | if (!str.includes('"')) { 1360 | return '"' + str + '"' 1361 | } 1362 | if (!str.includes("'")) { 1363 | return "'" + str + "'" 1364 | } 1365 | // If the value contains both single and double quotes, construct an 1366 | // expression that concatenates all non-double-quote substrings with 1367 | // the quotes, e.g.: 1368 | // 1369 | // concat("foo",'"',"bar") 1370 | 1371 | const parts: string[] = [] 1372 | // First, put a '"' after each component in the string. 1373 | for (const part of str.split('"')) { 1374 | if (part.length > 0) { 1375 | parts.push('"' + part + '"') 1376 | } 1377 | parts.push("'\"'") 1378 | } 1379 | // Then remove the extra '"' after the last component. 1380 | parts.pop() 1381 | // Finally, put it together into a concat() function call. 1382 | 1383 | return 'concat(' + parts.join(',') + ')' 1384 | } 1385 | 1386 | function xpathTextSelector(text: string, caseSensitive?: boolean, nthElement?: number) { 1387 | let xpathSelector = '' 1388 | if (caseSensitive) xpathSelector = `//*[contains(normalize-space(text()),"${text}")]` 1389 | else { 1390 | let uniqueText = [...new Set(text.split(''))].join('') 1391 | xpathSelector = `//*[contains(translate(normalize-space(text()),'${uniqueText.toUpperCase()}','${uniqueText.toLowerCase()}'),"${text 1392 | .toLowerCase() 1393 | .replace(/\s\s+/g, ' ')}")]` 1394 | } 1395 | if (nthElement) xpathSelector = `(${xpathSelector})[${nthElement + 1}]` 1396 | 1397 | return xpathSelector 1398 | } 1399 | 1400 | async function selectGame(page: Page, gameTitle: string, messageTransport: MessageTransport, gameSelector?: (arg0: GameData) => Promise | null): Promise { 1401 | const categoryDiv = await page.$('#category-container') 1402 | if (categoryDiv == null) { 1403 | messageTransport.warn(`selectGame: categoryDiv is null.`) 1404 | return false; 1405 | } 1406 | 1407 | // Press drop down to populate choices. 1408 | const categoryDropdownToggle = await categoryDiv.$('#category > ytcp-select > ytcp-text-dropdown-trigger') 1409 | await categoryDropdownToggle?.click() 1410 | await sleep(1000) 1411 | 1412 | const gamingCategoryButton = await page.$("[test-id='CREATOR_VIDEO_CATEGORY_GADGETS']") 1413 | if (!gamingCategoryButton) { 1414 | messageTransport.warn(`selectGame: Gaming category button not found.`) 1415 | return false; 1416 | } 1417 | 1418 | await gamingCategoryButton.click() 1419 | await sleep(500) 1420 | 1421 | // Wait for input. 1422 | const gameTitleBox = await categoryDiv.$('.ytcp-form-gaming input') 1423 | if (gameTitleBox == null) { 1424 | messageTransport.warn(`selectGame: gameTitleBox is null.`) 1425 | return false; 1426 | } 1427 | 1428 | // Type and call the game selector delegate. 1429 | await gameTitleBox.focus() 1430 | await gameTitleBox.type(gameTitle) 1431 | 1432 | // Wait for options. 1433 | const optionsSelectorHost = "#search-results > tp-yt-paper-dialog:not([aria-hidden='true'])" 1434 | const optionsPopupHost = await page.waitForSelector(optionsSelectorHost) 1435 | if (optionsPopupHost == null) { 1436 | messageTransport.warn(`selectGame: optionsPopupHost is null.`); 1437 | return false; 1438 | } 1439 | 1440 | const buttonOptions = await optionsPopupHost.$$('.selectable-item') 1441 | 1442 | // Check if we should select the option. 1443 | let pressed = false 1444 | for (let i = 0; i < buttonOptions.length; i++) { 1445 | const button = buttonOptions[i] 1446 | 1447 | let testId = await button.evaluate((el: Element) => el.getAttribute('test-id')) 1448 | if (testId == null || !testId.startsWith(`{"title"`)) continue 1449 | 1450 | let gameData = JSON.parse(testId) as GameData 1451 | if (gameSelector !== undefined && gameSelector !== null && !(await gameSelector(gameData))) continue 1452 | 1453 | await button.click() 1454 | pressed = true; 1455 | break 1456 | } 1457 | 1458 | if (!pressed && buttonOptions.length != 0) { 1459 | // Just select none. 1460 | await buttonOptions[0].click(); 1461 | return false; 1462 | } 1463 | return true; 1464 | } 1465 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "lib": [ 6 | "DOM", 7 | "ESNext", 8 | "ESNext.Array", 9 | "ESNext.AsyncIterable" 10 | ], 11 | "declaration": true, 12 | "declarationMap": true, 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "resolveJsonModule": true, 16 | "strict": true, 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true 20 | }, 21 | "include": [ 22 | "src/**/*" 23 | ] 24 | } --------------------------------------------------------------------------------