mirror of
https://github.com/Kirazul/Verome-API.git
synced 2026-03-08 08:15:20 +00:00
Remove CF worker files, m4a test files, and download API
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"javascript.autoClosingTags": false
|
||||||
|
}
|
||||||
366
deno.lock
generated
366
deno.lock
generated
@@ -1,5 +1,371 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"version": "5",
|
||||||
|
"specifiers": {
|
||||||
|
"npm:@ybd-project/ytdl-core@6.0.7": "6.0.7",
|
||||||
|
"npm:youtubei.js@16.0.1": "16.0.1"
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"@asamuzakjp/css-color@3.2.0_@csstools+css-parser-algorithms@3.0.5__@csstools+css-tokenizer@3.0.4_@csstools+css-tokenizer@3.0.4": {
|
||||||
|
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@csstools/css-calc",
|
||||||
|
"@csstools/css-color-parser",
|
||||||
|
"@csstools/css-parser-algorithms",
|
||||||
|
"@csstools/css-tokenizer",
|
||||||
|
"lru-cache"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@bufbuild/protobuf@2.10.2": {
|
||||||
|
"integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A=="
|
||||||
|
},
|
||||||
|
"@csstools/color-helpers@5.1.0": {
|
||||||
|
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="
|
||||||
|
},
|
||||||
|
"@csstools/css-calc@2.1.4_@csstools+css-parser-algorithms@3.0.5__@csstools+css-tokenizer@3.0.4_@csstools+css-tokenizer@3.0.4": {
|
||||||
|
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@csstools/css-parser-algorithms",
|
||||||
|
"@csstools/css-tokenizer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@csstools/css-color-parser@3.1.0_@csstools+css-parser-algorithms@3.0.5__@csstools+css-tokenizer@3.0.4_@csstools+css-tokenizer@3.0.4": {
|
||||||
|
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@csstools/color-helpers",
|
||||||
|
"@csstools/css-calc",
|
||||||
|
"@csstools/css-parser-algorithms",
|
||||||
|
"@csstools/css-tokenizer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@csstools/css-parser-algorithms@3.0.5_@csstools+css-tokenizer@3.0.4": {
|
||||||
|
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@csstools/css-tokenizer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@csstools/css-tokenizer@3.0.4": {
|
||||||
|
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="
|
||||||
|
},
|
||||||
|
"@ybd-project/ytdl-core@6.0.7": {
|
||||||
|
"integrity": "sha512-g/fBKJa21ivR5PpH09s6ogG2NW3WUyg3cjJsxLeYwj4vM8JMd+z3CSeulc4ii43YYwC0MioxbvWvkvEzxNBX1g==",
|
||||||
|
"dependencies": [
|
||||||
|
"acorn",
|
||||||
|
"bgutils-js",
|
||||||
|
"jsdom",
|
||||||
|
"undici"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"acorn@8.15.0": {
|
||||||
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
|
"bin": true
|
||||||
|
},
|
||||||
|
"agent-base@7.1.4": {
|
||||||
|
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="
|
||||||
|
},
|
||||||
|
"asynckit@0.4.0": {
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
|
"bgutils-js@3.2.0": {
|
||||||
|
"integrity": "sha512-CacO15JvxbclbLeCAAm9DETGlLuisRGWpPigoRvNsccSCPEC4pwYwA2g2x/pv7Om/sk79d4ib35V5HHmxPBpDg=="
|
||||||
|
},
|
||||||
|
"call-bind-apply-helpers@1.0.2": {
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"es-errors",
|
||||||
|
"function-bind"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"combined-stream@1.0.8": {
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": [
|
||||||
|
"delayed-stream"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cssstyle@4.6.0": {
|
||||||
|
"integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@asamuzakjp/css-color",
|
||||||
|
"rrweb-cssom@0.8.0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data-urls@5.0.0": {
|
||||||
|
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
|
||||||
|
"dependencies": [
|
||||||
|
"whatwg-mimetype",
|
||||||
|
"whatwg-url"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"debug@4.4.3": {
|
||||||
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
|
"dependencies": [
|
||||||
|
"ms"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"decimal.js@10.6.0": {
|
||||||
|
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
|
||||||
|
},
|
||||||
|
"delayed-stream@1.0.0": {
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||||
|
},
|
||||||
|
"dunder-proto@1.0.1": {
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"dependencies": [
|
||||||
|
"call-bind-apply-helpers",
|
||||||
|
"es-errors",
|
||||||
|
"gopd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"entities@6.0.1": {
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="
|
||||||
|
},
|
||||||
|
"es-define-property@1.0.1": {
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
|
||||||
|
},
|
||||||
|
"es-errors@1.3.0": {
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||||
|
},
|
||||||
|
"es-object-atoms@1.1.1": {
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"dependencies": [
|
||||||
|
"es-errors"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"es-set-tostringtag@2.1.0": {
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"dependencies": [
|
||||||
|
"es-errors",
|
||||||
|
"get-intrinsic",
|
||||||
|
"has-tostringtag",
|
||||||
|
"hasown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"form-data@4.0.5": {
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"dependencies": [
|
||||||
|
"asynckit",
|
||||||
|
"combined-stream",
|
||||||
|
"es-set-tostringtag",
|
||||||
|
"hasown",
|
||||||
|
"mime-types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"function-bind@1.1.2": {
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||||
|
},
|
||||||
|
"get-intrinsic@1.3.0": {
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"call-bind-apply-helpers",
|
||||||
|
"es-define-property",
|
||||||
|
"es-errors",
|
||||||
|
"es-object-atoms",
|
||||||
|
"function-bind",
|
||||||
|
"get-proto",
|
||||||
|
"gopd",
|
||||||
|
"has-symbols",
|
||||||
|
"hasown",
|
||||||
|
"math-intrinsics"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"get-proto@1.0.1": {
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"dependencies": [
|
||||||
|
"dunder-proto",
|
||||||
|
"es-object-atoms"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gopd@1.2.0": {
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
|
||||||
|
},
|
||||||
|
"has-symbols@1.1.0": {
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
|
||||||
|
},
|
||||||
|
"has-tostringtag@1.0.2": {
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"dependencies": [
|
||||||
|
"has-symbols"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hasown@2.0.2": {
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"function-bind"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"html-encoding-sniffer@4.0.0": {
|
||||||
|
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"whatwg-encoding"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"http-proxy-agent@7.0.2": {
|
||||||
|
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||||
|
"dependencies": [
|
||||||
|
"agent-base",
|
||||||
|
"debug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"https-proxy-agent@7.0.6": {
|
||||||
|
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||||
|
"dependencies": [
|
||||||
|
"agent-base",
|
||||||
|
"debug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"iconv-lite@0.6.3": {
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dependencies": [
|
||||||
|
"safer-buffer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"is-potential-custom-element-name@1.0.1": {
|
||||||
|
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
|
||||||
|
},
|
||||||
|
"jsdom@25.0.1": {
|
||||||
|
"integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
|
||||||
|
"dependencies": [
|
||||||
|
"cssstyle",
|
||||||
|
"data-urls",
|
||||||
|
"decimal.js",
|
||||||
|
"form-data",
|
||||||
|
"html-encoding-sniffer",
|
||||||
|
"http-proxy-agent",
|
||||||
|
"https-proxy-agent",
|
||||||
|
"is-potential-custom-element-name",
|
||||||
|
"nwsapi",
|
||||||
|
"parse5",
|
||||||
|
"rrweb-cssom@0.7.1",
|
||||||
|
"saxes",
|
||||||
|
"symbol-tree",
|
||||||
|
"tough-cookie",
|
||||||
|
"w3c-xmlserializer",
|
||||||
|
"webidl-conversions",
|
||||||
|
"whatwg-encoding",
|
||||||
|
"whatwg-mimetype",
|
||||||
|
"whatwg-url",
|
||||||
|
"ws",
|
||||||
|
"xml-name-validator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lru-cache@10.4.3": {
|
||||||
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
|
||||||
|
},
|
||||||
|
"math-intrinsics@1.1.0": {
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
|
||||||
|
},
|
||||||
|
"meriyah@6.1.4": {
|
||||||
|
"integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ=="
|
||||||
|
},
|
||||||
|
"mime-db@1.52.0": {
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||||
|
},
|
||||||
|
"mime-types@2.1.35": {
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": [
|
||||||
|
"mime-db"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ms@2.1.3": {
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
|
"nwsapi@2.2.23": {
|
||||||
|
"integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ=="
|
||||||
|
},
|
||||||
|
"parse5@7.3.0": {
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"dependencies": [
|
||||||
|
"entities"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"punycode@2.3.1": {
|
||||||
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
|
||||||
|
},
|
||||||
|
"rrweb-cssom@0.7.1": {
|
||||||
|
"integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="
|
||||||
|
},
|
||||||
|
"rrweb-cssom@0.8.0": {
|
||||||
|
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="
|
||||||
|
},
|
||||||
|
"safer-buffer@2.1.2": {
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"saxes@6.0.0": {
|
||||||
|
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||||
|
"dependencies": [
|
||||||
|
"xmlchars"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symbol-tree@3.2.4": {
|
||||||
|
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
|
||||||
|
},
|
||||||
|
"tldts-core@6.1.86": {
|
||||||
|
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="
|
||||||
|
},
|
||||||
|
"tldts@6.1.86": {
|
||||||
|
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"tldts-core"
|
||||||
|
],
|
||||||
|
"bin": true
|
||||||
|
},
|
||||||
|
"tough-cookie@5.1.2": {
|
||||||
|
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
|
||||||
|
"dependencies": [
|
||||||
|
"tldts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tr46@5.1.1": {
|
||||||
|
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||||
|
"dependencies": [
|
||||||
|
"punycode"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"undici@6.23.0": {
|
||||||
|
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="
|
||||||
|
},
|
||||||
|
"w3c-xmlserializer@5.0.0": {
|
||||||
|
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||||
|
"dependencies": [
|
||||||
|
"xml-name-validator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"webidl-conversions@7.0.0": {
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
||||||
|
},
|
||||||
|
"whatwg-encoding@3.1.1": {
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"iconv-lite"
|
||||||
|
],
|
||||||
|
"deprecated": true
|
||||||
|
},
|
||||||
|
"whatwg-mimetype@4.0.0": {
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="
|
||||||
|
},
|
||||||
|
"whatwg-url@14.2.0": {
|
||||||
|
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||||
|
"dependencies": [
|
||||||
|
"tr46",
|
||||||
|
"webidl-conversions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ws@8.18.3": {
|
||||||
|
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="
|
||||||
|
},
|
||||||
|
"xml-name-validator@5.0.0": {
|
||||||
|
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="
|
||||||
|
},
|
||||||
|
"xmlchars@2.2.0": {
|
||||||
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||||
|
},
|
||||||
|
"youtubei.js@16.0.1": {
|
||||||
|
"integrity": "sha512-3802bCAGkBc2/G5WUTc0l/bO5mPYJbQAHL04d9hE9PnrDHoBUT8MN721Yqt4RCNncAXdHcfee9VdJy3Fhq1r5g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@bufbuild/protobuf",
|
||||||
|
"meriyah"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"https://deno.land/std@0.208.0/async/delay.ts": "a6142eb44cdd856b645086af2b811b1fcce08ec06bb7d50969e6a872ee9b8659",
|
"https://deno.land/std@0.208.0/async/delay.ts": "a6142eb44cdd856b645086af2b811b1fcce08ec06bb7d50969e6a872ee9b8659",
|
||||||
"https://deno.land/std@0.208.0/http/server.ts": "f3cde6672e631d3e00785743cfa96bfed275618c0352c5ae84abbe5a2e0e4afc"
|
"https://deno.land/std@0.208.0/http/server.ts": "f3cde6672e631d3e00785743cfa96bfed275618c0352c5ae84abbe5a2e0e4afc"
|
||||||
|
|||||||
33
lib.ts
33
lib.ts
@@ -966,15 +966,33 @@ export async function getDynamicInstances() {
|
|||||||
return instancesCache;
|
return instancesCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hardcoded working Piped instances (they proxy streams through their CDN)
|
||||||
|
const workingPipedInstances = [
|
||||||
|
"https://api.piped.private.coffee",
|
||||||
|
"https://pipedapi.darkness.services",
|
||||||
|
"https://pipedapi.r4fo.com",
|
||||||
|
"https://api.piped.yt",
|
||||||
|
"https://pipedapi.kavin.rocks",
|
||||||
|
"https://pipedapi.adminforge.de",
|
||||||
|
"https://pipedapi.in.projectsegfau.lt",
|
||||||
|
"https://api.piped.projectsegfau.lt",
|
||||||
|
"https://pipedapi.leptons.xyz"
|
||||||
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://raw.githubusercontent.com/n-ce/Uma/main/dynamic_instances.json");
|
const response = await fetch("https://raw.githubusercontent.com/n-ce/Uma/main/dynamic_instances.json");
|
||||||
instancesCache = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Always use our working Piped instances
|
||||||
|
data.piped = workingPipedInstances;
|
||||||
|
|
||||||
|
instancesCache = data;
|
||||||
instancesCacheTime = now;
|
instancesCacheTime = now;
|
||||||
return instancesCache;
|
return instancesCache;
|
||||||
} catch {
|
} catch {
|
||||||
return {
|
return {
|
||||||
piped: ["https://api.piped.private.coffee"],
|
piped: workingPipedInstances,
|
||||||
invidious: ["https://invidious.nikkosphere.com", "https://yt.omada.cafe"],
|
invidious: ["https://yt.omada.cafe", "https://y.com.sb", "https://inv.nadeko.net"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,9 +1003,16 @@ export async function fetchFromPiped(videoId: string) {
|
|||||||
|
|
||||||
for (const instance of pipedInstances) {
|
for (const instance of pipedInstances) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${instance}/streams/${videoId}`);
|
const response = await fetch(`${instance}/streams/${videoId}`, {
|
||||||
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }
|
||||||
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Check for error response (bot detection)
|
||||||
|
if (data?.error) {
|
||||||
|
continue; // Try next instance
|
||||||
|
}
|
||||||
|
|
||||||
if (data?.audioStreams?.length) {
|
if (data?.audioStreams?.length) {
|
||||||
// Get the proxy host from the instance (e.g., pipedapi.kavin.rocks -> pipedproxy.kavin.rocks)
|
// Get the proxy host from the instance (e.g., pipedapi.kavin.rocks -> pipedproxy.kavin.rocks)
|
||||||
const instanceUrl = new URL(instance);
|
const instanceUrl = new URL(instance);
|
||||||
|
|||||||
151
mod.ts
151
mod.ts
@@ -10,75 +10,6 @@ import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
|
|||||||
import { YTMusic, YouTubeSearch, LastFM, fetchFromPiped, fetchFromInvidious, getLyrics, getTrendingMusic, getRadio, getTopArtists, getTopTracks, getArtistInfo, getTrackInfo, getSongComplete, getAlbumComplete, getArtistComplete, getFullChain } from "./lib.ts";
|
import { YTMusic, YouTubeSearch, LastFM, fetchFromPiped, fetchFromInvidious, getLyrics, getTrendingMusic, getRadio, getTopArtists, getTopTracks, getArtistInfo, getTrackInfo, getSongComplete, getAlbumComplete, getArtistComplete, getFullChain } from "./lib.ts";
|
||||||
import { html as uiHtml } from "./ui.ts";
|
import { html as uiHtml } from "./ui.ts";
|
||||||
|
|
||||||
// Direct YouTube innertube API for downloads
|
|
||||||
async function getYouTubeStreamUrl(videoId: string): Promise<{ url: string; mimeType: string } | null> {
|
|
||||||
const clients = [
|
|
||||||
{
|
|
||||||
name: "ANDROID",
|
|
||||||
context: {
|
|
||||||
client: {
|
|
||||||
clientName: "ANDROID",
|
|
||||||
clientVersion: "19.09.37",
|
|
||||||
androidSdkVersion: 30,
|
|
||||||
userAgent: "com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip",
|
|
||||||
hl: "en",
|
|
||||||
gl: "US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "IOS",
|
|
||||||
context: {
|
|
||||||
client: {
|
|
||||||
clientName: "IOS",
|
|
||||||
clientVersion: "19.09.3",
|
|
||||||
deviceModel: "iPhone14,3",
|
|
||||||
userAgent: "com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
|
|
||||||
hl: "en",
|
|
||||||
gl: "US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const client of clients) {
|
|
||||||
try {
|
|
||||||
const response = await fetch("https://www.youtube.com/youtubei/v1/player?prettyPrint=false", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"User-Agent": client.context.client.userAgent,
|
|
||||||
"X-YouTube-Client-Name": client.name === "ANDROID" ? "3" : "5",
|
|
||||||
"X-YouTube-Client-Version": client.context.client.clientVersion,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
videoId,
|
|
||||||
context: client.context,
|
|
||||||
contentCheckOk: true,
|
|
||||||
racyCheckOk: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.streamingData?.adaptiveFormats) {
|
|
||||||
// Find best audio format (m4a)
|
|
||||||
const audioFormats = data.streamingData.adaptiveFormats.filter(
|
|
||||||
(f: any) => f.mimeType?.includes("audio/mp4")
|
|
||||||
);
|
|
||||||
audioFormats.sort((a: any, b: any) => (b.bitrate || 0) - (a.bitrate || 0));
|
|
||||||
|
|
||||||
if (audioFormats[0]?.url) {
|
|
||||||
return { url: audioFormats[0].url, mimeType: audioFormats[0].mimeType };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PORT = parseInt(Deno.env.get("PORT") || "8000");
|
const PORT = parseInt(Deno.env.get("PORT") || "8000");
|
||||||
|
|
||||||
const ytmusic = new YTMusic();
|
const ytmusic = new YTMusic();
|
||||||
@@ -325,88 +256,6 @@ async function handler(req: Request): Promise<Response> {
|
|||||||
return proxyAudio(audioUrl, req);
|
return proxyAudio(audioUrl, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download audio - returns URL for client-side download
|
|
||||||
if (pathname === "/api/download") {
|
|
||||||
const id = searchParams.get("id");
|
|
||||||
const title = searchParams.get("title") || "audio";
|
|
||||||
const artist = searchParams.get("artist") || "";
|
|
||||||
if (!id) return error("Missing id");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try direct YouTube innertube API first
|
|
||||||
const ytStream = await getYouTubeStreamUrl(id);
|
|
||||||
|
|
||||||
if (ytStream?.url) {
|
|
||||||
const ext = ".m4a";
|
|
||||||
const filename = `${artist ? artist + " - " : ""}${title}`.replace(/[<>:"/\\|?*]/g, "").trim() + ext;
|
|
||||||
|
|
||||||
const response = await fetch(ytStream.url, {
|
|
||||||
headers: {
|
|
||||||
"User-Agent": "com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip",
|
|
||||||
"Accept": "*/*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
return new Response(response.body, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "audio/mp4",
|
|
||||||
"Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`,
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Expose-Headers": "Content-Disposition",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to Piped
|
|
||||||
const piped = await fetchFromPiped(id);
|
|
||||||
if (piped.success && piped.streamingUrls) {
|
|
||||||
const pipedAudio = piped.streamingUrls.find((s: any) => s.mimeType?.includes("audio/mp4"))
|
|
||||||
|| piped.streamingUrls.find((s: any) => s.mimeType?.includes("audio"));
|
|
||||||
if (pipedAudio?.url) {
|
|
||||||
const response = await fetch(pipedAudio.url);
|
|
||||||
if (response.ok) {
|
|
||||||
const ext = pipedAudio.mimeType?.includes("webm") ? ".webm" : ".m4a";
|
|
||||||
const filename = `${artist ? artist + " - " : ""}${title}`.replace(/[<>:"/\\|?*]/g, "").trim() + ext;
|
|
||||||
return new Response(response.body, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": pipedAudio.mimeType?.split(";")[0] || "audio/mp4",
|
|
||||||
"Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`,
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to Invidious
|
|
||||||
const invidious = await fetchFromInvidious(id);
|
|
||||||
if (invidious.success && invidious.streamingUrls) {
|
|
||||||
const invAudio = invidious.streamingUrls.find((s: any) => s.type?.includes("audio/mp4"))
|
|
||||||
|| invidious.streamingUrls.find((s: any) => s.type?.includes("audio"));
|
|
||||||
if (invAudio?.url) {
|
|
||||||
const response = await fetch(invAudio.url);
|
|
||||||
if (response.ok) {
|
|
||||||
const ext = invAudio.type?.includes("webm") ? ".webm" : ".m4a";
|
|
||||||
const filename = `${artist ? artist + " - " : ""}${title}`.replace(/[<>:"/\\|?*]/g, "").trim() + ext;
|
|
||||||
return new Response(response.body, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": invAudio.type?.split(";")[0] || "audio/mp4",
|
|
||||||
"Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`,
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json({ success: false, error: "No audio stream found" }, 404);
|
|
||||||
} catch (err) {
|
|
||||||
return json({ success: false, error: "Download failed: " + String(err) }, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ LYRICS & INFO ============
|
// ============ LYRICS & INFO ============
|
||||||
|
|
||||||
if (pathname === "/api/lyrics") {
|
if (pathname === "/api/lyrics") {
|
||||||
|
|||||||
16
ui.ts
16
ui.ts
@@ -54,8 +54,7 @@ export const html = `<!DOCTYPE html>
|
|||||||
.name{font-size:.9rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
.name{font-size:.9rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
.artist{font-size:.8rem;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
.artist{font-size:.8rem;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
.dur{font-size:.75rem;color:var(--dim);font-family:monospace}
|
.dur{font-size:.75rem;color:var(--dim);font-family:monospace}
|
||||||
.dl-btn{width:32px;height:32px;border-radius:50%;background:var(--surface2);border:1px solid var(--border);color:var(--accent);font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0;margin-right:8px}
|
|
||||||
.dl-btn:hover{background:var(--accent);color:#000;border-color:var(--accent)}
|
|
||||||
.empty{padding:48px;text-align:center;color:var(--dim)}
|
.empty{padding:48px;text-align:center;color:var(--dim)}
|
||||||
.loading{display:none;padding:48px;text-align:center;color:var(--accent)}
|
.loading{display:none;padding:48px;text-align:center;color:var(--accent)}
|
||||||
.tester-row{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap}
|
.tester-row{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap}
|
||||||
@@ -133,7 +132,6 @@ export const html = `<!DOCTYPE html>
|
|||||||
<div class="section-title">Streaming & Lyrics</div>
|
<div class="section-title">Streaming & Lyrics</div>
|
||||||
<div class="api-list">
|
<div class="api-list">
|
||||||
<div class="api-item"><span class="method">GET</span><span class="path">/api/stream?id=</span><span class="desc">Audio stream URLs</span></div>
|
<div class="api-item"><span class="method">GET</span><span class="path">/api/stream?id=</span><span class="desc">Audio stream URLs</span></div>
|
||||||
<div class="api-item"><span class="method">GET</span><span class="path">/api/download?id=&title=&artist=</span><span class="desc">Download audio file</span></div>
|
|
||||||
<div class="api-item"><span class="method">GET</span><span class="path">/api/proxy?url=</span><span class="desc">Audio proxy (CORS)</span></div>
|
<div class="api-item"><span class="method">GET</span><span class="path">/api/proxy?url=</span><span class="desc">Audio proxy (CORS)</span></div>
|
||||||
<div class="api-item"><span class="method">GET</span><span class="path">/api/lyrics?title=&artist=</span><span class="desc">Synced lyrics (LRC)</span></div>
|
<div class="api-item"><span class="method">GET</span><span class="path">/api/lyrics?title=&artist=</span><span class="desc">Synced lyrics (LRC)</span></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,6 +209,7 @@ export const html = `<!DOCTYPE html>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="ytplayer"></div>
|
<div id="ytplayer"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
// Completely disable ALL console output
|
// Completely disable ALL console output
|
||||||
@@ -320,21 +319,12 @@ function render(f,append){
|
|||||||
click="viewArtist('"+bid+"','"+encodeURIComponent(thumb)+"','"+encodeURIComponent(s.title||'')+"')";
|
click="viewArtist('"+bid+"','"+encodeURIComponent(thumb)+"','"+encodeURIComponent(s.title||'')+"')";
|
||||||
}
|
}
|
||||||
var badge=type!=='song'&&type!=='video'?'<span style="font-size:.65rem;color:var(--accent);margin-left:8px;text-transform:uppercase">'+type+'</span>':'';
|
var badge=type!=='song'&&type!=='video'?'<span style="font-size:.65rem;color:var(--accent);margin-left:8px;text-transform:uppercase">'+type+'</span>':'';
|
||||||
var dlBtn=playable?'<button class="dl-btn" onclick="event.stopPropagation();download('+i+')" title="Download">↓</button>':'';
|
return '<div class="result'+(i===idx?' active':'')+'" onclick="'+click+'" style="cursor:pointer"><img class="thumb" src="'+thumb+'"><div class="info"><div class="name">'+esc(s.title||s.name||'Unknown')+badge+'</div><div class="artist">'+esc(s.artists?.map(a=>a.name).join(', ')||s.subtitle||'')+'</div></div><div class="dur">'+(s.duration||'')+'</div></div>';
|
||||||
return '<div class="result'+(i===idx?' active':'')+'" onclick="'+click+'" style="cursor:pointer"><img class="thumb" src="'+thumb+'"><div class="info"><div class="name">'+esc(s.title||s.name||'Unknown')+badge+'</div><div class="artist">'+esc(s.artists?.map(a=>a.name).join(', ')||s.subtitle||'')+'</div></div>'+dlBtn+'<div class="dur">'+(s.duration||'')+'</div></div>';
|
|
||||||
}).join('');
|
}).join('');
|
||||||
if(append)el.innerHTML+=html;
|
if(append)el.innerHTML+=html;
|
||||||
else el.innerHTML=html;
|
else el.innerHTML=html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function download(i){
|
|
||||||
var s=songs[i];if(!s||!s.videoId)return;
|
|
||||||
var title=encodeURIComponent(s.title||'audio');
|
|
||||||
var artist=encodeURIComponent(s.artists?.map(a=>a.name).join(', ')||'');
|
|
||||||
// Open download in new tab - server will proxy the audio
|
|
||||||
window.open('/api/download?id='+s.videoId+'&title='+title+'&artist='+artist,'_blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
function play(i){
|
function play(i){
|
||||||
if(!songs[i]||!ready)return;idx=i;var s=songs[i];
|
if(!songs[i]||!ready)return;idx=i;var s=songs[i];
|
||||||
document.getElementById('pTitle').textContent=s.title||'Unknown';
|
document.getElementById('pTitle').textContent=s.title||'Unknown';
|
||||||
|
|||||||
Reference in New Issue
Block a user