✨ new version of ytmusic-api abandoning validate-any
This commit is contained in:
parent
c34ea72bfd
commit
ed3b4127f8
35
README.md
35
README.md
|
|
@ -38,20 +38,21 @@ $ npm run test
|
|||
## Built with
|
||||
|
||||
- TypeScript
|
||||
- [](https://npmjs.com/package/@types/mocha)
|
||||
- [](https://npmjs.com/package/@types/node)
|
||||
- [](https://npmjs.com/package/@types/tough-cookie)
|
||||
- [](https://npmjs.com/package/typescript)
|
||||
- Axios
|
||||
- [](https://npmjs.com/package/axios)
|
||||
- Tough Cookie
|
||||
- [](https://npmjs.com/package/tough-cookie)
|
||||
- Mocha
|
||||
- [](https://npmjs.com/package/mocha)
|
||||
- [](https://npmjs.com/package/mocha.parallel)
|
||||
- [](https://npmjs.com/package/ts-mocha)
|
||||
- VuePress
|
||||
- [](https://npmjs.com/package/@vuepress/plugin-search)
|
||||
- [](https://npmjs.com/package/vuepress)
|
||||
- Miscellaneous
|
||||
- [](https://npmjs.com/package/validate-any)
|
||||
- TypeScript
|
||||
- [](https://npmjs.com/package/@types/json-schema/v/7.0.11)
|
||||
- [](https://npmjs.com/package/@types/mocha/v/10.0.1)
|
||||
- [](https://npmjs.com/package/@types/node/v/18.11.17)
|
||||
- [](https://npmjs.com/package/@types/tough-cookie/v/4.0.2)
|
||||
- [](https://npmjs.com/package/typescript/v/4.9.4)
|
||||
- Mocha
|
||||
- [](https://npmjs.com/package/mocha/v/10.2.0)
|
||||
- [](https://npmjs.com/package/mocha.parallel/v/0.15.6)
|
||||
- [](https://npmjs.com/package/ts-mocha/v/10.0.0)
|
||||
- VuePress
|
||||
- [](https://npmjs.com/package/@vuepress/plugin-search/v/2.0.0-beta.46)
|
||||
- [](https://npmjs.com/package/vuepress/v/2.0.0-beta.46)
|
||||
- Miscellaneous
|
||||
- [](https://npmjs.com/package/axios/v/0.27.2)
|
||||
- [](https://npmjs.com/package/tough-cookie/v/4.1.2)
|
||||
- [](https://npmjs.com/package/zod/v/3.20.2)
|
||||
- [](https://npmjs.com/package/zod-to-json-schema/v/3.20.1)
|
||||
16
package.json
16
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ytmusic-api",
|
||||
"version": "3.1.1",
|
||||
"version": "4.0.0",
|
||||
"description": "YouTube Music API",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
@ -17,18 +17,20 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"tough-cookie": "^4.0.0",
|
||||
"validate-any": "1.3.2"
|
||||
"tough-cookie": "^4.1.2",
|
||||
"zod": "^3.20.2",
|
||||
"zod-to-json-schema": "^3.20.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/node": "^17.0.36",
|
||||
"@types/json-schema": "^7.0.11",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^18.11.17",
|
||||
"@types/tough-cookie": "^4.0.2",
|
||||
"@vuepress/plugin-search": "^2.0.0-beta.46",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha": "^10.2.0",
|
||||
"mocha.parallel": "^0.15.6",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"typescript": "^4.7.2",
|
||||
"typescript": "^4.9.4",
|
||||
"vuepress": "^2.0.0-beta.46"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
|||
125
pnpm-lock.yaml
125
pnpm-lock.yaml
|
|
@ -1,33 +1,37 @@
|
|||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@types/mocha': ^9.1.1
|
||||
'@types/node': ^17.0.36
|
||||
'@types/json-schema': ^7.0.11
|
||||
'@types/mocha': ^10.0.1
|
||||
'@types/node': ^18.11.17
|
||||
'@types/tough-cookie': ^4.0.2
|
||||
'@vuepress/plugin-search': ^2.0.0-beta.46
|
||||
axios: ^0.27.2
|
||||
mocha: ^10.0.0
|
||||
mocha: ^10.2.0
|
||||
mocha.parallel: ^0.15.6
|
||||
tough-cookie: ^4.0.0
|
||||
tough-cookie: ^4.1.2
|
||||
ts-mocha: ^10.0.0
|
||||
typescript: ^4.7.2
|
||||
validate-any: 1.3.2
|
||||
typescript: ^4.9.4
|
||||
vuepress: ^2.0.0-beta.46
|
||||
zod: ^3.20.2
|
||||
zod-to-json-schema: ^3.20.1
|
||||
|
||||
dependencies:
|
||||
axios: 0.27.2
|
||||
tough-cookie: 4.0.0
|
||||
validate-any: 1.3.2
|
||||
tough-cookie: 4.1.2
|
||||
zod: 3.20.2
|
||||
zod-to-json-schema: 3.20.1_zod@3.20.2
|
||||
|
||||
devDependencies:
|
||||
'@types/mocha': 9.1.1
|
||||
'@types/node': 17.0.36
|
||||
'@types/json-schema': 7.0.11
|
||||
'@types/mocha': 10.0.1
|
||||
'@types/node': 18.11.17
|
||||
'@types/tough-cookie': 4.0.2
|
||||
'@vuepress/plugin-search': 2.0.0-beta.46
|
||||
mocha: 10.0.0
|
||||
mocha.parallel: 0.15.6_mocha@10.0.0
|
||||
ts-mocha: 10.0.0_mocha@10.0.0
|
||||
typescript: 4.7.2
|
||||
mocha: 10.2.0
|
||||
mocha.parallel: 0.15.6_mocha@10.2.0
|
||||
ts-mocha: 10.0.0_mocha@10.2.0
|
||||
typescript: 4.9.4
|
||||
vuepress: 2.0.0-beta.46
|
||||
|
||||
packages:
|
||||
|
|
@ -83,7 +87,11 @@ packages:
|
|||
/@types/fs-extra/9.0.13:
|
||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.36
|
||||
'@types/node': 18.11.17
|
||||
dev: true
|
||||
|
||||
/@types/json-schema/7.0.11:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
dev: true
|
||||
|
||||
/@types/json5/0.0.29:
|
||||
|
|
@ -106,26 +114,22 @@ packages:
|
|||
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
|
||||
dev: true
|
||||
|
||||
/@types/mocha/9.1.1:
|
||||
resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==}
|
||||
/@types/mocha/10.0.1:
|
||||
resolution: {integrity: sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==}
|
||||
dev: true
|
||||
|
||||
/@types/ms/0.7.31:
|
||||
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
|
||||
dev: true
|
||||
|
||||
/@types/node/17.0.36:
|
||||
resolution: {integrity: sha512-V3orv+ggDsWVHP99K3JlwtH20R7J4IhI1Kksgc+64q5VxgfRkQG8Ws3MFm/FZOKDYGy9feGFlZ70/HpCNe9QaA==}
|
||||
/@types/node/18.11.17:
|
||||
resolution: {integrity: sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==}
|
||||
dev: true
|
||||
|
||||
/@types/tough-cookie/4.0.2:
|
||||
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
|
||||
dev: true
|
||||
|
||||
/@ungap/promise-all-settled/1.1.2:
|
||||
resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==}
|
||||
dev: true
|
||||
|
||||
/@vitejs/plugin-vue/2.3.3_vite@2.9.9+vue@3.2.36:
|
||||
resolution: {integrity: sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
|
@ -588,7 +592,7 @@ packages:
|
|||
/axios/0.27.2:
|
||||
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
||||
dependencies:
|
||||
follow-redirects: 1.14.9
|
||||
follow-redirects: 1.15.2
|
||||
form-data: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
|
@ -747,7 +751,7 @@ packages:
|
|||
dev: false
|
||||
|
||||
/concat-map/0.0.1:
|
||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/connect-history-api-fallback/1.6.0:
|
||||
|
|
@ -1132,8 +1136,8 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/follow-redirects/1.14.9:
|
||||
resolution: {integrity: sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==}
|
||||
/follow-redirects/1.15.2:
|
||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
|
|
@ -1510,22 +1514,21 @@ packages:
|
|||
minimist: 1.2.6
|
||||
dev: true
|
||||
|
||||
/mocha.parallel/0.15.6_mocha@10.0.0:
|
||||
/mocha.parallel/0.15.6_mocha@10.2.0:
|
||||
resolution: {integrity: sha512-pWph+QieKGjk7cHY2hB78wyKJDOQLyOMDuBLQLrFL7riJb8qbQBlCY3XztFHv0D1d4I1gCpiwFNjd4LhVOXPew==}
|
||||
peerDependencies:
|
||||
mocha: '>=2.2.5'
|
||||
dependencies:
|
||||
bluebird: 2.11.0
|
||||
mocha: 10.0.0
|
||||
mocha: 10.2.0
|
||||
semaphore: 1.1.0
|
||||
dev: true
|
||||
|
||||
/mocha/10.0.0:
|
||||
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==}
|
||||
/mocha/10.2.0:
|
||||
resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@ungap/promise-all-settled': 1.1.2
|
||||
ansi-colors: 4.1.1
|
||||
browser-stdout: 1.3.1
|
||||
chokidar: 3.5.3
|
||||
|
|
@ -1591,7 +1594,7 @@ packages:
|
|||
dev: true
|
||||
|
||||
/once/1.4.0:
|
||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
|
@ -1638,7 +1641,7 @@ packages:
|
|||
dev: true
|
||||
|
||||
/path-is-absolute/1.0.1:
|
||||
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
|
|
@ -1692,6 +1695,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/querystringify/2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
dev: false
|
||||
|
||||
/queue-microtask/1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
|
@ -1719,10 +1726,14 @@ packages:
|
|||
dev: true
|
||||
|
||||
/require-directory/2.1.1:
|
||||
resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/requires-port/1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
dev: false
|
||||
|
||||
/resolve/1.22.0:
|
||||
resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==}
|
||||
hasBin: true
|
||||
|
|
@ -1916,27 +1927,28 @@ packages:
|
|||
resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
|
||||
dev: true
|
||||
|
||||
/tough-cookie/4.0.0:
|
||||
resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==}
|
||||
/tough-cookie/4.1.2:
|
||||
resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
psl: 1.8.0
|
||||
punycode: 2.1.1
|
||||
universalify: 0.1.2
|
||||
universalify: 0.2.0
|
||||
url-parse: 1.5.10
|
||||
dev: false
|
||||
|
||||
/ts-debounce/4.0.0:
|
||||
resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==}
|
||||
dev: true
|
||||
|
||||
/ts-mocha/10.0.0_mocha@10.0.0:
|
||||
/ts-mocha/10.0.0_mocha@10.2.0:
|
||||
resolution: {integrity: sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==}
|
||||
engines: {node: '>= 6.X.X'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X
|
||||
dependencies:
|
||||
mocha: 10.0.0
|
||||
mocha: 10.2.0
|
||||
ts-node: 7.0.1
|
||||
optionalDependencies:
|
||||
tsconfig-paths: 3.14.1
|
||||
|
|
@ -1968,8 +1980,8 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/typescript/4.7.2:
|
||||
resolution: {integrity: sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==}
|
||||
/typescript/4.9.4:
|
||||
resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
|
@ -1978,8 +1990,8 @@ packages:
|
|||
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
|
||||
dev: true
|
||||
|
||||
/universalify/0.1.2:
|
||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||
/universalify/0.2.0:
|
||||
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
dev: false
|
||||
|
||||
|
|
@ -1993,14 +2005,17 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/url-parse/1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
|
||||
dev: true
|
||||
|
||||
/validate-any/1.3.2:
|
||||
resolution: {integrity: sha512-L1SpFcOpnFR4u7slwdtVRecyI/SAun/QvoFM3pJw+hE/4/D5LJMs2wcf8OYVOM8CpNab/zsXb58w9ck3XkIYNg==}
|
||||
dev: false
|
||||
|
||||
/vite/2.9.9:
|
||||
resolution: {integrity: sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==}
|
||||
engines: {node: '>=12.2.0'}
|
||||
|
|
@ -2123,7 +2138,7 @@ packages:
|
|||
dev: true
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/y18n/5.0.8:
|
||||
|
|
@ -2168,3 +2183,15 @@ packages:
|
|||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/zod-to-json-schema/3.20.1_zod@3.20.2:
|
||||
resolution: {integrity: sha512-U+zmNJUKqzv92E+LdEYv0g2LxBLks4HAwfC6cue8jXby5PAeSEPGO4xV9Sl4zmLYyFvJkm0FOfOs6orUO+AI1w==}
|
||||
peerDependencies:
|
||||
zod: ^3.20.0
|
||||
dependencies:
|
||||
zod: 3.20.2
|
||||
dev: false
|
||||
|
||||
/zod/3.20.2:
|
||||
resolution: {integrity: sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==}
|
||||
dev: false
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import axios, { AxiosInstance } from "axios"
|
||||
import { Cookie, CookieJar } from "tough-cookie"
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
AlbumDetailed, AlbumFull, ArtistDetailed, ArtistFull, PlaylistFull, SearchResult, SongDetailed,
|
||||
SongFull, VideoDetailed, VideoFull
|
||||
} from "./"
|
||||
import AlbumParser from "./parsers/AlbumParser"
|
||||
import ArtistParser from "./parsers/ArtistParser"
|
||||
import PlaylistParser from "./parsers/PlaylistParser"
|
||||
import SearchParser from "./parsers/SearchParser"
|
||||
import SongParser from "./parsers/SongParser"
|
||||
import VideoParser from "./parsers/VideoParser"
|
||||
import {
|
||||
AlbumDetailed, AlbumFull, ArtistDetailed, ArtistFull, PlaylistFull, SearchResult, SongDetailed,
|
||||
SongFull, VideoDetailed, VideoFull
|
||||
} from "./schemas"
|
||||
import traverse from "./utils/traverse"
|
||||
import traverseList from "./utils/traverseList"
|
||||
import traverseString from "./utils/traverseString"
|
||||
|
|
@ -122,7 +123,7 @@ export default class YTMusic {
|
|||
const headers: Record<string, any> = {
|
||||
...this.client.defaults.headers,
|
||||
"x-origin": this.client.defaults.baseURL,
|
||||
"X-Goog-Visitor-Id": this.config.VISITOR_DATA || '',
|
||||
"X-Goog-Visitor-Id": this.config.VISITOR_DATA || "",
|
||||
"X-YouTube-Client-Name": this.config.INNERTUBE_CONTEXT_CLIENT_NAME,
|
||||
"X-YouTube-Client-Version": this.config.INNERTUBE_CLIENT_VERSION,
|
||||
"X-YouTube-Device": this.config.DEVICE,
|
||||
|
|
@ -213,7 +214,7 @@ export default class YTMusic {
|
|||
*
|
||||
* @param query Query string
|
||||
*/
|
||||
public async search(query: string): Promise<SearchResult[]> {
|
||||
public async search(query: string): Promise<z.infer<typeof SearchResult>[]> {
|
||||
const searchData = await this.constructRequest("search", {
|
||||
query,
|
||||
params: null
|
||||
|
|
@ -227,7 +228,7 @@ export default class YTMusic {
|
|||
*
|
||||
* @param query Query string
|
||||
*/
|
||||
public async searchSongs(query: string): Promise<SongDetailed[]> {
|
||||
public async searchSongs(query: string): Promise<z.infer<typeof SongDetailed>[]> {
|
||||
const searchData = await this.constructRequest("search", {
|
||||
query,
|
||||
params: "Eg-KAQwIARAAGAAgACgAMABqChAEEAMQCRAFEAo%3D"
|
||||
|
|
@ -243,7 +244,7 @@ export default class YTMusic {
|
|||
*
|
||||
* @param query Query string
|
||||
*/
|
||||
public async searchVideos(query: string): Promise<VideoDetailed[]> {
|
||||
public async searchVideos(query: string): Promise<z.infer<typeof VideoDetailed>[]> {
|
||||
const searchData = await this.constructRequest("search", {
|
||||
query,
|
||||
params: "Eg-KAQwIABABGAAgACgAMABqChAEEAMQCRAFEAo%3D"
|
||||
|
|
@ -259,7 +260,7 @@ export default class YTMusic {
|
|||
*
|
||||
* @param query Query string
|
||||
*/
|
||||
public async searchArtists(query: string): Promise<ArtistDetailed[]> {
|
||||
public async searchArtists(query: string): Promise<z.infer<typeof ArtistDetailed>[]> {
|
||||
const searchData = await this.constructRequest("search", {
|
||||
query,
|
||||
params: "Eg-KAQwIABAAGAAgASgAMABqChAEEAMQCRAFEAo%3D"
|
||||
|
|
@ -275,7 +276,7 @@ export default class YTMusic {
|
|||
*
|
||||
* @param query Query string
|
||||
*/
|
||||
public async searchAlbums(query: string): Promise<AlbumDetailed[]> {
|
||||
public async searchAlbums(query: string): Promise<z.infer<typeof AlbumDetailed>[]> {
|
||||
const searchData = await this.constructRequest("search", {
|
||||
query,
|
||||
params: "Eg-KAQwIABAAGAEgACgAMABqChAEEAMQCRAFEAo%3D"
|
||||
|
|
@ -291,7 +292,7 @@ export default class YTMusic {
|
|||
*
|
||||
* @param query Query string
|
||||
*/
|
||||
public async searchPlaylists(query: string): Promise<PlaylistFull[]> {
|
||||
public async searchPlaylists(query: string): Promise<z.infer<typeof PlaylistFull>[]> {
|
||||
const searchData = await this.constructRequest("search", {
|
||||
query,
|
||||
params: "Eg-KAQwIABAAGAAgACgBMABqChAEEAMQCRAFEAo%3D"
|
||||
|
|
@ -308,7 +309,7 @@ export default class YTMusic {
|
|||
* @param videoId Video ID
|
||||
* @returns Song Data
|
||||
*/
|
||||
public async getSong(videoId: string): Promise<SongFull> {
|
||||
public async getSong(videoId: string): Promise<z.infer<typeof SongFull>> {
|
||||
if (!videoId.match(/^[a-zA-Z0-9-_]{11}$/)) throw new Error("Invalid videoId")
|
||||
const data = await this.constructRequest("player", { videoId })
|
||||
|
||||
|
|
@ -323,7 +324,7 @@ export default class YTMusic {
|
|||
* @param videoId Video ID
|
||||
* @returns Video Data
|
||||
*/
|
||||
public async getVideo(videoId: string): Promise<VideoFull> {
|
||||
public async getVideo(videoId: string): Promise<z.infer<typeof VideoFull>> {
|
||||
if (!videoId.match(/^[a-zA-Z0-9-_]{11}$/)) throw new Error("Invalid videoId")
|
||||
const data = await this.constructRequest("player", { videoId })
|
||||
|
||||
|
|
@ -338,8 +339,10 @@ export default class YTMusic {
|
|||
* @param artistId Artist ID
|
||||
* @returns Artist Data
|
||||
*/
|
||||
public async getArtist(artistId: string): Promise<ArtistFull> {
|
||||
const data = await this.constructRequest("browse", { browseId: artistId })
|
||||
public async getArtist(artistId: string): Promise<z.infer<typeof ArtistFull>> {
|
||||
const data = await this.constructRequest("browse", {
|
||||
browseId: artistId
|
||||
})
|
||||
|
||||
return ArtistParser.parse(data, artistId)
|
||||
}
|
||||
|
|
@ -350,13 +353,17 @@ export default class YTMusic {
|
|||
* @param artistId Artist ID
|
||||
* @returns Artist's Songs
|
||||
*/
|
||||
public async getArtistSongs(artistId: string): Promise<SongDetailed[]> {
|
||||
const artistData = await this.constructRequest("browse", { browseId: artistId })
|
||||
public async getArtistSongs(artistId: string): Promise<z.infer<typeof SongDetailed>[]> {
|
||||
const artistData = await this.constructRequest("browse", {
|
||||
browseId: artistId
|
||||
})
|
||||
const browseToken = traverse(artistData, "musicShelfRenderer", "title", "browseId")
|
||||
|
||||
if (browseToken instanceof Array) return []
|
||||
|
||||
const songsData = await this.constructRequest("browse", { browseId: browseToken })
|
||||
const songsData = await this.constructRequest("browse", {
|
||||
browseId: browseToken
|
||||
})
|
||||
const continueToken = traverse(songsData, "continuation")
|
||||
const moreSongsData = await this.constructRequest(
|
||||
"browse",
|
||||
|
|
@ -376,8 +383,10 @@ export default class YTMusic {
|
|||
* @param artistId Artist ID
|
||||
* @returns Artist's Albums
|
||||
*/
|
||||
public async getArtistAlbums(artistId: string): Promise<AlbumDetailed[]> {
|
||||
const artistData = await this.constructRequest("browse", { browseId: artistId })
|
||||
public async getArtistAlbums(artistId: string): Promise<z.infer<typeof AlbumDetailed>[]> {
|
||||
const artistData = await this.constructRequest("browse", {
|
||||
browseId: artistId
|
||||
})
|
||||
const artistAlbumsData = traverseList(artistData, "musicCarouselShelfRenderer")[0]
|
||||
const browseBody = traverse(artistAlbumsData, "moreContentButton", "browseEndpoint")
|
||||
|
||||
|
|
@ -397,8 +406,10 @@ export default class YTMusic {
|
|||
* @param albumId Album ID
|
||||
* @returns Album Data
|
||||
*/
|
||||
public async getAlbum(albumId: string): Promise<AlbumFull> {
|
||||
const data = await this.constructRequest("browse", { browseId: albumId })
|
||||
public async getAlbum(albumId: string): Promise<z.infer<typeof AlbumFull>> {
|
||||
const data = await this.constructRequest("browse", {
|
||||
browseId: albumId
|
||||
})
|
||||
|
||||
return AlbumParser.parse(data, albumId)
|
||||
}
|
||||
|
|
@ -409,9 +420,11 @@ export default class YTMusic {
|
|||
* @param playlistId Playlist ID
|
||||
* @returns Playlist Data
|
||||
*/
|
||||
public async getPlaylist(playlistId: string): Promise<PlaylistFull> {
|
||||
public async getPlaylist(playlistId: string): Promise<z.infer<typeof PlaylistFull>> {
|
||||
if (playlistId.startsWith("PL")) playlistId = "VL" + playlistId
|
||||
const data = await this.constructRequest("browse", { browseId: playlistId })
|
||||
const data = await this.constructRequest("browse", {
|
||||
browseId: playlistId
|
||||
})
|
||||
|
||||
return PlaylistParser.parse(data, playlistId)
|
||||
}
|
||||
|
|
@ -422,9 +435,11 @@ export default class YTMusic {
|
|||
* @param playlistId Playlist ID
|
||||
* @returns Playlist's Videos
|
||||
*/
|
||||
public async getPlaylistVideos(playlistId: string): Promise<VideoDetailed[]> {
|
||||
public async getPlaylistVideos(playlistId: string): Promise<z.infer<typeof VideoDetailed>[]> {
|
||||
if (playlistId.startsWith("PL")) playlistId = "VL" + playlistId
|
||||
const playlistData = await this.constructRequest("browse", { browseId: playlistId })
|
||||
const playlistData = await this.constructRequest("browse", {
|
||||
browseId: playlistId
|
||||
})
|
||||
|
||||
const songs = traverseList(
|
||||
playlistData,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
import assert from "assert"
|
||||
import describeParallel from "mocha.parallel"
|
||||
import { iValidationError, LIST, STRING, validate } from "validate-any"
|
||||
import Validator from "validate-any/dist/classes/Validator"
|
||||
import { z } from "zod"
|
||||
|
||||
import YTMusic from "../"
|
||||
import {
|
||||
ALBUM_DETAILED, ALBUM_FULL, ARTIST_DETAILED, ARTIST_FULL, PLAYLIST_FULL, SONG_DETAILED,
|
||||
SONG_FULL, VIDEO_DETAILED, VIDEO_FULL
|
||||
} from "../interfaces"
|
||||
AlbumDetailed, AlbumFull, ArtistDetailed, ArtistFull, PlaylistFull, SongDetailed, SongFull,
|
||||
VideoDetailed, VideoFull
|
||||
} from "../schemas"
|
||||
import YTMusic from "../YTMusic"
|
||||
|
||||
const issues: iValidationError[][] = []
|
||||
const errors = <z.ZodError<any>[]>[]
|
||||
const queries = ["Lilac", "Weekend", "Eill", "Eminem", "Lisa Hannigan"]
|
||||
const expect = (data: any, validator: Validator<any>) => {
|
||||
const { errors } = validate(data, validator)
|
||||
if (errors.length > 0) {
|
||||
issues.push(errors)
|
||||
const expect = (data: any, schema: z.Schema) => {
|
||||
const result = schema.safeParse(data)
|
||||
if (!result.success) {
|
||||
errors.push(result.error)
|
||||
}
|
||||
assert.equal(errors.length, 0)
|
||||
assert.equal(result.success, true)
|
||||
}
|
||||
|
||||
const ytmusic = new YTMusic()
|
||||
|
|
@ -26,90 +25,95 @@ queries.forEach(query => {
|
|||
describeParallel("Query: " + query, () => {
|
||||
it("Search suggestions", async () => {
|
||||
const suggestions = await ytmusic.getSearchSuggestions(query)
|
||||
expect(suggestions, LIST(STRING()))
|
||||
expect(suggestions, z.array(z.string()))
|
||||
})
|
||||
|
||||
it("Search Songs", async () => {
|
||||
const songs = await ytmusic.searchSongs(query)
|
||||
expect(songs, LIST(SONG_DETAILED))
|
||||
expect(songs, z.array(SongDetailed))
|
||||
})
|
||||
|
||||
it("Search Videos", async () => {
|
||||
const videos = await ytmusic.searchVideos(query)
|
||||
expect(videos, LIST(VIDEO_DETAILED))
|
||||
expect(videos, z.array(VideoDetailed))
|
||||
})
|
||||
|
||||
it("Search Artists", async () => {
|
||||
const artists = await ytmusic.searchArtists(query)
|
||||
expect(artists, LIST(ARTIST_DETAILED))
|
||||
expect(artists, z.array(ArtistDetailed))
|
||||
})
|
||||
|
||||
it("Search Albums", async () => {
|
||||
const albums = await ytmusic.searchAlbums(query)
|
||||
expect(albums, LIST(ALBUM_DETAILED))
|
||||
expect(albums, z.array(AlbumDetailed))
|
||||
})
|
||||
|
||||
it("Search Playlists", async () => {
|
||||
const playlists = await ytmusic.searchPlaylists(query)
|
||||
expect(playlists, LIST(PLAYLIST_FULL))
|
||||
expect(playlists, z.array(PlaylistFull))
|
||||
})
|
||||
|
||||
it("Search All", async () => {
|
||||
const results = await ytmusic.search(query)
|
||||
expect(
|
||||
results,
|
||||
LIST(ALBUM_DETAILED, ARTIST_DETAILED, PLAYLIST_FULL, SONG_DETAILED, VIDEO_DETAILED)
|
||||
z.array(
|
||||
AlbumDetailed.or(ArtistDetailed)
|
||||
.or(PlaylistFull)
|
||||
.or(SongDetailed)
|
||||
.or(VideoDetailed)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
it("Get details of the first song result", async () => {
|
||||
const songs = await ytmusic.searchSongs(query)
|
||||
const song = await ytmusic.getSong(songs[0]!.videoId)
|
||||
expect(song, SONG_FULL)
|
||||
expect(song, SongFull)
|
||||
})
|
||||
|
||||
it("Get details of the first video result", async () => {
|
||||
const videos = await ytmusic.searchVideos(query)
|
||||
const video = await ytmusic.getVideo(videos[0]!.videoId)
|
||||
expect(video, VIDEO_FULL)
|
||||
expect(video, VideoFull)
|
||||
})
|
||||
|
||||
it("Get details of the first artist result", async () => {
|
||||
const artists = await ytmusic.searchArtists(query)
|
||||
const artist = await ytmusic.getArtist(artists[0]!.artistId)
|
||||
expect(artist, ARTIST_FULL)
|
||||
expect(artist, ArtistFull)
|
||||
})
|
||||
|
||||
it("Get the songs of the first artist result", async () => {
|
||||
const artists = await ytmusic.searchArtists(query)
|
||||
const songs = await ytmusic.getArtistSongs(artists[0]!.artistId)
|
||||
expect(songs, LIST(SONG_DETAILED))
|
||||
expect(songs, z.array(SongDetailed))
|
||||
})
|
||||
|
||||
it("Get the albums of the first artist result", async () => {
|
||||
const artists = await ytmusic.searchArtists(query)
|
||||
const albums = await ytmusic.getArtistAlbums(artists[0]!.artistId)
|
||||
expect(albums, LIST(ALBUM_DETAILED))
|
||||
expect(albums, z.array(AlbumDetailed))
|
||||
})
|
||||
|
||||
it("Get details of the first album result", async () => {
|
||||
const albums = await ytmusic.searchAlbums(query)
|
||||
const album = await ytmusic.getAlbum(albums[0]!.albumId)
|
||||
expect(album, ALBUM_FULL)
|
||||
expect(album, AlbumFull)
|
||||
})
|
||||
|
||||
it("Get details of the first playlist result", async () => {
|
||||
const playlists = await ytmusic.searchPlaylists(query)
|
||||
const playlist = await ytmusic.getPlaylist(playlists[0]!.playlistId)
|
||||
expect(playlist, PLAYLIST_FULL)
|
||||
expect(playlist, PlaylistFull)
|
||||
})
|
||||
|
||||
it("Get the videos of the first playlist result", async () => {
|
||||
const playlists = await ytmusic.searchPlaylists(query)
|
||||
const videos = await ytmusic.getPlaylistVideos(playlists[0]!.playlistId)
|
||||
expect(videos, LIST(VIDEO_DETAILED))
|
||||
expect(videos, z.array(VideoDetailed))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after(() => console.log("Issues:", issues))
|
||||
after(() => console.log("Issues:", errors))
|
||||
|
|
|
|||
93
src/index.ts
93
src/index.ts
|
|
@ -1,93 +0,0 @@
|
|||
import YTMusic from "./YTMusic"
|
||||
|
||||
export interface ThumbnailFull {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface SongDetailed {
|
||||
type: "SONG"
|
||||
videoId: string
|
||||
name: string
|
||||
artists: ArtistBasic[]
|
||||
album: AlbumBasic
|
||||
duration: number
|
||||
thumbnails: ThumbnailFull[]
|
||||
}
|
||||
|
||||
export interface SongFull extends Omit<SongDetailed, "album"> {
|
||||
description: string
|
||||
formats: any[]
|
||||
adaptiveFormats: any[]
|
||||
}
|
||||
|
||||
export interface VideoDetailed {
|
||||
type: "VIDEO"
|
||||
videoId: string
|
||||
name: string
|
||||
artists: ArtistBasic[]
|
||||
duration: number
|
||||
thumbnails: ThumbnailFull[]
|
||||
}
|
||||
|
||||
export interface VideoFull extends VideoDetailed {
|
||||
description: string
|
||||
unlisted: boolean
|
||||
familySafe: boolean
|
||||
paid: boolean
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface ArtistBasic {
|
||||
artistId: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface ArtistDetailed extends ArtistBasic {
|
||||
type: "ARTIST"
|
||||
artistId: string
|
||||
thumbnails: ThumbnailFull[]
|
||||
}
|
||||
|
||||
export interface ArtistFull extends ArtistDetailed {
|
||||
description: string
|
||||
topSongs: Omit<SongDetailed, "duration">[]
|
||||
topAlbums: AlbumDetailed[]
|
||||
}
|
||||
|
||||
export interface AlbumBasic {
|
||||
albumId: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface AlbumDetailed extends AlbumBasic {
|
||||
type: "ALBUM"
|
||||
playlistId: string
|
||||
artists: ArtistBasic[]
|
||||
year: number | null
|
||||
thumbnails: ThumbnailFull[]
|
||||
}
|
||||
|
||||
export interface AlbumFull extends AlbumDetailed {
|
||||
description: string
|
||||
songs: SongDetailed[]
|
||||
}
|
||||
|
||||
export interface PlaylistFull {
|
||||
type: "PLAYLIST"
|
||||
playlistId: string
|
||||
name: string
|
||||
artist: ArtistBasic
|
||||
videoCount: number
|
||||
thumbnails: ThumbnailFull[]
|
||||
}
|
||||
|
||||
export type SearchResult =
|
||||
| SongDetailed
|
||||
| VideoDetailed
|
||||
| AlbumDetailed
|
||||
| ArtistDetailed
|
||||
| PlaylistFull
|
||||
|
||||
export default YTMusic
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import { BOOLEAN, LIST, NULL, NUMBER, OBJECT, OR, STRING } from "validate-any"
|
||||
import ObjectValidator from "validate-any/dist/validators/ObjectValidator"
|
||||
|
||||
import {
|
||||
AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic, ArtistDetailed, ArtistFull, PlaylistFull,
|
||||
SongDetailed, SongFull, ThumbnailFull, VideoDetailed, VideoFull
|
||||
} from "./"
|
||||
|
||||
export const THUMBNAIL_FULL: ObjectValidator<ThumbnailFull> = OBJECT({
|
||||
url: STRING(),
|
||||
width: NUMBER(),
|
||||
height: NUMBER()
|
||||
})
|
||||
|
||||
export const ARTIST_BASIC: ObjectValidator<ArtistBasic> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
export const ALBUM_BASIC: ObjectValidator<AlbumBasic> = OBJECT({
|
||||
albumId: STRING(),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
export const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const VIDEO_DETAILED: ObjectValidator<VideoDetailed> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const ARTIST_DETAILED: ObjectValidator<ArtistDetailed> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const ALBUM_DETAILED: ObjectValidator<AlbumDetailed> = OBJECT({
|
||||
type: STRING("ALBUM"),
|
||||
albumId: STRING(),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
year: OR(NUMBER(), NULL()),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
formats: LIST(OBJECT()),
|
||||
adaptiveFormats: LIST(OBJECT())
|
||||
})
|
||||
|
||||
export const VIDEO_FULL: ObjectValidator<VideoFull> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
unlisted: BOOLEAN(),
|
||||
familySafe: BOOLEAN(),
|
||||
paid: BOOLEAN(),
|
||||
tags: LIST(STRING())
|
||||
})
|
||||
|
||||
export const ARTIST_FULL: ObjectValidator<ArtistFull> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
topSongs: LIST(
|
||||
OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
),
|
||||
topAlbums: LIST(ALBUM_DETAILED)
|
||||
})
|
||||
|
||||
export const ALBUM_FULL: ObjectValidator<AlbumFull> = OBJECT({
|
||||
type: STRING("ALBUM"),
|
||||
albumId: STRING(),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
year: OR(NUMBER(), NULL()),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
songs: LIST(SONG_DETAILED)
|
||||
})
|
||||
|
||||
export const PLAYLIST_FULL: ObjectValidator<PlaylistFull> = OBJECT({
|
||||
type: STRING("PLAYLIST"),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artist: ARTIST_BASIC,
|
||||
videoCount: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic } from "../"
|
||||
import { ALBUM_DETAILED, ALBUM_FULL } from "../interfaces"
|
||||
import { AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic } from "../schemas"
|
||||
import checkType from "../utils/checkType"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
|
|
@ -19,7 +18,7 @@ export default class AlbumParser {
|
|||
}))
|
||||
const thumbnails = traverseList(data, "header", "thumbnails")
|
||||
|
||||
return checkType<AlbumFull>(
|
||||
return checkType(
|
||||
{
|
||||
type: "ALBUM",
|
||||
...albumBasic,
|
||||
|
|
@ -34,14 +33,14 @@ export default class AlbumParser {
|
|||
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
||||
)
|
||||
},
|
||||
ALBUM_FULL
|
||||
AlbumFull
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): AlbumDetailed {
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
|
||||
return checkType<AlbumDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
|
|
@ -56,12 +55,12 @@ export default class AlbumParser {
|
|||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
AlbumDetailed
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistAlbum(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
||||
return checkType<AlbumDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
|
|
@ -71,12 +70,12 @@ export default class AlbumParser {
|
|||
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
AlbumDetailed
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistTopAlbums(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
||||
return checkType<AlbumDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
|
|
@ -86,7 +85,7 @@ export default class AlbumParser {
|
|||
year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
AlbumDetailed
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { ArtistBasic, ArtistDetailed, ArtistFull } from "../"
|
||||
import { ARTIST_DETAILED, ARTIST_FULL } from "../interfaces"
|
||||
import { ArtistBasic, ArtistDetailed, ArtistFull } from "../schemas"
|
||||
import checkType from "../utils/checkType"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
|
|
@ -15,7 +14,7 @@ export default class ArtistParser {
|
|||
|
||||
const description = traverseString(data, "header", "description", "text")()
|
||||
|
||||
return checkType<ArtistFull>(
|
||||
return checkType(
|
||||
{
|
||||
type: "ARTIST",
|
||||
...artistBasic,
|
||||
|
|
@ -30,21 +29,21 @@ export default class ArtistParser {
|
|||
AlbumParser.parseArtistTopAlbums(item, artistBasic)
|
||||
)
|
||||
},
|
||||
ARTIST_FULL
|
||||
ArtistFull
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): ArtistDetailed {
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
|
||||
return checkType<ArtistDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "ARTIST",
|
||||
artistId: traverseString(item, "browseId")(),
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ARTIST_DETAILED
|
||||
ArtistDetailed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { PlaylistFull } from "../"
|
||||
import { PLAYLIST_FULL } from "../interfaces"
|
||||
import { PlaylistFull } from "../schemas"
|
||||
import checkType from "../utils/checkType"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
|
||||
export default class PlaylistParser {
|
||||
public static parse(data: any, playlistId: string): PlaylistFull {
|
||||
return checkType<PlaylistFull>(
|
||||
return checkType(
|
||||
{
|
||||
type: "PLAYLIST",
|
||||
playlistId,
|
||||
|
|
@ -22,7 +21,7 @@ export default class PlaylistParser {
|
|||
.replaceAll(",", ""),
|
||||
thumbnails: traverseList(data, "header", "thumbnails")
|
||||
},
|
||||
PLAYLIST_FULL
|
||||
PlaylistFull
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +29,7 @@ export default class PlaylistParser {
|
|||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const artistId = traverseString(flexColumns[1], "browseId")()
|
||||
|
||||
return checkType<PlaylistFull>(
|
||||
return checkType(
|
||||
{
|
||||
type: "PLAYLIST",
|
||||
playlistId: traverseString(item, "overlay", "playlistId")(),
|
||||
|
|
@ -46,7 +45,7 @@ export default class PlaylistParser {
|
|||
.replaceAll(",", ""),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
PLAYLIST_FULL
|
||||
PlaylistFull
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SearchResult } from "../"
|
||||
import { SearchResult } from "../schemas"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import AlbumParser from "./AlbumParser"
|
||||
import ArtistParser from "./ArtistParser"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import { LIST, OBJECT, STRING } from "validate-any"
|
||||
|
||||
import { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from "../"
|
||||
import { ALBUM_BASIC, ARTIST_BASIC, SONG_DETAILED, SONG_FULL, THUMBNAIL_FULL } from "../interfaces"
|
||||
import { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from "../schemas"
|
||||
import checkType from "../utils/checkType"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
|
|
@ -9,7 +6,7 @@ import Parser from "./Parser"
|
|||
|
||||
export default class SongParser {
|
||||
public static parse(data: any): SongFull {
|
||||
return checkType<SongFull>(
|
||||
return checkType(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: traverseString(data, "videoDetails", "videoId")(),
|
||||
|
|
@ -26,14 +23,14 @@ export default class SongParser {
|
|||
formats: traverseList(data, "streamingData", "formats"),
|
||||
adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats")
|
||||
},
|
||||
SONG_FULL
|
||||
SongFull
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): SongDetailed {
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
|
||||
return checkType<SongDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: traverseString(item, "playlistItemData", "videoId")(),
|
||||
|
|
@ -52,7 +49,7 @@ export default class SongParser {
|
|||
duration: Parser.parseDuration(traverseString(flexColumns[1], "runs", "text")(-1)),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
SONG_DETAILED
|
||||
SongDetailed
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +57,7 @@ export default class SongParser {
|
|||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playlistItemData", "videoId")()
|
||||
|
||||
return checkType<SongDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId,
|
||||
|
|
@ -80,7 +77,7 @@ export default class SongParser {
|
|||
),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
SONG_DETAILED
|
||||
SongDetailed
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +88,7 @@ export default class SongParser {
|
|||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playlistItemData", "videoId")()
|
||||
|
||||
return checkType<Omit<SongDetailed, "duration">>(
|
||||
return checkType(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId,
|
||||
|
|
@ -103,14 +100,7 @@ export default class SongParser {
|
|||
},
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
SongDetailed.omit({ duration: true })
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +113,7 @@ export default class SongParser {
|
|||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playlistItemData", "videoId")()
|
||||
|
||||
return checkType<SongDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId,
|
||||
|
|
@ -135,7 +125,7 @@ export default class SongParser {
|
|||
),
|
||||
thumbnails
|
||||
},
|
||||
SONG_DETAILED
|
||||
SongDetailed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { VideoDetailed, VideoFull } from "../"
|
||||
import { VIDEO_DETAILED } from "../interfaces"
|
||||
import { VideoDetailed, VideoFull } from "../schemas"
|
||||
import checkType from "../utils/checkType"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
|
|
@ -53,7 +52,7 @@ export default class VideoParser {
|
|||
traverseString(item, "playNavigationEndpoint", "videoId")() ||
|
||||
traverseList(item, "thumbnails")[0].url.match(/https:\/\/i\.ytimg\.com\/vi\/(.+)\//)[1]
|
||||
|
||||
return checkType<VideoDetailed>(
|
||||
return checkType(
|
||||
{
|
||||
type: "VIDEO",
|
||||
videoId,
|
||||
|
|
@ -69,7 +68,7 @@ export default class VideoParser {
|
|||
),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
VIDEO_DETAILED
|
||||
VideoDetailed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
import { z } from "zod"
|
||||
|
||||
export type ThumbnailFull = z.infer<typeof ThumbnailFull>
|
||||
export const ThumbnailFull = z.object({
|
||||
url: z.string(),
|
||||
width: z.number(),
|
||||
height: z.number()
|
||||
})
|
||||
|
||||
export type ArtistBasic = z.infer<typeof ArtistBasic>
|
||||
export const ArtistBasic = z.object({
|
||||
artistId: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
|
||||
export type AlbumBasic = z.infer<typeof AlbumBasic>
|
||||
export const AlbumBasic = z.object({
|
||||
albumId: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
|
||||
export type SongDetailed = z.infer<typeof SongDetailed>
|
||||
export const SongDetailed = z.object({
|
||||
type: z.literal("SONG"),
|
||||
videoId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
album: AlbumBasic,
|
||||
duration: z.number(),
|
||||
thumbnails: z.array(ThumbnailFull)
|
||||
})
|
||||
|
||||
export type VideoDetailed = z.infer<typeof VideoDetailed>
|
||||
export const VideoDetailed = z.object({
|
||||
type: z.literal("VIDEO"),
|
||||
videoId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
duration: z.number(),
|
||||
thumbnails: z.array(ThumbnailFull)
|
||||
})
|
||||
|
||||
export type ArtistDetailed = z.infer<typeof ArtistDetailed>
|
||||
export const ArtistDetailed = z.object({
|
||||
artistId: z.string(),
|
||||
name: z.string(),
|
||||
type: z.literal("ARTIST"),
|
||||
thumbnails: z.array(ThumbnailFull)
|
||||
})
|
||||
|
||||
export type AlbumDetailed = z.infer<typeof AlbumDetailed>
|
||||
export const AlbumDetailed = z.object({
|
||||
type: z.literal("ALBUM"),
|
||||
albumId: z.string(),
|
||||
playlistId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
year: z.number().nullable(),
|
||||
thumbnails: z.array(ThumbnailFull)
|
||||
})
|
||||
|
||||
export type SongFull = z.infer<typeof SongFull>
|
||||
export const SongFull = z.object({
|
||||
type: z.literal("SONG"),
|
||||
videoId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
duration: z.number(),
|
||||
thumbnails: z.array(ThumbnailFull),
|
||||
description: z.string(),
|
||||
formats: z.array(z.any()),
|
||||
adaptiveFormats: z.array(z.any())
|
||||
})
|
||||
|
||||
export type VideoFull = z.infer<typeof VideoFull>
|
||||
export const VideoFull = z.object({
|
||||
type: z.literal("VIDEO"),
|
||||
videoId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
duration: z.number(),
|
||||
thumbnails: z.array(ThumbnailFull),
|
||||
description: z.string(),
|
||||
unlisted: z.boolean(),
|
||||
familySafe: z.boolean(),
|
||||
paid: z.boolean(),
|
||||
tags: z.array(z.string())
|
||||
})
|
||||
|
||||
export type ArtistFull = z.infer<typeof ArtistFull>
|
||||
export const ArtistFull = z.object({
|
||||
artistId: z.string(),
|
||||
name: z.string(),
|
||||
type: z.literal("ARTIST"),
|
||||
thumbnails: z.array(ThumbnailFull),
|
||||
description: z.string(),
|
||||
topSongs: z.array(
|
||||
z.object({
|
||||
type: z.literal("SONG"),
|
||||
videoId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
album: AlbumBasic,
|
||||
thumbnails: z.array(ThumbnailFull)
|
||||
})
|
||||
),
|
||||
topAlbums: z.array(AlbumDetailed)
|
||||
})
|
||||
|
||||
export type AlbumFull = z.infer<typeof AlbumFull>
|
||||
export const AlbumFull = z.object({
|
||||
type: z.literal("ALBUM"),
|
||||
albumId: z.string(),
|
||||
playlistId: z.string(),
|
||||
name: z.string(),
|
||||
artists: z.array(ArtistBasic),
|
||||
year: z.number().nullable(),
|
||||
thumbnails: z.array(ThumbnailFull),
|
||||
description: z.string(),
|
||||
songs: z.array(SongDetailed)
|
||||
})
|
||||
|
||||
export type PlaylistFull = z.infer<typeof PlaylistFull>
|
||||
export const PlaylistFull = z.object({
|
||||
type: z.literal("PLAYLIST"),
|
||||
playlistId: z.string(),
|
||||
name: z.string(),
|
||||
artist: ArtistBasic,
|
||||
videoCount: z.number(),
|
||||
thumbnails: z.array(ThumbnailFull)
|
||||
})
|
||||
|
||||
export type SearchResult = z.infer<typeof SearchResult>
|
||||
export const SearchResult = SongDetailed.or(VideoDetailed)
|
||||
.or(AlbumDetailed)
|
||||
.or(ArtistDetailed)
|
||||
.or(PlaylistFull)
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
import Validator from "validate-any/dist/classes/Validator"
|
||||
import { validate } from "validate-any"
|
||||
import { z } from "zod"
|
||||
import zodtojson from "zod-to-json-schema"
|
||||
|
||||
export default <T>(data: T, validator: Validator<T>): T => {
|
||||
const result = validate(data, validator)
|
||||
export default <T extends z.Schema>(data: z.infer<T>, schema: T): z.infer<T> => {
|
||||
const result = schema.safeParse(data)
|
||||
if (result.success) {
|
||||
return result.data
|
||||
return data
|
||||
} else {
|
||||
console.error(
|
||||
"Invalid data schema, please report to https://github.com/zS1L3NT/ts-npm-ytmusic-api/issues/new/choose",
|
||||
JSON.stringify(
|
||||
{
|
||||
expected: validator.getSchema(),
|
||||
actual: data,
|
||||
errors: result.errors
|
||||
schema: zodtojson(schema),
|
||||
data,
|
||||
error: result.error
|
||||
},
|
||||
null,
|
||||
2
|
||||
|
|
|
|||
Loading…
Reference in New Issue