├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── images └── example.png ├── package.json ├── pnpm-lock.yaml ├── src ├── assets │ └── oldSongs.json ├── config.ts ├── functions │ ├── getKsoft.ts │ ├── getLocalSongs.ts │ ├── getMetadata.ts │ ├── getTurkeyTime.ts │ ├── getVideoId.ts │ ├── getYoutubeFromSpotify.ts │ ├── isUrl.ts │ ├── logger.ts │ └── mergeDocs.ts ├── index.ts ├── structures │ ├── Firebase.ts │ └── Questions.ts └── types │ ├── Response │ ├── Ksoft.d.ts │ ├── README.md │ └── Youtube.ts │ └── Song.d.ts ├── start.bat ├── start.sh └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | FIREBASE_PROJECT_ID="" 2 | FIREBASE_API_KEY="" 3 | FIREBASE_APP_ID="" 4 | FIREBASE_USER="" 5 | FIREBASE_PASSWORD="" 6 | 7 | YOUTUBE_API_KEY="" 8 | 9 | SPOTIFY_CLIENT_ID="" 10 | SPOTIFY_CLIENT_SECRET="" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Abdulbaki "EGGSY" Dursun 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 | # Firestore Checker 2 | 3 | 🔬 The tool I use to add songs to [my Daily Song page](https://eggsy.xyz/daily) on my website. It's an essential tool to me and helps me to fetch different data from different APIs easily and automatically. 4 | 5 | ### Example 6 | 7 |

8 | 9 | ![image](images/example.png) 10 | 11 |

12 | 13 | ### What it does? 14 | 15 | - Reads YouTube video ID from URL. 16 | - Fetches YouTube metadata such as title, artist and thumbnail. 17 | - Works with Spotify URLs (converts them to YouTube video IDs for the embed). 18 | - ~~Fetches lyrics and Spotify song URL from KSoft API.~~ (depreceated) 19 | 20 | ### Why? 21 | 22 | I like to share the music I listen and like. I've been doing this ever since I added a feature called "daily" to my old Discord bot. All songs were stored on a MongoDB collection along with bot's other collections and every new day, bot would send a different like according to the date. 23 | 24 | This way I can share my music with my friends, and after I shut my bot down, I still wanted to share my music, so I created my first blog page using Express, Vue and Nuxt. After the huge refactor to Tailwind CSS and the current version of the website. I now have a Daily page where I show the songs of last 10 days along with the today's song. This tool helps me to fetch all required data from APIs and add a record to Firestore collection. 25 | 26 | ### Who needs this? 27 | 28 | Probably no one. 29 | 30 | ### Furthermore 31 | 32 | Sorry, I won't be putting any build or start steps because if you really want to use this, you'd have to figure your way out. You'll need a Firebase account, a Firestore database, a user to be able to authenticate and add songs, rules to make sure of the security, only allow your user to add/delete/edit documents. 33 | 34 | If you don't have a website, or you have a backend tool (like if you have a Discord bot) you can edit the source file and delete the lines where it authenticates as a user. You can do this if it's safe to allow every user who can get your Firestore app information. It's usually safe for things like Discord bots since people won't be able to see those details, but if you own a website, people might find and control your database. Please be careful. 35 | -------------------------------------------------------------------------------- /images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggsy/firestore-checker/973b7e090c2b9c8f4f2389f8325edd41cefb290c/images/example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firestore-checker", 3 | "version": "2.5.0", 4 | "description": "The tool I use to add new songs with YouTube information, lyrics, and Spotify URL to my Daily Song page.", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "author": { 8 | "email": "eggsydev@gmail.com", 9 | "name": "Abdulbaki 'EGGSY' Dursun", 10 | "url": "https://eggsy.xyz" 11 | }, 12 | "repository": { 13 | "type": "github", 14 | "url": "https://github.com/eggsy/firestore-checker" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/eggsy/firestore-checker/issues" 18 | }, 19 | "scripts": { 20 | "dev": "cross-env NODE_ENV=development ts-node-dev --respawn src/index.ts", 21 | "build": "cross-env NODE_ENV=production rimraf dist && pnpm i --reporter=silent && tsc && pnpm copy-files", 22 | "copy-files": "copyfiles -u 1 src/assets/*.json dist/", 23 | "start": "cd dist && node index.js" 24 | }, 25 | "dependencies": { 26 | "@types/spotify-web-api-node": "^5.0.6", 27 | "axios": "^0.24.0", 28 | "consola": "^2.15.3", 29 | "dotenv": "^10.0.0", 30 | "firebase": "^9.6.4", 31 | "get-next-date": "^1.0.0", 32 | "moment": "^2.29.1", 33 | "prompts": "^2.4.2", 34 | "rimraf": "^3.0.2", 35 | "spotify-to-youtube": "^1.0.2", 36 | "spotify-web-api-node": "^5.0.2", 37 | "typescript": "^4.5.5" 38 | }, 39 | "devDependencies": { 40 | "copyfiles": "^2.4.1", 41 | "cross-env": "^7.0.3", 42 | "ts-node-dev": "^1.1.8" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@types/spotify-web-api-node': ^5.0.6 5 | axios: ^0.24.0 6 | consola: ^2.15.3 7 | copyfiles: ^2.4.1 8 | cross-env: ^7.0.3 9 | dotenv: ^10.0.0 10 | firebase: ^9.6.4 11 | get-next-date: ^1.0.0 12 | moment: ^2.29.1 13 | prompts: ^2.4.2 14 | rimraf: ^3.0.2 15 | spotify-to-youtube: ^1.0.2 16 | spotify-web-api-node: ^5.0.2 17 | ts-node-dev: ^1.1.8 18 | typescript: ^4.5.5 19 | 20 | dependencies: 21 | '@types/spotify-web-api-node': 5.0.6 22 | axios: 0.24.0 23 | consola: 2.15.3 24 | dotenv: 10.0.0 25 | firebase: 9.6.4 26 | get-next-date: 1.0.0 27 | moment: 2.29.1 28 | prompts: 2.4.2 29 | rimraf: 3.0.2 30 | spotify-to-youtube: 1.0.2 31 | spotify-web-api-node: 5.0.2 32 | typescript: 4.5.5 33 | 34 | devDependencies: 35 | copyfiles: 2.4.1 36 | cross-env: 7.0.3 37 | ts-node-dev: 1.1.8_typescript@4.5.5 38 | 39 | packages: 40 | 41 | /@firebase/analytics-compat/0.1.6_8c68ed8df7b66473d54007d382e86e85: 42 | resolution: {integrity: sha512-xvdp4/zwOG1f+v9JSpfCQoPJ98HcJR42cEnZ9pRIQLmUy7L7QceIuaF3m+zVtoqa4agBQnJ1dhe58FshOFKOPw==} 43 | peerDependencies: 44 | '@firebase/app-compat': 0.x 45 | dependencies: 46 | '@firebase/analytics': 0.7.5_@firebase+app@0.7.14 47 | '@firebase/analytics-types': 0.7.0 48 | '@firebase/app-compat': 0.1.15 49 | '@firebase/component': 0.5.10 50 | '@firebase/util': 1.4.3 51 | tslib: 2.3.1 52 | transitivePeerDependencies: 53 | - '@firebase/app' 54 | dev: false 55 | 56 | /@firebase/analytics-types/0.7.0: 57 | resolution: {integrity: sha512-DNE2Waiwy5+zZnCfintkDtBfaW6MjIG883474v6Z0K1XZIvl76cLND4iv0YUb48leyF+PJK1KO2XrgHb/KpmhQ==} 58 | dev: false 59 | 60 | /@firebase/analytics/0.7.5_@firebase+app@0.7.14: 61 | resolution: {integrity: sha512-vrKDh84hBbKPJaU2oAZDewyC79D8opJOQZ5AU3BXBBwEfRjKt3C3jj/Vl6aJUme+RKXlomTw3xcHIOoPzTgBVA==} 62 | peerDependencies: 63 | '@firebase/app': 0.x 64 | dependencies: 65 | '@firebase/app': 0.7.14 66 | '@firebase/component': 0.5.10 67 | '@firebase/installations': 0.5.5_@firebase+app@0.7.14 68 | '@firebase/logger': 0.3.2 69 | '@firebase/util': 1.4.3 70 | tslib: 2.3.1 71 | dev: false 72 | 73 | /@firebase/app-check-compat/0.2.3_8c68ed8df7b66473d54007d382e86e85: 74 | resolution: {integrity: sha512-e2mKkuecr1XgsyTGXKfg83PcV1UdT7+tXYoHIjeBeLrP5gGL4OQbWCzzt6uVQpk1gmJbUktje/rd6Et6cdL+wg==} 75 | peerDependencies: 76 | '@firebase/app-compat': 0.x 77 | dependencies: 78 | '@firebase/app-check': 0.5.3_@firebase+app@0.7.14 79 | '@firebase/app-compat': 0.1.15 80 | '@firebase/component': 0.5.10 81 | '@firebase/logger': 0.3.2 82 | '@firebase/util': 1.4.3 83 | tslib: 2.3.1 84 | transitivePeerDependencies: 85 | - '@firebase/app' 86 | dev: false 87 | 88 | /@firebase/app-check-interop-types/0.1.0: 89 | resolution: {integrity: sha512-uZfn9s4uuRsaX5Lwx+gFP3B6YsyOKUE+Rqa6z9ojT4VSRAsZFko9FRn6OxQUA1z5t5d08fY4pf+/+Dkd5wbdbA==} 90 | dev: false 91 | 92 | /@firebase/app-check/0.5.3_@firebase+app@0.7.14: 93 | resolution: {integrity: sha512-M2/UO5PgxHCl0wPYWGdF6lO8nqclwuRMCIrc+75xv3/Dr3hhUu4ztF5JNaAV5tktSCt1UrnASG+4rNVifCzSRw==} 94 | peerDependencies: 95 | '@firebase/app': 0.x 96 | dependencies: 97 | '@firebase/app': 0.7.14 98 | '@firebase/component': 0.5.10 99 | '@firebase/logger': 0.3.2 100 | '@firebase/util': 1.4.3 101 | tslib: 2.3.1 102 | dev: false 103 | 104 | /@firebase/app-compat/0.1.15: 105 | resolution: {integrity: sha512-hfk6eHKUb+qHPZLtQuUdNFY4gTYD47Q64oajKAtb6r6YcErYvxRoZPDJ2Xjv4PCioG+3q+f0YDUKfMJlRtK0IA==} 106 | dependencies: 107 | '@firebase/app': 0.7.14 108 | '@firebase/component': 0.5.10 109 | '@firebase/logger': 0.3.2 110 | '@firebase/util': 1.4.3 111 | tslib: 2.3.1 112 | dev: false 113 | 114 | /@firebase/app-types/0.7.0: 115 | resolution: {integrity: sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==} 116 | dev: false 117 | 118 | /@firebase/app/0.7.14: 119 | resolution: {integrity: sha512-15Pv8Rt45GlDwqlnFRXqlNjjERx2VkWDyWGLsXlCZYg9+F8c++GTWezacFoZfkLzUZ4W54iyofDujfUadzHv1g==} 120 | dependencies: 121 | '@firebase/component': 0.5.10 122 | '@firebase/logger': 0.3.2 123 | '@firebase/util': 1.4.3 124 | tslib: 2.3.1 125 | dev: false 126 | 127 | /@firebase/auth-compat/0.2.6_ac0404923be6fa57942dda687cee29d1: 128 | resolution: {integrity: sha512-gBxhGGJ/jiL4LfMC6Nlx7LteKNMlKV+0SsyrXT3SFzpseCX8ayOVo+qKCTAkTJqupm4li1lIgwIXhrD2OsbHBw==} 129 | peerDependencies: 130 | '@firebase/app-compat': 0.x 131 | dependencies: 132 | '@firebase/app-compat': 0.1.15 133 | '@firebase/auth': 0.19.6_@firebase+app@0.7.14 134 | '@firebase/auth-types': 0.11.0_921e789eab5feb003723e0d148cd47c5 135 | '@firebase/component': 0.5.10 136 | '@firebase/util': 1.4.3 137 | node-fetch: 2.6.5 138 | selenium-webdriver: 4.1.1 139 | tslib: 2.3.1 140 | transitivePeerDependencies: 141 | - '@firebase/app' 142 | - '@firebase/app-types' 143 | - bufferutil 144 | - utf-8-validate 145 | dev: false 146 | 147 | /@firebase/auth-interop-types/0.1.6_921e789eab5feb003723e0d148cd47c5: 148 | resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==} 149 | peerDependencies: 150 | '@firebase/app-types': 0.x 151 | '@firebase/util': 1.x 152 | dependencies: 153 | '@firebase/app-types': 0.7.0 154 | '@firebase/util': 1.4.3 155 | dev: false 156 | 157 | /@firebase/auth-types/0.11.0_921e789eab5feb003723e0d148cd47c5: 158 | resolution: {integrity: sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==} 159 | peerDependencies: 160 | '@firebase/app-types': 0.x 161 | '@firebase/util': 1.x 162 | dependencies: 163 | '@firebase/app-types': 0.7.0 164 | '@firebase/util': 1.4.3 165 | dev: false 166 | 167 | /@firebase/auth/0.19.6_@firebase+app@0.7.14: 168 | resolution: {integrity: sha512-hlIX9jJWk/4E6JiEf9H4LLv8dNJLR17iryR1MaC58Y4U9ioYcxz8q7NMSUTyr5mJjYD/YBWrQXkE1V+gUnMnJw==} 169 | peerDependencies: 170 | '@firebase/app': 0.x 171 | dependencies: 172 | '@firebase/app': 0.7.14 173 | '@firebase/component': 0.5.10 174 | '@firebase/logger': 0.3.2 175 | '@firebase/util': 1.4.3 176 | node-fetch: 2.6.5 177 | selenium-webdriver: 4.0.0-rc-1 178 | tslib: 2.3.1 179 | transitivePeerDependencies: 180 | - bufferutil 181 | - utf-8-validate 182 | dev: false 183 | 184 | /@firebase/component/0.5.10: 185 | resolution: {integrity: sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==} 186 | dependencies: 187 | '@firebase/util': 1.4.3 188 | tslib: 2.3.1 189 | dev: false 190 | 191 | /@firebase/database-compat/0.1.5_bf6a48fc8e85e6523d4715f1396e1d1c: 192 | resolution: {integrity: sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==} 193 | peerDependencies: 194 | '@firebase/app-compat': 0.x 195 | dependencies: 196 | '@firebase/app-compat': 0.1.15 197 | '@firebase/component': 0.5.10 198 | '@firebase/database': 0.12.5_@firebase+app-types@0.7.0 199 | '@firebase/database-types': 0.9.4 200 | '@firebase/logger': 0.3.2 201 | '@firebase/util': 1.4.3 202 | tslib: 2.3.1 203 | transitivePeerDependencies: 204 | - '@firebase/app-types' 205 | dev: false 206 | 207 | /@firebase/database-types/0.9.4: 208 | resolution: {integrity: sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==} 209 | dependencies: 210 | '@firebase/app-types': 0.7.0 211 | '@firebase/util': 1.4.3 212 | dev: false 213 | 214 | /@firebase/database/0.12.5_@firebase+app-types@0.7.0: 215 | resolution: {integrity: sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==} 216 | dependencies: 217 | '@firebase/auth-interop-types': 0.1.6_921e789eab5feb003723e0d148cd47c5 218 | '@firebase/component': 0.5.10 219 | '@firebase/logger': 0.3.2 220 | '@firebase/util': 1.4.3 221 | faye-websocket: 0.11.4 222 | tslib: 2.3.1 223 | transitivePeerDependencies: 224 | - '@firebase/app-types' 225 | dev: false 226 | 227 | /@firebase/firestore-compat/0.1.12_ac0404923be6fa57942dda687cee29d1: 228 | resolution: {integrity: sha512-+8FwiYctRc5Vwa59iGD6IdTNCKqgZYB6yl/PvDJfi+WNhJbMznpHYWBI+urNGHAXBpHRDCwJS08LVsVTsBsS0w==} 229 | peerDependencies: 230 | '@firebase/app-compat': 0.x 231 | dependencies: 232 | '@firebase/app-compat': 0.1.15 233 | '@firebase/component': 0.5.10 234 | '@firebase/firestore': 3.4.3_@firebase+app@0.7.14 235 | '@firebase/firestore-types': 2.5.0_921e789eab5feb003723e0d148cd47c5 236 | '@firebase/util': 1.4.3 237 | tslib: 2.3.1 238 | transitivePeerDependencies: 239 | - '@firebase/app' 240 | - '@firebase/app-types' 241 | dev: false 242 | 243 | /@firebase/firestore-types/2.5.0_921e789eab5feb003723e0d148cd47c5: 244 | resolution: {integrity: sha512-I6c2m1zUhZ5SH0cWPmINabDyH5w0PPFHk2UHsjBpKdZllzJZ2TwTkXbDtpHUZNmnc/zAa0WNMNMvcvbb/xJLKA==} 245 | peerDependencies: 246 | '@firebase/app-types': 0.x 247 | '@firebase/util': 1.x 248 | dependencies: 249 | '@firebase/app-types': 0.7.0 250 | '@firebase/util': 1.4.3 251 | dev: false 252 | 253 | /@firebase/firestore/3.4.3_@firebase+app@0.7.14: 254 | resolution: {integrity: sha512-mUZY/aTKpliCyoYs7/64olumeTbM42axu2u8QDl28AX+4q7vHGIiks9+H2gaqz/zgWODXiQeBmJlHCb1RlJGhQ==} 255 | engines: {node: '>=10.10.0'} 256 | peerDependencies: 257 | '@firebase/app': 0.x 258 | dependencies: 259 | '@firebase/app': 0.7.14 260 | '@firebase/component': 0.5.10 261 | '@firebase/logger': 0.3.2 262 | '@firebase/util': 1.4.3 263 | '@firebase/webchannel-wrapper': 0.6.1 264 | '@grpc/grpc-js': 1.5.3 265 | '@grpc/proto-loader': 0.6.9 266 | node-fetch: 2.6.5 267 | tslib: 2.3.1 268 | dev: false 269 | 270 | /@firebase/functions-compat/0.1.8_ac0404923be6fa57942dda687cee29d1: 271 | resolution: {integrity: sha512-9nB6uPzSbnzOE+V7USbHsQxze/xeJC5WTgBOhyHA8eEU/z5mBGfD1eV31QbI7mbSFL8m4N8F5cidDw3zB1G/Jw==} 272 | peerDependencies: 273 | '@firebase/app-compat': 0.x 274 | dependencies: 275 | '@firebase/app-compat': 0.1.15 276 | '@firebase/component': 0.5.10 277 | '@firebase/functions': 0.7.7_6fe50591265b03be0da1ae819dd57237 278 | '@firebase/functions-types': 0.5.0 279 | '@firebase/util': 1.4.3 280 | tslib: 2.3.1 281 | transitivePeerDependencies: 282 | - '@firebase/app' 283 | - '@firebase/app-types' 284 | dev: false 285 | 286 | /@firebase/functions-types/0.5.0: 287 | resolution: {integrity: sha512-qza0M5EwX+Ocrl1cYI14zoipUX4gI/Shwqv0C1nB864INAD42Dgv4v94BCyxGHBg2kzlWy8PNafdP7zPO8aJQA==} 288 | dev: false 289 | 290 | /@firebase/functions/0.7.7_6fe50591265b03be0da1ae819dd57237: 291 | resolution: {integrity: sha512-e944UigvrqwGHODww8QU1oaZ+KFdqcf/hmf5L2vEakQEIOjCRy6Kal8xAlYpaP4QbC1DEUfY4qC9QoFUErI2fQ==} 292 | peerDependencies: 293 | '@firebase/app': 0.x 294 | dependencies: 295 | '@firebase/app': 0.7.14 296 | '@firebase/app-check-interop-types': 0.1.0 297 | '@firebase/auth-interop-types': 0.1.6_921e789eab5feb003723e0d148cd47c5 298 | '@firebase/component': 0.5.10 299 | '@firebase/messaging-interop-types': 0.1.0 300 | '@firebase/util': 1.4.3 301 | node-fetch: 2.6.5 302 | tslib: 2.3.1 303 | transitivePeerDependencies: 304 | - '@firebase/app-types' 305 | dev: false 306 | 307 | /@firebase/installations/0.5.5_@firebase+app@0.7.14: 308 | resolution: {integrity: sha512-mYWUxYXPlxcR0YOikPw88TjIS2NK35Z0ivkJL0+FevNnVIsqwGSe12AtPlZB/kzjB0RtHoKW+cWC0V9xiTgJ3Q==} 309 | peerDependencies: 310 | '@firebase/app': 0.x 311 | dependencies: 312 | '@firebase/app': 0.7.14 313 | '@firebase/component': 0.5.10 314 | '@firebase/util': 1.4.3 315 | idb: 3.0.2 316 | tslib: 2.3.1 317 | dev: false 318 | 319 | /@firebase/logger/0.3.2: 320 | resolution: {integrity: sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==} 321 | dependencies: 322 | tslib: 2.3.1 323 | dev: false 324 | 325 | /@firebase/messaging-compat/0.1.7_8c68ed8df7b66473d54007d382e86e85: 326 | resolution: {integrity: sha512-EKKcMin7myL8GXu2Mq6daje6o13VZtgxUKe+sLVL4DfxPbJdhVvNirYTi14KM81tDPdKAIakZNIGiIm/r8S2zA==} 327 | peerDependencies: 328 | '@firebase/app-compat': 0.x 329 | dependencies: 330 | '@firebase/app-compat': 0.1.15 331 | '@firebase/component': 0.5.10 332 | '@firebase/messaging': 0.9.7_@firebase+app@0.7.14 333 | '@firebase/util': 1.4.3 334 | tslib: 2.3.1 335 | transitivePeerDependencies: 336 | - '@firebase/app' 337 | dev: false 338 | 339 | /@firebase/messaging-interop-types/0.1.0: 340 | resolution: {integrity: sha512-DbvUl/rXAZpQeKBnwz0NYY5OCqr2nFA0Bj28Fmr3NXGqR4PAkfTOHuQlVtLO1Nudo3q0HxAYLa68ZDAcuv2uKQ==} 341 | dev: false 342 | 343 | /@firebase/messaging/0.9.7_@firebase+app@0.7.14: 344 | resolution: {integrity: sha512-qRPWO5fvS1yahe0As0rlpo+Gc3v5IKHlULGJPYvWGJWE3W5aCvooxsT3vYgYNmAMFjgLlugfg8KKd41O1ebgqg==} 345 | peerDependencies: 346 | '@firebase/app': 0.x 347 | dependencies: 348 | '@firebase/app': 0.7.14 349 | '@firebase/component': 0.5.10 350 | '@firebase/installations': 0.5.5_@firebase+app@0.7.14 351 | '@firebase/messaging-interop-types': 0.1.0 352 | '@firebase/util': 1.4.3 353 | idb: 3.0.2 354 | tslib: 2.3.1 355 | dev: false 356 | 357 | /@firebase/performance-compat/0.1.5_8c68ed8df7b66473d54007d382e86e85: 358 | resolution: {integrity: sha512-s9mqR0GXJaqvIZD/GsshacpKOGa3NP6Yht33mNEtpL7ERqj35mvD1CBoUwH52eMYAaxlQd9y9JrphQgK3EmWWw==} 359 | peerDependencies: 360 | '@firebase/app-compat': 0.x 361 | dependencies: 362 | '@firebase/app-compat': 0.1.15 363 | '@firebase/component': 0.5.10 364 | '@firebase/logger': 0.3.2 365 | '@firebase/performance': 0.5.5_@firebase+app@0.7.14 366 | '@firebase/performance-types': 0.1.0 367 | '@firebase/util': 1.4.3 368 | tslib: 2.3.1 369 | transitivePeerDependencies: 370 | - '@firebase/app' 371 | dev: false 372 | 373 | /@firebase/performance-types/0.1.0: 374 | resolution: {integrity: sha512-6p1HxrH0mpx+622Ql6fcxFxfkYSBpE3LSuwM7iTtYU2nw91Hj6THC8Bc8z4nboIq7WvgsT/kOTYVVZzCSlXl8w==} 375 | dev: false 376 | 377 | /@firebase/performance/0.5.5_@firebase+app@0.7.14: 378 | resolution: {integrity: sha512-eA8mEKVnyY64fwAKxHbJF5t1hNkdR0EZVib0LfEWl/2elPmFcjik097hqLHzdFE88JYCxNGfFaSPo9Lbk/qe6A==} 379 | peerDependencies: 380 | '@firebase/app': 0.x 381 | dependencies: 382 | '@firebase/app': 0.7.14 383 | '@firebase/component': 0.5.10 384 | '@firebase/installations': 0.5.5_@firebase+app@0.7.14 385 | '@firebase/logger': 0.3.2 386 | '@firebase/util': 1.4.3 387 | tslib: 2.3.1 388 | dev: false 389 | 390 | /@firebase/polyfill/0.3.36: 391 | resolution: {integrity: sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==} 392 | dependencies: 393 | core-js: 3.6.5 394 | promise-polyfill: 8.1.3 395 | whatwg-fetch: 2.0.4 396 | dev: false 397 | 398 | /@firebase/remote-config-compat/0.1.5_8c68ed8df7b66473d54007d382e86e85: 399 | resolution: {integrity: sha512-bgpmrCGyOj46c0xNFvivcXRHlaVkbt4mX2etbF9s6jaOILPd4rBHIfAiBpKL64GGwTkrOjWO9/HZun4I01gbpg==} 400 | peerDependencies: 401 | '@firebase/app-compat': 0.x 402 | dependencies: 403 | '@firebase/app-compat': 0.1.15 404 | '@firebase/component': 0.5.10 405 | '@firebase/logger': 0.3.2 406 | '@firebase/remote-config': 0.3.4_@firebase+app@0.7.14 407 | '@firebase/remote-config-types': 0.2.0 408 | '@firebase/util': 1.4.3 409 | tslib: 2.3.1 410 | transitivePeerDependencies: 411 | - '@firebase/app' 412 | dev: false 413 | 414 | /@firebase/remote-config-types/0.2.0: 415 | resolution: {integrity: sha512-hqK5sCPeZvcHQ1D6VjJZdW6EexLTXNMJfPdTwbD8NrXUw6UjWC4KWhLK/TSlL0QPsQtcKRkaaoP+9QCgKfMFPw==} 416 | dev: false 417 | 418 | /@firebase/remote-config/0.3.4_@firebase+app@0.7.14: 419 | resolution: {integrity: sha512-SLlyVVNJ6DnU1AOjNrmv5u9Fge7gUwZVooyxMIkaT3Lj9MBM5MwfJsoG3UyiV4l7yI0iPj34LuKPpMJXOOcs4w==} 420 | peerDependencies: 421 | '@firebase/app': 0.x 422 | dependencies: 423 | '@firebase/app': 0.7.14 424 | '@firebase/component': 0.5.10 425 | '@firebase/installations': 0.5.5_@firebase+app@0.7.14 426 | '@firebase/logger': 0.3.2 427 | '@firebase/util': 1.4.3 428 | tslib: 2.3.1 429 | dev: false 430 | 431 | /@firebase/storage-compat/0.1.9_ac0404923be6fa57942dda687cee29d1: 432 | resolution: {integrity: sha512-FwSNw1FMH8Qk9l+nDmlamesEFVjOfmWO4B2BV4l3YRn5ibvxIvBqRQZP8TGUknHCWKM1b7dMq3C19cVxeJ77VQ==} 433 | peerDependencies: 434 | '@firebase/app-compat': 0.x 435 | dependencies: 436 | '@firebase/app-compat': 0.1.15 437 | '@firebase/component': 0.5.10 438 | '@firebase/storage': 0.9.1_@firebase+app@0.7.14 439 | '@firebase/storage-types': 0.6.0_921e789eab5feb003723e0d148cd47c5 440 | '@firebase/util': 1.4.3 441 | tslib: 2.3.1 442 | transitivePeerDependencies: 443 | - '@firebase/app' 444 | - '@firebase/app-types' 445 | dev: false 446 | 447 | /@firebase/storage-types/0.6.0_921e789eab5feb003723e0d148cd47c5: 448 | resolution: {integrity: sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA==} 449 | peerDependencies: 450 | '@firebase/app-types': 0.x 451 | '@firebase/util': 1.x 452 | dependencies: 453 | '@firebase/app-types': 0.7.0 454 | '@firebase/util': 1.4.3 455 | dev: false 456 | 457 | /@firebase/storage/0.9.1_@firebase+app@0.7.14: 458 | resolution: {integrity: sha512-IMPZ21Mm05R9GKTgiiMpbata0tgzQTtZ2YMbVReSTx16GJTIpadXpjFzxhJMjVi/7Wq57LnSxsg9fe56IBSacw==} 459 | peerDependencies: 460 | '@firebase/app': 0.x 461 | dependencies: 462 | '@firebase/app': 0.7.14 463 | '@firebase/component': 0.5.10 464 | '@firebase/util': 1.4.3 465 | node-fetch: 2.6.5 466 | tslib: 2.3.1 467 | dev: false 468 | 469 | /@firebase/util/1.4.3: 470 | resolution: {integrity: sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==} 471 | dependencies: 472 | tslib: 2.3.1 473 | dev: false 474 | 475 | /@firebase/webchannel-wrapper/0.6.1: 476 | resolution: {integrity: sha512-9FqhNjKQWpQ3fGnSOCovHOm+yhhiorKEqYLAfd525jWavunDJcx8rOW6i6ozAh+FbwcYMkL7b+3j4UR/30MpoQ==} 477 | dev: false 478 | 479 | /@grpc/grpc-js/1.5.3: 480 | resolution: {integrity: sha512-q0xgaZ3ymUM+ZOhe1hdocVSdKHCnJ6llLSXcP+MqMXMyYPUZ3mzQOCxZ3Zkg+QZ7sZ950sn7hvueQrIJZumPZg==} 481 | engines: {node: ^8.13.0 || >=10.10.0} 482 | dependencies: 483 | '@grpc/proto-loader': 0.6.9 484 | '@types/node': 17.0.10 485 | dev: false 486 | 487 | /@grpc/proto-loader/0.6.9: 488 | resolution: {integrity: sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==} 489 | engines: {node: '>=6'} 490 | hasBin: true 491 | dependencies: 492 | '@types/long': 4.0.1 493 | lodash.camelcase: 4.3.0 494 | long: 4.0.0 495 | protobufjs: 6.11.2 496 | yargs: 16.2.0 497 | dev: false 498 | 499 | /@protobufjs/aspromise/1.1.2: 500 | resolution: {integrity: sha1-m4sMxmPWaafY9vXQiToU00jzD78=} 501 | dev: false 502 | 503 | /@protobufjs/base64/1.1.2: 504 | resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} 505 | dev: false 506 | 507 | /@protobufjs/codegen/2.0.4: 508 | resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} 509 | dev: false 510 | 511 | /@protobufjs/eventemitter/1.1.0: 512 | resolution: {integrity: sha1-NVy8mLr61ZePntCV85diHx0Ga3A=} 513 | dev: false 514 | 515 | /@protobufjs/fetch/1.1.0: 516 | resolution: {integrity: sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=} 517 | dependencies: 518 | '@protobufjs/aspromise': 1.1.2 519 | '@protobufjs/inquire': 1.1.0 520 | dev: false 521 | 522 | /@protobufjs/float/1.0.2: 523 | resolution: {integrity: sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=} 524 | dev: false 525 | 526 | /@protobufjs/inquire/1.1.0: 527 | resolution: {integrity: sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=} 528 | dev: false 529 | 530 | /@protobufjs/path/1.1.2: 531 | resolution: {integrity: sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=} 532 | dev: false 533 | 534 | /@protobufjs/pool/1.1.0: 535 | resolution: {integrity: sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=} 536 | dev: false 537 | 538 | /@protobufjs/utf8/1.1.0: 539 | resolution: {integrity: sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=} 540 | dev: false 541 | 542 | /@types/long/4.0.1: 543 | resolution: {integrity: sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==} 544 | dev: false 545 | 546 | /@types/node/17.0.10: 547 | resolution: {integrity: sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==} 548 | dev: false 549 | 550 | /@types/spotify-api/0.0.13: 551 | resolution: {integrity: sha512-rHmG4soR13N3jDCu0QL9cv6q3jMM/AU/0w5w7ukTt89e23rv5Vz86BNqXP/34wEzYWgiTNSG+f+xGT1rjABj4g==} 552 | dev: false 553 | 554 | /@types/spotify-web-api-node/5.0.6: 555 | resolution: {integrity: sha512-2zu9z9mtML5W5Q2cgtuu/FQ/KeZuOJ6ruKya5BUgyOKRGMamFbTe8Z/00TqRlm6MGDUWBcPKY4BZZAxhwPozgQ==} 556 | dependencies: 557 | '@types/spotify-api': 0.0.13 558 | dev: false 559 | 560 | /@types/strip-bom/3.0.0: 561 | resolution: {integrity: sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=} 562 | dev: true 563 | 564 | /@types/strip-json-comments/0.0.30: 565 | resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} 566 | dev: true 567 | 568 | /ansi-regex/5.0.1: 569 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 570 | engines: {node: '>=8'} 571 | 572 | /ansi-styles/4.3.0: 573 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 574 | engines: {node: '>=8'} 575 | dependencies: 576 | color-convert: 2.0.1 577 | 578 | /anymatch/3.1.2: 579 | resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} 580 | engines: {node: '>= 8'} 581 | dependencies: 582 | normalize-path: 3.0.0 583 | picomatch: 2.3.1 584 | dev: true 585 | 586 | /arg/4.1.3: 587 | resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} 588 | dev: true 589 | 590 | /asynckit/0.4.0: 591 | resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} 592 | dev: false 593 | 594 | /axios/0.19.2: 595 | resolution: {integrity: sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==} 596 | deprecated: Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410 597 | dependencies: 598 | follow-redirects: 1.5.10 599 | dev: false 600 | 601 | /axios/0.24.0: 602 | resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} 603 | dependencies: 604 | follow-redirects: 1.14.7 605 | transitivePeerDependencies: 606 | - debug 607 | dev: false 608 | 609 | /balanced-match/1.0.2: 610 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 611 | 612 | /binary-extensions/2.2.0: 613 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 614 | engines: {node: '>=8'} 615 | dev: true 616 | 617 | /brace-expansion/1.1.11: 618 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 619 | dependencies: 620 | balanced-match: 1.0.2 621 | concat-map: 0.0.1 622 | 623 | /braces/3.0.2: 624 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 625 | engines: {node: '>=8'} 626 | dependencies: 627 | fill-range: 7.0.1 628 | dev: true 629 | 630 | /buffer-from/1.1.2: 631 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 632 | dev: true 633 | 634 | /call-bind/1.0.2: 635 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} 636 | dependencies: 637 | function-bind: 1.1.1 638 | get-intrinsic: 1.1.1 639 | dev: false 640 | 641 | /chokidar/3.5.3: 642 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 643 | engines: {node: '>= 8.10.0'} 644 | dependencies: 645 | anymatch: 3.1.2 646 | braces: 3.0.2 647 | glob-parent: 5.1.2 648 | is-binary-path: 2.1.0 649 | is-glob: 4.0.3 650 | normalize-path: 3.0.0 651 | readdirp: 3.6.0 652 | optionalDependencies: 653 | fsevents: 2.3.2 654 | dev: true 655 | 656 | /cliui/7.0.4: 657 | resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} 658 | dependencies: 659 | string-width: 4.2.3 660 | strip-ansi: 6.0.1 661 | wrap-ansi: 7.0.0 662 | 663 | /color-convert/2.0.1: 664 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 665 | engines: {node: '>=7.0.0'} 666 | dependencies: 667 | color-name: 1.1.4 668 | 669 | /color-name/1.1.4: 670 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 671 | 672 | /combined-stream/1.0.8: 673 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 674 | engines: {node: '>= 0.8'} 675 | dependencies: 676 | delayed-stream: 1.0.0 677 | dev: false 678 | 679 | /component-emitter/1.3.0: 680 | resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} 681 | dev: false 682 | 683 | /concat-map/0.0.1: 684 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 685 | 686 | /consola/2.15.3: 687 | resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} 688 | dev: false 689 | 690 | /cookiejar/2.1.3: 691 | resolution: {integrity: sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==} 692 | dev: false 693 | 694 | /copyfiles/2.4.1: 695 | resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} 696 | hasBin: true 697 | dependencies: 698 | glob: 7.2.0 699 | minimatch: 3.0.4 700 | mkdirp: 1.0.4 701 | noms: 0.0.0 702 | through2: 2.0.5 703 | untildify: 4.0.0 704 | yargs: 16.2.0 705 | dev: true 706 | 707 | /core-js/3.6.5: 708 | resolution: {integrity: sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==} 709 | requiresBuild: true 710 | dev: false 711 | 712 | /core-util-is/1.0.3: 713 | resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} 714 | 715 | /create-require/1.1.1: 716 | resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} 717 | dev: true 718 | 719 | /cross-env/7.0.3: 720 | resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} 721 | engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} 722 | hasBin: true 723 | dependencies: 724 | cross-spawn: 7.0.3 725 | dev: true 726 | 727 | /cross-spawn/7.0.3: 728 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 729 | engines: {node: '>= 8'} 730 | dependencies: 731 | path-key: 3.1.1 732 | shebang-command: 2.0.0 733 | which: 2.0.2 734 | dev: true 735 | 736 | /debug/3.1.0: 737 | resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} 738 | dependencies: 739 | ms: 2.0.0 740 | dev: false 741 | 742 | /debug/4.3.3: 743 | resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} 744 | engines: {node: '>=6.0'} 745 | peerDependencies: 746 | supports-color: '*' 747 | peerDependenciesMeta: 748 | supports-color: 749 | optional: true 750 | dependencies: 751 | ms: 2.1.2 752 | dev: false 753 | 754 | /delayed-stream/1.0.0: 755 | resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} 756 | engines: {node: '>=0.4.0'} 757 | dev: false 758 | 759 | /diff/4.0.2: 760 | resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} 761 | engines: {node: '>=0.3.1'} 762 | dev: true 763 | 764 | /dotenv/10.0.0: 765 | resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} 766 | engines: {node: '>=10'} 767 | dev: false 768 | 769 | /dynamic-dedupe/0.3.0: 770 | resolution: {integrity: sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=} 771 | dependencies: 772 | xtend: 4.0.2 773 | dev: true 774 | 775 | /emoji-regex/8.0.0: 776 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 777 | 778 | /escalade/3.1.1: 779 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 780 | engines: {node: '>=6'} 781 | 782 | /fast-safe-stringify/2.1.1: 783 | resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} 784 | dev: false 785 | 786 | /faye-websocket/0.11.4: 787 | resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} 788 | engines: {node: '>=0.8.0'} 789 | dependencies: 790 | websocket-driver: 0.7.4 791 | dev: false 792 | 793 | /fill-range/7.0.1: 794 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 795 | engines: {node: '>=8'} 796 | dependencies: 797 | to-regex-range: 5.0.1 798 | dev: true 799 | 800 | /firebase/9.6.4: 801 | resolution: {integrity: sha512-f9DpaBeesOKhF1mkAzAmDQ8pWq9VMvjV9y27BDtih+0VrAlHgmGPTt58y9IFZbjQyRsegpuVXjml1hMXe7qG+A==} 802 | dependencies: 803 | '@firebase/analytics': 0.7.5_@firebase+app@0.7.14 804 | '@firebase/analytics-compat': 0.1.6_8c68ed8df7b66473d54007d382e86e85 805 | '@firebase/app': 0.7.14 806 | '@firebase/app-check': 0.5.3_@firebase+app@0.7.14 807 | '@firebase/app-check-compat': 0.2.3_8c68ed8df7b66473d54007d382e86e85 808 | '@firebase/app-compat': 0.1.15 809 | '@firebase/app-types': 0.7.0 810 | '@firebase/auth': 0.19.6_@firebase+app@0.7.14 811 | '@firebase/auth-compat': 0.2.6_ac0404923be6fa57942dda687cee29d1 812 | '@firebase/database': 0.12.5_@firebase+app-types@0.7.0 813 | '@firebase/database-compat': 0.1.5_bf6a48fc8e85e6523d4715f1396e1d1c 814 | '@firebase/firestore': 3.4.3_@firebase+app@0.7.14 815 | '@firebase/firestore-compat': 0.1.12_ac0404923be6fa57942dda687cee29d1 816 | '@firebase/functions': 0.7.7_6fe50591265b03be0da1ae819dd57237 817 | '@firebase/functions-compat': 0.1.8_ac0404923be6fa57942dda687cee29d1 818 | '@firebase/installations': 0.5.5_@firebase+app@0.7.14 819 | '@firebase/messaging': 0.9.7_@firebase+app@0.7.14 820 | '@firebase/messaging-compat': 0.1.7_8c68ed8df7b66473d54007d382e86e85 821 | '@firebase/performance': 0.5.5_@firebase+app@0.7.14 822 | '@firebase/performance-compat': 0.1.5_8c68ed8df7b66473d54007d382e86e85 823 | '@firebase/polyfill': 0.3.36 824 | '@firebase/remote-config': 0.3.4_@firebase+app@0.7.14 825 | '@firebase/remote-config-compat': 0.1.5_8c68ed8df7b66473d54007d382e86e85 826 | '@firebase/storage': 0.9.1_@firebase+app@0.7.14 827 | '@firebase/storage-compat': 0.1.9_ac0404923be6fa57942dda687cee29d1 828 | '@firebase/util': 1.4.3 829 | transitivePeerDependencies: 830 | - bufferutil 831 | - utf-8-validate 832 | dev: false 833 | 834 | /follow-redirects/1.14.7: 835 | resolution: {integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==} 836 | engines: {node: '>=4.0'} 837 | peerDependencies: 838 | debug: '*' 839 | peerDependenciesMeta: 840 | debug: 841 | optional: true 842 | dev: false 843 | 844 | /follow-redirects/1.5.10: 845 | resolution: {integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==} 846 | engines: {node: '>=4.0'} 847 | dependencies: 848 | debug: 3.1.0 849 | dev: false 850 | 851 | /form-data/3.0.1: 852 | resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} 853 | engines: {node: '>= 6'} 854 | dependencies: 855 | asynckit: 0.4.0 856 | combined-stream: 1.0.8 857 | mime-types: 2.1.34 858 | dev: false 859 | 860 | /formidable/1.2.6: 861 | resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==} 862 | deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau' 863 | dev: false 864 | 865 | /fs.realpath/1.0.0: 866 | resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} 867 | 868 | /fsevents/2.3.2: 869 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 870 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 871 | os: [darwin] 872 | requiresBuild: true 873 | dev: true 874 | optional: true 875 | 876 | /function-bind/1.1.1: 877 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 878 | 879 | /get-caller-file/2.0.5: 880 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 881 | engines: {node: 6.* || 8.* || >= 10.*} 882 | 883 | /get-intrinsic/1.1.1: 884 | resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} 885 | dependencies: 886 | function-bind: 1.1.1 887 | has: 1.0.3 888 | has-symbols: 1.0.2 889 | dev: false 890 | 891 | /get-midnight-date/1.0.0: 892 | resolution: {integrity: sha1-jRvJZnvFPHPcACxCTWRbwzopAlo=} 893 | dev: false 894 | 895 | /get-next-date/1.0.0: 896 | resolution: {integrity: sha1-nKxQDssEpDvgM8GzKTOALDB3lvo=} 897 | dependencies: 898 | get-midnight-date: 1.0.0 899 | dev: false 900 | 901 | /glob-parent/5.1.2: 902 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 903 | engines: {node: '>= 6'} 904 | dependencies: 905 | is-glob: 4.0.3 906 | dev: true 907 | 908 | /glob/7.2.0: 909 | resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} 910 | dependencies: 911 | fs.realpath: 1.0.0 912 | inflight: 1.0.6 913 | inherits: 2.0.4 914 | minimatch: 3.0.4 915 | once: 1.4.0 916 | path-is-absolute: 1.0.1 917 | 918 | /has-symbols/1.0.2: 919 | resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} 920 | engines: {node: '>= 0.4'} 921 | dev: false 922 | 923 | /has/1.0.3: 924 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 925 | engines: {node: '>= 0.4.0'} 926 | dependencies: 927 | function-bind: 1.1.1 928 | 929 | /http-parser-js/0.5.5: 930 | resolution: {integrity: sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==} 931 | dev: false 932 | 933 | /idb/3.0.2: 934 | resolution: {integrity: sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==} 935 | dev: false 936 | 937 | /immediate/3.0.6: 938 | resolution: {integrity: sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=} 939 | dev: false 940 | 941 | /inflight/1.0.6: 942 | resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} 943 | dependencies: 944 | once: 1.4.0 945 | wrappy: 1.0.2 946 | 947 | /inherits/2.0.4: 948 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 949 | 950 | /is-binary-path/2.1.0: 951 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 952 | engines: {node: '>=8'} 953 | dependencies: 954 | binary-extensions: 2.2.0 955 | dev: true 956 | 957 | /is-core-module/2.8.1: 958 | resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} 959 | dependencies: 960 | has: 1.0.3 961 | dev: true 962 | 963 | /is-extglob/2.1.1: 964 | resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} 965 | engines: {node: '>=0.10.0'} 966 | dev: true 967 | 968 | /is-fullwidth-code-point/3.0.0: 969 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 970 | engines: {node: '>=8'} 971 | 972 | /is-glob/4.0.3: 973 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 974 | engines: {node: '>=0.10.0'} 975 | dependencies: 976 | is-extglob: 2.1.1 977 | dev: true 978 | 979 | /is-number/7.0.0: 980 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 981 | engines: {node: '>=0.12.0'} 982 | dev: true 983 | 984 | /isarray/0.0.1: 985 | resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=} 986 | dev: true 987 | 988 | /isarray/1.0.0: 989 | resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} 990 | 991 | /isexe/2.0.0: 992 | resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} 993 | dev: true 994 | 995 | /jszip/3.7.1: 996 | resolution: {integrity: sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==} 997 | dependencies: 998 | lie: 3.3.0 999 | pako: 1.0.11 1000 | readable-stream: 2.3.7 1001 | set-immediate-shim: 1.0.1 1002 | dev: false 1003 | 1004 | /kleur/3.0.3: 1005 | resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} 1006 | engines: {node: '>=6'} 1007 | dev: false 1008 | 1009 | /lie/3.3.0: 1010 | resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} 1011 | dependencies: 1012 | immediate: 3.0.6 1013 | dev: false 1014 | 1015 | /lodash.camelcase/4.3.0: 1016 | resolution: {integrity: sha1-soqmKIorn8ZRA1x3EfZathkDMaY=} 1017 | dev: false 1018 | 1019 | /lodash/4.17.21: 1020 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1021 | dev: false 1022 | 1023 | /long/4.0.0: 1024 | resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} 1025 | dev: false 1026 | 1027 | /lru-cache/6.0.0: 1028 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1029 | engines: {node: '>=10'} 1030 | dependencies: 1031 | yallist: 4.0.0 1032 | dev: false 1033 | 1034 | /make-error/1.3.6: 1035 | resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 1036 | dev: true 1037 | 1038 | /methods/1.1.2: 1039 | resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} 1040 | engines: {node: '>= 0.6'} 1041 | dev: false 1042 | 1043 | /mime-db/1.51.0: 1044 | resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} 1045 | engines: {node: '>= 0.6'} 1046 | dev: false 1047 | 1048 | /mime-types/2.1.34: 1049 | resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} 1050 | engines: {node: '>= 0.6'} 1051 | dependencies: 1052 | mime-db: 1.51.0 1053 | dev: false 1054 | 1055 | /mime/2.6.0: 1056 | resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} 1057 | engines: {node: '>=4.0.0'} 1058 | hasBin: true 1059 | dev: false 1060 | 1061 | /minimatch/3.0.4: 1062 | resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} 1063 | dependencies: 1064 | brace-expansion: 1.1.11 1065 | 1066 | /minimist/1.2.5: 1067 | resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} 1068 | dev: true 1069 | 1070 | /mkdirp/1.0.4: 1071 | resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} 1072 | engines: {node: '>=10'} 1073 | hasBin: true 1074 | dev: true 1075 | 1076 | /moment/2.29.1: 1077 | resolution: {integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==} 1078 | dev: false 1079 | 1080 | /ms/2.0.0: 1081 | resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} 1082 | dev: false 1083 | 1084 | /ms/2.1.2: 1085 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1086 | dev: false 1087 | 1088 | /node-fetch/2.6.5: 1089 | resolution: {integrity: sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==} 1090 | engines: {node: 4.x || >=6.0.0} 1091 | dependencies: 1092 | whatwg-url: 5.0.0 1093 | dev: false 1094 | 1095 | /noms/0.0.0: 1096 | resolution: {integrity: sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=} 1097 | dependencies: 1098 | inherits: 2.0.4 1099 | readable-stream: 1.0.34 1100 | dev: true 1101 | 1102 | /normalize-path/3.0.0: 1103 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1104 | engines: {node: '>=0.10.0'} 1105 | dev: true 1106 | 1107 | /object-inspect/1.12.0: 1108 | resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} 1109 | dev: false 1110 | 1111 | /once/1.4.0: 1112 | resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} 1113 | dependencies: 1114 | wrappy: 1.0.2 1115 | 1116 | /pako/1.0.11: 1117 | resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} 1118 | dev: false 1119 | 1120 | /path-is-absolute/1.0.1: 1121 | resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} 1122 | engines: {node: '>=0.10.0'} 1123 | 1124 | /path-key/3.1.1: 1125 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1126 | engines: {node: '>=8'} 1127 | dev: true 1128 | 1129 | /path-parse/1.0.7: 1130 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1131 | dev: true 1132 | 1133 | /picomatch/2.3.1: 1134 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1135 | engines: {node: '>=8.6'} 1136 | dev: true 1137 | 1138 | /process-nextick-args/2.0.1: 1139 | resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} 1140 | 1141 | /promise-polyfill/8.1.3: 1142 | resolution: {integrity: sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==} 1143 | dev: false 1144 | 1145 | /prompts/2.4.2: 1146 | resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} 1147 | engines: {node: '>= 6'} 1148 | dependencies: 1149 | kleur: 3.0.3 1150 | sisteransi: 1.0.5 1151 | dev: false 1152 | 1153 | /protobufjs/6.11.2: 1154 | resolution: {integrity: sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==} 1155 | hasBin: true 1156 | requiresBuild: true 1157 | dependencies: 1158 | '@protobufjs/aspromise': 1.1.2 1159 | '@protobufjs/base64': 1.1.2 1160 | '@protobufjs/codegen': 2.0.4 1161 | '@protobufjs/eventemitter': 1.1.0 1162 | '@protobufjs/fetch': 1.1.0 1163 | '@protobufjs/float': 1.0.2 1164 | '@protobufjs/inquire': 1.1.0 1165 | '@protobufjs/path': 1.1.2 1166 | '@protobufjs/pool': 1.1.0 1167 | '@protobufjs/utf8': 1.1.0 1168 | '@types/long': 4.0.1 1169 | '@types/node': 17.0.10 1170 | long: 4.0.0 1171 | dev: false 1172 | 1173 | /psl/1.8.0: 1174 | resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} 1175 | dev: false 1176 | 1177 | /punycode/2.1.1: 1178 | resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} 1179 | engines: {node: '>=6'} 1180 | dev: false 1181 | 1182 | /qs/6.10.3: 1183 | resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} 1184 | engines: {node: '>=0.6'} 1185 | dependencies: 1186 | side-channel: 1.0.4 1187 | dev: false 1188 | 1189 | /readable-stream/1.0.34: 1190 | resolution: {integrity: sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=} 1191 | dependencies: 1192 | core-util-is: 1.0.3 1193 | inherits: 2.0.4 1194 | isarray: 0.0.1 1195 | string_decoder: 0.10.31 1196 | dev: true 1197 | 1198 | /readable-stream/2.3.7: 1199 | resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} 1200 | dependencies: 1201 | core-util-is: 1.0.3 1202 | inherits: 2.0.4 1203 | isarray: 1.0.0 1204 | process-nextick-args: 2.0.1 1205 | safe-buffer: 5.1.2 1206 | string_decoder: 1.1.1 1207 | util-deprecate: 1.0.2 1208 | 1209 | /readable-stream/3.6.0: 1210 | resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} 1211 | engines: {node: '>= 6'} 1212 | dependencies: 1213 | inherits: 2.0.4 1214 | string_decoder: 1.3.0 1215 | util-deprecate: 1.0.2 1216 | dev: false 1217 | 1218 | /readdirp/3.6.0: 1219 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1220 | engines: {node: '>=8.10.0'} 1221 | dependencies: 1222 | picomatch: 2.3.1 1223 | dev: true 1224 | 1225 | /require-directory/2.1.1: 1226 | resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} 1227 | engines: {node: '>=0.10.0'} 1228 | 1229 | /resolve/1.21.1: 1230 | resolution: {integrity: sha512-lfEImVbnolPuaSZuLQ52cAxPBHeI77sPwCOWRdy12UG/CNa8an7oBHH1R+Fp1/mUqSJi4c8TIP6FOIPSZAUrEQ==} 1231 | hasBin: true 1232 | dependencies: 1233 | is-core-module: 2.8.1 1234 | path-parse: 1.0.7 1235 | supports-preserve-symlinks-flag: 1.0.0 1236 | dev: true 1237 | 1238 | /rimraf/2.7.1: 1239 | resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} 1240 | hasBin: true 1241 | dependencies: 1242 | glob: 7.2.0 1243 | dev: true 1244 | 1245 | /rimraf/3.0.2: 1246 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1247 | hasBin: true 1248 | dependencies: 1249 | glob: 7.2.0 1250 | dev: false 1251 | 1252 | /safe-buffer/5.1.2: 1253 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 1254 | 1255 | /safe-buffer/5.2.1: 1256 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1257 | dev: false 1258 | 1259 | /selenium-webdriver/4.0.0-rc-1: 1260 | resolution: {integrity: sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==} 1261 | engines: {node: '>= 10.15.0'} 1262 | dependencies: 1263 | jszip: 3.7.1 1264 | rimraf: 3.0.2 1265 | tmp: 0.2.1 1266 | ws: 8.4.2 1267 | transitivePeerDependencies: 1268 | - bufferutil 1269 | - utf-8-validate 1270 | dev: false 1271 | 1272 | /selenium-webdriver/4.1.1: 1273 | resolution: {integrity: sha512-Fr9e9LC6zvD6/j7NO8M1M/NVxFX67abHcxDJoP5w2KN/Xb1SyYLjMVPGgD14U2TOiKe4XKHf42OmFw9g2JgCBQ==} 1274 | engines: {node: '>= 10.15.0'} 1275 | dependencies: 1276 | jszip: 3.7.1 1277 | tmp: 0.2.1 1278 | ws: 8.4.2 1279 | transitivePeerDependencies: 1280 | - bufferutil 1281 | - utf-8-validate 1282 | dev: false 1283 | 1284 | /semver/7.3.5: 1285 | resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} 1286 | engines: {node: '>=10'} 1287 | hasBin: true 1288 | dependencies: 1289 | lru-cache: 6.0.0 1290 | dev: false 1291 | 1292 | /set-immediate-shim/1.0.1: 1293 | resolution: {integrity: sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=} 1294 | engines: {node: '>=0.10.0'} 1295 | dev: false 1296 | 1297 | /shebang-command/2.0.0: 1298 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1299 | engines: {node: '>=8'} 1300 | dependencies: 1301 | shebang-regex: 3.0.0 1302 | dev: true 1303 | 1304 | /shebang-regex/3.0.0: 1305 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1306 | engines: {node: '>=8'} 1307 | dev: true 1308 | 1309 | /side-channel/1.0.4: 1310 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 1311 | dependencies: 1312 | call-bind: 1.0.2 1313 | get-intrinsic: 1.1.1 1314 | object-inspect: 1.12.0 1315 | dev: false 1316 | 1317 | /sisteransi/1.0.5: 1318 | resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 1319 | dev: false 1320 | 1321 | /source-map-support/0.5.21: 1322 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 1323 | dependencies: 1324 | buffer-from: 1.1.2 1325 | source-map: 0.6.1 1326 | dev: true 1327 | 1328 | /source-map/0.6.1: 1329 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 1330 | engines: {node: '>=0.10.0'} 1331 | dev: true 1332 | 1333 | /spotify-to-youtube/1.0.2: 1334 | resolution: {integrity: sha512-Pl5sNOi0taeG/aWErTN/KecyYqWpECLMoc1RL3LlY4SI2d+7q79ZSKS0yNoWXgklNuslhG00UTrlqT0sZt+lNw==} 1335 | dependencies: 1336 | youtube-music-api: 1.0.6 1337 | dev: false 1338 | 1339 | /spotify-web-api-node/5.0.2: 1340 | resolution: {integrity: sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA==} 1341 | dependencies: 1342 | superagent: 6.1.0 1343 | transitivePeerDependencies: 1344 | - supports-color 1345 | dev: false 1346 | 1347 | /string-width/4.2.3: 1348 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1349 | engines: {node: '>=8'} 1350 | dependencies: 1351 | emoji-regex: 8.0.0 1352 | is-fullwidth-code-point: 3.0.0 1353 | strip-ansi: 6.0.1 1354 | 1355 | /string_decoder/0.10.31: 1356 | resolution: {integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=} 1357 | dev: true 1358 | 1359 | /string_decoder/1.1.1: 1360 | resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} 1361 | dependencies: 1362 | safe-buffer: 5.1.2 1363 | 1364 | /string_decoder/1.3.0: 1365 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1366 | dependencies: 1367 | safe-buffer: 5.2.1 1368 | dev: false 1369 | 1370 | /strip-ansi/6.0.1: 1371 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1372 | engines: {node: '>=8'} 1373 | dependencies: 1374 | ansi-regex: 5.0.1 1375 | 1376 | /strip-bom/3.0.0: 1377 | resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} 1378 | engines: {node: '>=4'} 1379 | dev: true 1380 | 1381 | /strip-json-comments/2.0.1: 1382 | resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} 1383 | engines: {node: '>=0.10.0'} 1384 | dev: true 1385 | 1386 | /superagent/6.1.0: 1387 | resolution: {integrity: sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==} 1388 | engines: {node: '>= 7.0.0'} 1389 | deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . Thanks to @shadowgate15, @spence-s, and @niftylettuce. Superagent is sponsored by Forward Email at . 1390 | dependencies: 1391 | component-emitter: 1.3.0 1392 | cookiejar: 2.1.3 1393 | debug: 4.3.3 1394 | fast-safe-stringify: 2.1.1 1395 | form-data: 3.0.1 1396 | formidable: 1.2.6 1397 | methods: 1.1.2 1398 | mime: 2.6.0 1399 | qs: 6.10.3 1400 | readable-stream: 3.6.0 1401 | semver: 7.3.5 1402 | transitivePeerDependencies: 1403 | - supports-color 1404 | dev: false 1405 | 1406 | /supports-preserve-symlinks-flag/1.0.0: 1407 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1408 | engines: {node: '>= 0.4'} 1409 | dev: true 1410 | 1411 | /through2/2.0.5: 1412 | resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} 1413 | dependencies: 1414 | readable-stream: 2.3.7 1415 | xtend: 4.0.2 1416 | dev: true 1417 | 1418 | /tmp/0.2.1: 1419 | resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} 1420 | engines: {node: '>=8.17.0'} 1421 | dependencies: 1422 | rimraf: 3.0.2 1423 | dev: false 1424 | 1425 | /to-regex-range/5.0.1: 1426 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1427 | engines: {node: '>=8.0'} 1428 | dependencies: 1429 | is-number: 7.0.0 1430 | dev: true 1431 | 1432 | /tough-cookie/4.0.0: 1433 | resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==} 1434 | engines: {node: '>=6'} 1435 | dependencies: 1436 | psl: 1.8.0 1437 | punycode: 2.1.1 1438 | universalify: 0.1.2 1439 | dev: false 1440 | 1441 | /tr46/0.0.3: 1442 | resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} 1443 | dev: false 1444 | 1445 | /tree-kill/1.2.2: 1446 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 1447 | hasBin: true 1448 | dev: true 1449 | 1450 | /ts-node-dev/1.1.8_typescript@4.5.5: 1451 | resolution: {integrity: sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==} 1452 | engines: {node: '>=0.8.0'} 1453 | hasBin: true 1454 | peerDependencies: 1455 | node-notifier: '*' 1456 | typescript: '*' 1457 | peerDependenciesMeta: 1458 | node-notifier: 1459 | optional: true 1460 | dependencies: 1461 | chokidar: 3.5.3 1462 | dynamic-dedupe: 0.3.0 1463 | minimist: 1.2.5 1464 | mkdirp: 1.0.4 1465 | resolve: 1.21.1 1466 | rimraf: 2.7.1 1467 | source-map-support: 0.5.21 1468 | tree-kill: 1.2.2 1469 | ts-node: 9.1.1_typescript@4.5.5 1470 | tsconfig: 7.0.0 1471 | typescript: 4.5.5 1472 | dev: true 1473 | 1474 | /ts-node/9.1.1_typescript@4.5.5: 1475 | resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==} 1476 | engines: {node: '>=10.0.0'} 1477 | hasBin: true 1478 | peerDependencies: 1479 | typescript: '>=2.7' 1480 | dependencies: 1481 | arg: 4.1.3 1482 | create-require: 1.1.1 1483 | diff: 4.0.2 1484 | make-error: 1.3.6 1485 | source-map-support: 0.5.21 1486 | typescript: 4.5.5 1487 | yn: 3.1.1 1488 | dev: true 1489 | 1490 | /tsconfig/7.0.0: 1491 | resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} 1492 | dependencies: 1493 | '@types/strip-bom': 3.0.0 1494 | '@types/strip-json-comments': 0.0.30 1495 | strip-bom: 3.0.0 1496 | strip-json-comments: 2.0.1 1497 | dev: true 1498 | 1499 | /tslib/2.3.1: 1500 | resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} 1501 | dev: false 1502 | 1503 | /typescript/4.5.5: 1504 | resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==} 1505 | engines: {node: '>=4.2.0'} 1506 | hasBin: true 1507 | dev: false 1508 | 1509 | /universalify/0.1.2: 1510 | resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 1511 | engines: {node: '>= 4.0.0'} 1512 | dev: false 1513 | 1514 | /untildify/4.0.0: 1515 | resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} 1516 | engines: {node: '>=8'} 1517 | dev: true 1518 | 1519 | /util-deprecate/1.0.2: 1520 | resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} 1521 | 1522 | /webidl-conversions/3.0.1: 1523 | resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} 1524 | dev: false 1525 | 1526 | /websocket-driver/0.7.4: 1527 | resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} 1528 | engines: {node: '>=0.8.0'} 1529 | dependencies: 1530 | http-parser-js: 0.5.5 1531 | safe-buffer: 5.2.1 1532 | websocket-extensions: 0.1.4 1533 | dev: false 1534 | 1535 | /websocket-extensions/0.1.4: 1536 | resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} 1537 | engines: {node: '>=0.8.0'} 1538 | dev: false 1539 | 1540 | /whatwg-fetch/2.0.4: 1541 | resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} 1542 | dev: false 1543 | 1544 | /whatwg-url/5.0.0: 1545 | resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} 1546 | dependencies: 1547 | tr46: 0.0.3 1548 | webidl-conversions: 3.0.1 1549 | dev: false 1550 | 1551 | /which/2.0.2: 1552 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1553 | engines: {node: '>= 8'} 1554 | hasBin: true 1555 | dependencies: 1556 | isexe: 2.0.0 1557 | dev: true 1558 | 1559 | /wrap-ansi/7.0.0: 1560 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1561 | engines: {node: '>=10'} 1562 | dependencies: 1563 | ansi-styles: 4.3.0 1564 | string-width: 4.2.3 1565 | strip-ansi: 6.0.1 1566 | 1567 | /wrappy/1.0.2: 1568 | resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} 1569 | 1570 | /ws/8.4.2: 1571 | resolution: {integrity: sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==} 1572 | engines: {node: '>=10.0.0'} 1573 | peerDependencies: 1574 | bufferutil: ^4.0.1 1575 | utf-8-validate: ^5.0.2 1576 | peerDependenciesMeta: 1577 | bufferutil: 1578 | optional: true 1579 | utf-8-validate: 1580 | optional: true 1581 | dev: false 1582 | 1583 | /xtend/4.0.2: 1584 | resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} 1585 | engines: {node: '>=0.4'} 1586 | dev: true 1587 | 1588 | /y18n/5.0.8: 1589 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 1590 | engines: {node: '>=10'} 1591 | 1592 | /yallist/4.0.0: 1593 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1594 | dev: false 1595 | 1596 | /yargs-parser/20.2.9: 1597 | resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} 1598 | engines: {node: '>=10'} 1599 | 1600 | /yargs/16.2.0: 1601 | resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} 1602 | engines: {node: '>=10'} 1603 | dependencies: 1604 | cliui: 7.0.4 1605 | escalade: 3.1.1 1606 | get-caller-file: 2.0.5 1607 | require-directory: 2.1.1 1608 | string-width: 4.2.3 1609 | y18n: 5.0.8 1610 | yargs-parser: 20.2.9 1611 | 1612 | /yn/3.1.1: 1613 | resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} 1614 | engines: {node: '>=6'} 1615 | dev: true 1616 | 1617 | /youtube-music-api/1.0.6: 1618 | resolution: {integrity: sha512-/U63iOLaci7zrxKw26dEc7sYZmfPkZLdWJ2MRx9nUmG8gUwhNjLtNYDfny49+vjpxvnLOWO9Pp734G5/beU+kg==} 1619 | dependencies: 1620 | axios: 0.19.2 1621 | lodash: 4.17.21 1622 | tough-cookie: 4.0.0 1623 | dev: false 1624 | -------------------------------------------------------------------------------- /src/assets/oldSongs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "date": "09.01.2020", "url": "jK69usDDkaI" }, 3 | { "date": "10.01.2020", "url": "I6g-FUVDdxw" }, 4 | { "date": "11.01.2020", "url": "DGDwoCzzS4I" }, 5 | { "date": "12.01.2020", "url": "mQIb8nprQF0" }, 6 | { "date": "13.01.2020", "url": "qt3ULomSLYY" }, 7 | { "date": "14.01.2020", "url": "z6vUHZInJvU" }, 8 | { "date": "15.01.2020", "url": "_a0mVq-SCBs" }, 9 | { "date": "16.01.2020", "url": "mHN0_cYUibE" }, 10 | { "date": "17.01.2020", "url": "XUDxbqaRKvs" }, 11 | { "date": "18.01.2020", "url": "uRI0_G9RIDw" }, 12 | { "date": "19.01.2020", "url": "1QpQqt7FLSE" }, 13 | { "date": "20.01.2020", "url": "X8JH6npiSC4" }, 14 | { "date": "21.01.2020", "url": "Q6RHj-wO5YE" }, 15 | { "date": "22.01.2020", "url": "QHVp9xiUr9U" }, 16 | { "date": "23.01.2020", "url": "gGhFWUgtLjI" }, 17 | { "date": "24.01.2020", "url": "YKxM-Evsvnk" }, 18 | { "date": "25.01.2020", "url": "p6YjWstSYt0" }, 19 | { "date": "26.01.2020", "url": "60m4gd0b2rc" }, 20 | { "date": "27.01.2020", "url": "IP_ypVQheJQ" }, 21 | { "date": "28.01.2020", "url": "V9udVD0-yAg" }, 22 | { "date": "29.01.2020", "url": "eTwnyqwFgOA" }, 23 | { "date": "30.01.2020", "url": "bdsRi-w3Olw" }, 24 | { "date": "31.01.2020", "url": "JMTXkecJf0c" }, 25 | { "date": "01.02.2020", "url": "_ykuYOXVPds" }, 26 | { "date": "02.02.2020", "url": "kIdycgP_Tpk" }, 27 | { "date": "03.02.2020", "url": "mWCy6xA1IIc" }, 28 | { "date": "04.02.2020", "url": "FQIvtOT5EVM" }, 29 | { "date": "05.02.2020", "url": "mXmHJABhNHg" }, 30 | { "date": "06.02.2020", "url": "oUAujb5TdO4" }, 31 | { "date": "07.02.2020", "url": "3KV_7vB6Nnc" }, 32 | { "date": "08.02.2020", "url": "2Nm_hnaUI2g" }, 33 | { "date": "09.02.2020", "url": "Ft8lBPRqU6o" }, 34 | { "date": "10.02.2020", "url": "wFRS1knPg_Y" }, 35 | { "date": "11.02.2020", "url": "AvRxoklcpYU" }, 36 | { "date": "12.02.2020", "url": "rQMsS4OdWpY" }, 37 | { "date": "13.02.2020", "url": "eAyOgCCsovM" }, 38 | { "date": "14.02.2020", "url": "7uWYL9cfQeI" }, 39 | { "date": "15.02.2020", "url": "VgKstfAH8BU" }, 40 | { "date": "16.02.2020", "url": "JgWCWJVbsr0" }, 41 | { "date": "17.02.2020", "url": "T02URshLpLQ" }, 42 | { "date": "18.02.2020", "url": "fmtDvV-2EhM" }, 43 | { "date": "19.02.2020", "url": "FOWIXm-_-0g" }, 44 | { "date": "20.02.2020", "url": "xDGFg2w4820" }, 45 | { "date": "21.02.2020", "url": "Xx42-LFrwro" }, 46 | { "date": "22.02.2020", "url": "GUKIEjmQ1Bc" }, 47 | { "date": "23.02.2020", "url": "ZUYiKaH3DW8" }, 48 | { "date": "24.02.2020", "url": "eirksDphwP0" }, 49 | { "date": "25.02.2020", "url": "UISaT4Eh9CM" }, 50 | { "date": "26.02.2020", "url": "LpQArtCeXTk" }, 51 | { "date": "27.02.2020", "url": "tv8A7i23Y6Y" }, 52 | { "date": "28.02.2020", "url": "1QuYMEJ9zoA" }, 53 | { "date": "29.02.2020", "url": "c32I4bIQgqU" }, 54 | { "date": "01.03.2020", "url": "CqhHbfaS7bo" }, 55 | { "date": "02.03.2020", "url": "qs-r-xal6W4" }, 56 | { "date": "03.03.2020", "url": "EMVglat8Qe8" }, 57 | { "date": "04.03.2020", "url": "0p6sSwfkAD4" }, 58 | { "date": "05.03.2020", "url": "Hq_J-XVOucg" }, 59 | { "date": "06.03.2020", "url": "LPqNMmncN2o" }, 60 | { "date": "07.03.2020", "url": "wTyPiYsG9pc" }, 61 | { "date": "08.03.2020", "url": "j1h0rPYKces" }, 62 | { "date": "09.03.2020", "url": "XogE2cPEpfE" }, 63 | { "date": "10.03.2020", "url": "z5jd8s0NRfg" }, 64 | { "date": "11.03.2020", "url": "aXlyQfj32qE" }, 65 | { "date": "12.03.2020", "url": "dzDJ6dFaZQA" }, 66 | { "date": "13.03.2020", "url": "Uo1ezSXMfMc" }, 67 | { "date": "14.03.2020", "url": "h-NT2DByrCc" }, 68 | { "date": "15.03.2020", "url": "5yP9olT_TdM" }, 69 | { "date": "16.03.2020", "url": "xVbhHdjnrXs" }, 70 | { "date": "17.03.2020", "url": "VGzOj5JTMnE" }, 71 | { "date": "18.03.2020", "url": "FpJg57_3uXE" }, 72 | { "date": "29.03.2020", "url": "JaOI89rUlX8" }, 73 | { "date": "30.03.2020", "url": "ELWZ3tIKSMY" }, 74 | { "date": "31.03.2020", "url": "r78xfXZb_WU" }, 75 | { "date": "01.04.2020", "url": "MpwTZX4l5hY" }, 76 | { "date": "02.04.2020", "url": "6oTKDjApAEw" }, 77 | { "date": "03.04.2020", "url": "N60MbgnhdwA" }, 78 | { "date": "04.04.2020", "url": "eGWEHkWZA20" }, 79 | { "date": "05.04.2020", "url": "YHaXglsxngA" }, 80 | { "date": "06.04.2020", "url": "0Ulcb-DwShU" }, 81 | { "date": "29.04.2020", "url": "GTKm0mnZgp8" }, 82 | { "date": "30.04.2020", "url": "FY5DeHafS4c" }, 83 | { "date": "01.05.2020", "url": "K5vBPwZNHYE" }, 84 | { "date": "02.05.2020", "url": "Ji0A6LNj2cg" }, 85 | { "date": "03.05.2020", "url": "qoUeSraOb-4" }, 86 | { "date": "04.05.2020", "url": "rymYToIEL9o" }, 87 | { "date": "05.05.2020", "url": "jxJUy8IohnM" }, 88 | { "date": "06.05.2020", "url": "hiOkMt7iJ7g" }, 89 | { "date": "07.05.2020", "url": "-Ef3vKpPakU" }, 90 | { "date": "08.05.2020", "url": "jGFFPe1-qaM" }, 91 | { "date": "09.05.2020", "url": "Ncd4gKmZImw" }, 92 | { "date": "10.05.2020", "url": "b9EqnSrswEU" }, 93 | { "date": "11.05.2020", "url": "if5sW3mkpdQ" }, 94 | { "date": "12.05.2020", "url": "_fiLLhYix34" }, 95 | { "date": "14.05.2020", "url": "KLtz5e3ugjE" }, 96 | { "date": "15.05.2020", "url": "T5GqqbI4UGY" }, 97 | { "date": "16.05.2020", "url": "B89q3xU0HnQ" }, 98 | { "date": "17.05.2020", "url": "lkcftEx5WJ4" }, 99 | { "date": "18.05.2020", "url": "OfI9e25AmmY" }, 100 | { "date": "19.05.2020", "url": "k20iDh7IVak" }, 101 | { "date": "20.05.2020", "url": "Cy-Vd0lXse4" }, 102 | { "date": "21.05.2020", "url": "ucrhTx1hGJQ" }, 103 | { "date": "01.06.2020", "url": "0QK2mskKNQ8" }, 104 | { "date": "02.06.2020", "url": "fJlawjuGPXI" }, 105 | { "date": "03.06.2020", "url": "-WGBiYwFLmg" }, 106 | { "date": "05.06.2020", "url": "hH5yLiWY6BU" }, 107 | { "date": "06.06.2020", "url": "TI2op6qSrYI" }, 108 | { "date": "07.06.2020", "url": "WZwfvEsAFrc" }, 109 | { "date": "17.06.2020", "url": "Alc-LuD24ns" }, 110 | { "date": "18.06.2020", "url": "Y5XwoJRSpjQ" }, 111 | { "date": "23.06.2020", "url": "bKPfBqhGjRs" }, 112 | { "date": "24.06.2020", "url": "v10r-9DuVQI" }, 113 | { "date": "25.06.2020", "url": "4LGiw0ciB88" }, 114 | { "date": "26.06.2020", "url": "v425V7WckYc" }, 115 | { "date": "27.06.2020", "url": "eYDI8b5Nn5s" }, 116 | { "date": "11.07.2020", "url": "E-CepHwRzQ0" }, 117 | { "date": "12.07.2020", "url": "eDImvdyxdz8" }, 118 | { "date": "13.07.2020", "url": "Cpy-FJRplvg" }, 119 | { "date": "14.07.2020", "url": "o-Ilfs9l4lg" }, 120 | { "date": "15.07.2020", "url": "YJK9ehfnoXU" }, 121 | { "date": "16.07.2020", "url": "TA5kvZ9aqDI" }, 122 | { "date": "17.07.2020", "url": "TpfTu5l5EyY" }, 123 | { "date": "18.07.2020", "url": "Q-beLWfLeG8" }, 124 | { "date": "19.07.2020", "url": "Y3sFi7Gq3NA" }, 125 | { "date": "20.07.2020", "url": "t_brlDMZq0Y" }, 126 | { "date": "21.07.2020", "url": "s-ltU5EcNGs" }, 127 | { "date": "22.07.2020", "url": "oRl6FXGIdwE" }, 128 | { "date": "23.07.2020", "url": "_Y1LuOmbKIU" }, 129 | { "date": "24.07.2020", "url": "o3P9ZptDTQE" }, 130 | { "date": "25.07.2020", "url": "X8Kp4sraui0" }, 131 | { "date": "26.07.2020", "url": "0x3al9yAlbQ" }, 132 | { "date": "27.07.2020", "url": "SBFMs9zYDqM" }, 133 | { "date": "28.07.2020", "url": "JBdXJNUzuiM" }, 134 | { "date": "29.07.2020", "url": "ir6o8IqlV8w" }, 135 | { "date": "02.08.2020", "url": "PqmKtRKWOJA" }, 136 | { "date": "05.08.2020", "url": "AvMQWRpMZQ8" }, 137 | { "date": "06.08.2020", "url": "zfCkDe0k5Qk" }, 138 | { "date": "07.08.2020", "url": "MqEVljq65kY" }, 139 | { "date": "08.08.2020", "url": "MIbotR5sTUU" }, 140 | { "date": "09.08.2020", "url": "cmuM5i5SSOI" }, 141 | { "date": "10.08.2020", "url": "FiuLN38W9bU" }, 142 | { "date": "12.08.2020", "url": "vaAvloF8rrg" }, 143 | { "date": "13.08.2020", "url": "z8SB-A-xI6E" }, 144 | { "date": "14.08.2020", "url": "_Co2dWzOIrk" }, 145 | { "date": "15.08.2020", "url": "4WxJAmMeYI0" }, 146 | { "date": "16.08.2020", "url": "IyEr_8PJg0I" }, 147 | { "date": "18.08.2020", "url": "EomXN9FkGkg" }, 148 | { "date": "19.08.2020", "url": "u2lxG2wC6Wg" }, 149 | { "date": "20.08.2020", "url": "FsGl5JefvJs" }, 150 | { "date": "21.08.2020", "url": "8opAVxkwa9Y" }, 151 | { "date": "23.08.2020", "url": "rOYjfcrZTfI" }, 152 | { "date": "25.08.2020", "url": "Xqm9SGDzjcQ" }, 153 | { "date": "26.08.2020", "url": "Lhwp20Hxf-k" }, 154 | { "date": "27.08.2020", "url": "E8pq9Z4UxII" }, 155 | { "date": "28.08.2020", "url": "WVPammA7qyQ" }, 156 | { "date": "29.08.2020", "url": "g4aBjmVZRvw" }, 157 | { "date": "30.08.2020", "url": "R_LJX5K7E7A" }, 158 | { "date": "01.09.2020", "url": "PxMEPHYIdYc" }, 159 | { "date": "02.09.2020", "url": "kDHsDO6ReAI" } 160 | ] 161 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { config } from "dotenv"; 3 | 4 | config({ 5 | path: 6 | process.env.NODE_ENV === "development" 7 | ? resolve("./.env") 8 | : resolve("../.env"), 9 | }); 10 | 11 | export default { 12 | firebase: { 13 | config: { 14 | appId: process.env.FIREBASE_APP_ID, 15 | apiKey: process.env.FIREBASE_API_KEY, 16 | projectId: process.env.FIREBASE_PROJECT_ID, 17 | }, 18 | user: { 19 | email: process.env.FIREBASE_USER, 20 | password: process.env.FIREBASE_PASSWORD, 21 | }, 22 | }, 23 | key: { 24 | youtube: process.env.YOUTUBE_API_KEY, 25 | spotify: { 26 | clientId: process.env.SPOTIFY_CLIENT_ID, 27 | clientSecret: process.env.SPOTIFY_CLIENT_SECRET, 28 | }, 29 | // ksoft: process.env.KSOFT_API_KEY, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/functions/getKsoft.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import config from "../config"; 3 | 4 | /* Interfaces */ 5 | interface Ksoft { 6 | lyrics: string[]; 7 | spotifyUrl: string; 8 | } 9 | 10 | /* Import types */ 11 | import type { KsoftResponse } from "../types/Response/Ksoft"; 12 | 13 | /** 14 | * Get lyrics and Spotify URL from KSoft API with given title and artist. 15 | * @deprecated KSoft Lyrics API is no longer available 16 | */ 17 | export default async function getKsoftInfo( 18 | title: string, 19 | artist: string 20 | ): Promise { 21 | let searchValue = title; 22 | if (artist) searchValue = `${title} ${artist}`; 23 | 24 | const apiUri = `https://api.ksoft.si/lyrics/search?q=${encodeURI( 25 | searchValue 26 | )}&limit=1`; 27 | 28 | const { data: ksoft } = ( 29 | await axios.get(apiUri, { 30 | headers: { Authorization: `Bearer ${null /* config.key.ksoft */}` }, 31 | }) 32 | ).data as KsoftResponse; 33 | 34 | if (!ksoft || ksoft.length === 0) return { lyrics: [], spotifyUrl: null }; 35 | else 36 | return { 37 | lyrics: ksoft[0]?.lyrics?.split("\n") || [], 38 | spotifyUrl: ksoft[0]?.meta?.spotify?.track, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/functions/getLocalSongs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Reads from local asset file and converts content to new metadata style. 3 | */ 4 | export default function getLocalSongs() { 5 | const songs = require("../assets/oldSongs.json"); 6 | return songs; 7 | } 8 | -------------------------------------------------------------------------------- /src/functions/getMetadata.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import config from "../config"; 3 | 4 | /* Import types */ 5 | import type { Metadata } from "../types/Song"; 6 | import type { YoutubeResponse } from "../types/Response/Youtube"; 7 | 8 | /** 9 | * Get video metadata such as title, artist and thumbnail from YouTube. 10 | * @param videoId string 11 | */ 12 | export default async function getMetadata(videoId: string): Promise { 13 | if (!videoId) throw new Error("No video ID specified."); 14 | 15 | const { items } = 16 | (( 17 | await axios.get( 18 | `https://www.googleapis.com/youtube/v3/videos?part=id%2C+snippet&id=${videoId}&key=${config.key.youtube}` 19 | ) 20 | ).data as YoutubeResponse) || {}; 21 | 22 | if (!items[0]) 23 | throw new Error("No results found on YouTube related to that ID."); 24 | 25 | const { title, channelTitle, thumbnails } = items[0]?.snippet; 26 | 27 | const metadata: Metadata = { 28 | title, 29 | artist: channelTitle?.replace(" - Topic", ""), 30 | thumbnail: thumbnails?.default?.url || "http://via.placeholder.com/75", 31 | lyrics: [], 32 | spotifyUrl: "", 33 | }; 34 | 35 | /* 36 | 37 | const ksoft = await getKsoftInfo(metadata.title, metadata.artist); 38 | 39 | metadata.lyrics = ksoft.lyrics; 40 | metadata.spotifyUrl = ksoft.spotifyUrl; 41 | 42 | */ 43 | 44 | return metadata; 45 | } 46 | -------------------------------------------------------------------------------- /src/functions/getTurkeyTime.ts: -------------------------------------------------------------------------------- 1 | export default function getTurkeyTime(): Date { 2 | return new Date( 3 | new Date().toLocaleString("en-US", { 4 | timeZone: "Europe/Istanbul", 5 | }) 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/functions/getVideoId.ts: -------------------------------------------------------------------------------- 1 | /* Import functions */ 2 | import getYoutubeFromSpotify from "./getYoutubeFromSpotify"; 3 | import isUrl from "./isUrl"; 4 | 5 | /** 6 | * Get YouTube video ID from passed parameter. 7 | * @param url string 8 | */ 9 | export default async function getVideoId(url: string): Promise { 10 | const isParamUrl = isUrl(url); 11 | const youtubeUrls = ["youtube.com", "music.youtube.com", "www.youtube.com"]; 12 | 13 | if (isParamUrl === true) { 14 | const newUrl = new URL(url); 15 | 16 | if (newUrl.hostname === "open.spotify.com") 17 | return getYoutubeFromSpotify(url); 18 | else if (youtubeUrls.includes(newUrl.hostname)) 19 | return newUrl.searchParams.get("v"); 20 | else if (newUrl.hostname === "youtu.be") 21 | return newUrl.pathname.replace(/\//g, ""); 22 | } else return url; 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/getYoutubeFromSpotify.ts: -------------------------------------------------------------------------------- 1 | /* Import Spotify packages */ 2 | import SpotifyToYoutube from "spotify-to-youtube"; 3 | import SpotifyWebApi from "spotify-web-api-node"; 4 | 5 | /* Import config */ 6 | import config from "../config"; 7 | 8 | /* Import functions */ 9 | import isUrl from "./isUrl"; 10 | 11 | /** 12 | * Spotify API wrapper to convert Spotify URL to YouTube 13 | */ 14 | export default async function getYoutubeFromSpotify(url: string) { 15 | const isActualUrl = isUrl(url); 16 | 17 | if (isActualUrl === false) 18 | throw new Error("Please insert an actual Spotify URL."); 19 | 20 | // Create new Spotify API instance 21 | const spotifyApi = new SpotifyWebApi({ 22 | clientId: config.key.spotify.clientId, 23 | clientSecret: config.key.spotify.clientSecret, 24 | }); 25 | 26 | // Get access token 27 | await spotifyApi.clientCredentialsGrant().then(({ body }) => { 28 | spotifyApi.setAccessToken(body.access_token); 29 | }); 30 | 31 | // Create new Spotify To YouTube instance 32 | const spotifyToYoutube = SpotifyToYoutube(spotifyApi); 33 | const trackId = new URL(url).pathname.split("/")[2]; 34 | 35 | const id = await spotifyToYoutube(trackId); 36 | return id; 37 | } 38 | -------------------------------------------------------------------------------- /src/functions/isUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if passed parameter is URL or not. 3 | * @param string string 4 | */ 5 | export default function isUrl(string: string) { 6 | try { 7 | new URL(string); 8 | return true; 9 | } catch (err) { 10 | return false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/functions/logger.ts: -------------------------------------------------------------------------------- 1 | import consola from "consola"; 2 | 3 | /** 4 | * Log message to console. 5 | * @param message 6 | */ 7 | export function log(message: any) { 8 | consola.log(message); 9 | } 10 | 11 | /** 12 | * Log message to console in "success" style 13 | * @param message 14 | */ 15 | export function success(message: any) { 16 | consola.success(message); 17 | } 18 | 19 | /** 20 | * Log message to console in "warn" style 21 | * @param message 22 | */ 23 | export function warn(message: any) { 24 | consola.warn(message); 25 | } 26 | 27 | /** 28 | * Log message to console in "error" style 29 | * @param message 30 | */ 31 | export function error(message: any) { 32 | consola.error(message); 33 | } 34 | -------------------------------------------------------------------------------- /src/functions/mergeDocs.ts: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | 3 | /* Import types */ 4 | import type { Song } from "../types/Song"; 5 | 6 | /** 7 | * Merge and sort docs. 8 | * @param firstArray Song[] 9 | * @param secondArray Song[] 10 | */ 11 | export default function mergeDocs( 12 | firstArray: Song[], 13 | secondArray: Song[] 14 | ): Song[] { 15 | return [...firstArray, ...secondArray].sort((a, b) => { 16 | const dateA = moment(a.date, "DD.MM.YYYY").unix(); 17 | const dateB = moment(b.date, "DD.MM.YYYY").unix(); 18 | 19 | return dateA - dateB; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Firebase from "./structures/Firebase"; 2 | import Questions from "./structures/Questions"; 3 | 4 | const firebase = new Firebase(); 5 | 6 | firebase.on("songsFetched", async () => { 7 | const questions = new Questions(firebase); 8 | await questions.start(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/structures/Firebase.ts: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import EventEmitter from "events"; 3 | 4 | /* Import firebase */ 5 | import { initializeApp } from "firebase/app"; 6 | import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; 7 | import { getFirestore, collection, getDocs, addDoc } from "firebase/firestore"; 8 | 9 | /* Import config */ 10 | import config from "../config"; 11 | 12 | /* Import functions */ 13 | import getLocalSongs from "../functions/getLocalSongs"; 14 | import getVideoId from "../functions/getVideoId"; 15 | import mergeDocs from "../functions/mergeDocs"; 16 | 17 | /* Import types */ 18 | import type { Song } from "../types/Song"; 19 | 20 | export default class Firebase extends EventEmitter { 21 | app = initializeApp(config.firebase.config); 22 | collection = collection(getFirestore(this.app), "dailySongs"); 23 | 24 | /* State */ 25 | private loggedIn = false; 26 | songs: Song[]; 27 | 28 | constructor() { 29 | super(); 30 | 31 | signInWithEmailAndPassword( 32 | getAuth(this.app), 33 | config.firebase.user.email, 34 | config.firebase.user.password 35 | ).then(async () => { 36 | // Set loggedIn state 37 | this.loggedIn = true; 38 | 39 | // Emit that it's successfully logged in 40 | this.emit("loggedIn"); 41 | 42 | // Set state 43 | this.songs = mergeDocs(await this.getSongs(), getLocalSongs()); 44 | 45 | // Emit that songs are fetched 46 | this.emit("songsFetched"); 47 | }); 48 | } 49 | 50 | /** 51 | * Reads from Firestore collection and returns a nice array of songs. 52 | * @returns Promise 53 | */ 54 | async getSongs(): Promise { 55 | if (this.loggedIn === false) 56 | throw new Error("Still logging in to Firebase."); 57 | 58 | const query = await getDocs(this.collection); 59 | const docs = query.docs; 60 | 61 | const songs: Song[] = []; 62 | 63 | for (let song of docs) { 64 | const { url, date } = song.data() as Song; 65 | if (!url || !date) continue; 66 | 67 | const fUrl = (await getVideoId(url)) || "[MISSING URL]"; 68 | const fDate = moment(date.toDate()).utcOffset(3).format("DD.MM.YYYY"); 69 | 70 | songs.push({ 71 | url: fUrl, 72 | date: fDate, 73 | }); 74 | } 75 | 76 | return songs; 77 | } 78 | 79 | /** 80 | * Adds new record to the collection in Firestore. 81 | */ 82 | async addSong(data: Song) { 83 | if (this.loggedIn === false) 84 | throw new Error("Still logging in to Firebase."); 85 | 86 | /* 87 | This basically turns manipulates given date and sets 88 | hours to 9, minutes to 0 because that's when it's midnight 89 | in Turkey's timezone (UTC+3 so `9 + 3 = 12`). 90 | 91 | You can change this if you're on a different timezone, or want 92 | want to display in different timezone. 93 | */ 94 | let date = moment(data.date as string, "DD.MM.YYYY"); 95 | 96 | if (date.utcOffset() / 60 !== 3) 97 | date = date.utc().set({ 98 | hour: 21, 99 | minutes: 0, 100 | }); 101 | 102 | addDoc(this.collection, { ...data, date: date.toDate() }); 103 | 104 | this.songs.push({ 105 | date: date.format("DD.MM.YYYY"), 106 | url: data.url, 107 | }); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/structures/Questions.ts: -------------------------------------------------------------------------------- 1 | import prompts from "prompts"; 2 | import moment from "moment"; 3 | import nextDay from "get-next-date"; 4 | 5 | /* Import functions */ 6 | import getVideoId from "../functions/getVideoId"; 7 | import getMetadata from "../functions/getMetadata"; 8 | import getTurkeyTime from "../functions/getTurkeyTime"; 9 | import { success, warn } from "../functions/logger"; 10 | 11 | /* Import classes */ 12 | import Firebase from "./firebase"; 13 | 14 | /* Import types */ 15 | import type { Metadata, Song } from "../types/Song"; 16 | 17 | export default class Questions { 18 | songs: Song[]; 19 | firebase: Firebase; 20 | 21 | constructor(firebase: Firebase) { 22 | this.songs = firebase.songs; 23 | this.firebase = firebase; 24 | } 25 | 26 | async start() { 27 | /* First */ 28 | const { url } = await prompts({ 29 | type: "text", 30 | name: "url", 31 | message: "Enter the URL or the ID of the YouTube video to check", 32 | }); 33 | 34 | if (!url) return this.askAndContinue(); 35 | 36 | const songId = await getVideoId(url); 37 | const isSongAddedBefore = this.songs.find( 38 | (musiki) => musiki.url === songId 39 | ); 40 | 41 | if (!!isSongAddedBefore !== false) { 42 | warn(`This song was added before on ${isSongAddedBefore.date}`); 43 | return this.askAndContinue(); 44 | } 45 | 46 | /* Second */ 47 | const { addToList } = await prompts({ 48 | type: "confirm", 49 | name: "addToList", 50 | message: "This song was never added before, would you like to add it?", 51 | initial: true, 52 | }); 53 | 54 | if (addToList === false) return this.askAndContinue(); 55 | 56 | /* Third */ 57 | const lastSong = this.songs.slice(-1)[0]; 58 | const isTodayLaterThanLastSong = 59 | moment(lastSong.date, "DD.MM.YYYY").toDate().getTime() < 60 | getTurkeyTime().getTime(); 61 | 62 | const oneDayLater = moment( 63 | nextDay( 64 | isTodayLaterThanLastSong 65 | ? getTurkeyTime() 66 | : moment(lastSong.date, "DD.MM.YYYY").toDate() 67 | ) 68 | ).format("DD.MM.YYYY"); 69 | 70 | const { newSongDate } = await prompts({ 71 | type: "text", 72 | name: "newSongDate", 73 | message: "Enter the date that you want this song to be on", 74 | initial: oneDayLater, 75 | }); 76 | 77 | const isDateOccupied = this.songs.find( 78 | (musiki) => musiki.date === newSongDate 79 | ); 80 | 81 | if (!!isDateOccupied === true) { 82 | warn("There's already a song selected for this date."); 83 | return this.askAndContinue(); 84 | } 85 | 86 | const { spotifyUrl, ...metadata }: Metadata = await getMetadata(songId); 87 | 88 | const song: Song = { 89 | date: newSongDate, 90 | url: songId, 91 | metadata, 92 | spotifyUrl, 93 | }; 94 | 95 | if (addToList === true) { 96 | this.firebase.addSong(song); 97 | success(`Successfully added that song to be played on ${newSongDate}`); 98 | 99 | return this.askAndContinue(); 100 | } 101 | } 102 | 103 | /** 104 | * Ask and start the questions again. 105 | */ 106 | private async askAndContinue() { 107 | const { isContinue } = await prompts({ 108 | type: "confirm", 109 | name: "isContinue", 110 | message: "Do you want to continue?", 111 | initial: true, 112 | }); 113 | 114 | if (isContinue === true) this.start(); 115 | else process.exit(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/types/Response/Ksoft.d.ts: -------------------------------------------------------------------------------- 1 | export interface KsoftResponse { 2 | total: number; 3 | took: number; 4 | data: Datum[]; 5 | } 6 | 7 | interface Datum { 8 | artist: string; 9 | artist_id: number; 10 | album: string; 11 | album_ids: string; 12 | album_year: string; 13 | name: string; 14 | lyrics: string; 15 | search_str: string; 16 | album_art: string; 17 | popularity: number; 18 | singalong: Singalong[]; 19 | meta: Meta; 20 | id: string; 21 | search_score: number; 22 | url: string; 23 | } 24 | 25 | interface Meta { 26 | other: Other; 27 | deezer: DeezerOrSpotify; 28 | spotify: DeezerOrSpotify; 29 | } 30 | 31 | interface DeezerOrSpotify { 32 | album: string; 33 | track: string; 34 | artists: string[]; 35 | } 36 | 37 | interface Other { 38 | bpm: number; 39 | gain: number; 40 | musicbrainz: Musicbrainz; 41 | } 42 | 43 | interface Musicbrainz { 44 | artist?: any; 45 | } 46 | 47 | interface Singalong { 48 | line: string; 49 | duration: string; 50 | milliseconds: string; 51 | lrc_timestamp: string; 52 | } 53 | -------------------------------------------------------------------------------- /src/types/Response/README.md: -------------------------------------------------------------------------------- 1 | ### Automatically generated types 2 | 3 | Types in this folder are automatically generated by a VS Code extension called [JSON to TS](https://marketplace.visualstudio.com/items?itemName=MariusAlchimavicius.json-to-ts), types might not be 10/10 but they work pretty well for this project's needs. 4 | -------------------------------------------------------------------------------- /src/types/Response/Youtube.ts: -------------------------------------------------------------------------------- 1 | export interface YoutubeResponse { 2 | kind: string; 3 | etag: string; 4 | items: Item[]; 5 | pageInfo: PageInfo; 6 | } 7 | 8 | interface PageInfo { 9 | totalResults: number; 10 | resultsPerPage: number; 11 | } 12 | 13 | interface Item { 14 | kind: string; 15 | etag: string; 16 | id: string; 17 | snippet: Snippet; 18 | } 19 | 20 | interface Snippet { 21 | publishedAt: string; 22 | channelId: string; 23 | title: string; 24 | description: string; 25 | thumbnails: Thumbnails; 26 | channelTitle: string; 27 | tags: string[]; 28 | categoryId: string; 29 | liveBroadcastContent: string; 30 | localized: Localized; 31 | } 32 | 33 | interface Localized { 34 | title: string; 35 | description: string; 36 | } 37 | 38 | interface Thumbnails { 39 | default: Default; 40 | medium: Default; 41 | high: Default; 42 | standard: Default; 43 | maxres: Default; 44 | } 45 | 46 | interface Default { 47 | url: string; 48 | width: number; 49 | height: number; 50 | } 51 | -------------------------------------------------------------------------------- /src/types/Song.d.ts: -------------------------------------------------------------------------------- 1 | export interface Song { 2 | date: any; // Firestore Date is not an actual Date! 3 | url: string; 4 | spotifyUrl?: string; 5 | metadata?: Metadata; 6 | } 7 | 8 | export interface Metadata { 9 | artist: string; 10 | lyrics: string[]; 11 | thumbnail: string; 12 | title: string; 13 | spotifyUrl?: string; 14 | } 15 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd dist && node index.js -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd dist && node index.js -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "esModuleInterop": true, 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } --------------------------------------------------------------------------------