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