mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat: refactor api
This commit is contained in:
parent
9859049a22
commit
f6a66f4eba
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ node_modules/
|
||||
dist/
|
||||
.vercel
|
||||
.output
|
||||
.vinxi
|
||||
.vinxi
|
||||
.cache
|
@ -22,11 +22,17 @@
|
||||
"@tanstack/react-router": "^1.58.9",
|
||||
"@tanstack/router-devtools": "^1.58.9",
|
||||
"@unocss/reset": "^0.62.4",
|
||||
"cheerio": "^1.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^4.5.0",
|
||||
"favicons-scraper": "^1.3.2",
|
||||
"flat-cache": "^6.1.0",
|
||||
"h3": "^1.12.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"jotai": "^2.10.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"ofetch": "^1.4.0",
|
||||
"overlayscrollbars": "^2.10.0",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"react": "^18.3.1",
|
||||
|
256
pnpm-lock.yaml
generated
256
pnpm-lock.yaml
generated
@ -32,21 +32,39 @@ importers:
|
||||
'@unocss/reset':
|
||||
specifier: ^0.62.4
|
||||
version: 0.62.4
|
||||
cheerio:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
fast-xml-parser:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
favicons-scraper:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
flat-cache:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
h3:
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.0
|
||||
iconv-lite:
|
||||
specifier: ^0.6.3
|
||||
version: 0.6.3
|
||||
jotai:
|
||||
specifier: ^2.10.0
|
||||
version: 2.10.0(@types/react@18.3.9)(react@18.3.1)
|
||||
node-fetch:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
ofetch:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
overlayscrollbars:
|
||||
specifier: ^2.10.0
|
||||
version: 2.10.0
|
||||
@ -104,7 +122,7 @@ importers:
|
||||
version: 0.4.12(eslint@9.11.1(jiti@1.21.6))
|
||||
nitropack:
|
||||
specifier: ^2.9.7
|
||||
version: 2.9.7(magicast@0.3.5)
|
||||
version: 2.9.7(encoding@0.1.13)(magicast@0.3.5)
|
||||
tsx:
|
||||
specifier: ^4.19.1
|
||||
version: 4.19.1
|
||||
@ -122,7 +140,7 @@ importers:
|
||||
version: 5.4.8(@types/node@22.6.1)(terser@5.33.0)
|
||||
vite-plugin-with-nitro:
|
||||
specifier: 0.0.0-beta.4
|
||||
version: 0.0.0-beta.4(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
|
||||
version: 0.0.0-beta.4(encoding@0.1.13)(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
|
||||
@ -959,6 +977,9 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@keyv/serialize@1.0.1':
|
||||
resolution: {integrity: sha512-kKXeynfORDGPUEEl2PvTExM2zs+IldC6ZD8jPcfvI351MDNtfMlw9V9s4XZXuJNDK2qR5gbEKxRyoYx3quHUVQ==}
|
||||
|
||||
'@mapbox/node-pre-gyp@1.0.11':
|
||||
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||
hasBin: true
|
||||
@ -1887,6 +1908,9 @@ packages:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
cacheable@1.8.0:
|
||||
resolution: {integrity: sha512-ewQhhLQ7aUO95MALxgQ/04M8Eh97uCfinTK1pfktsCq+mULf4gNFAg1jSkLi5SDIw/2Md6K6Np3yWjWtYEzvqw==}
|
||||
|
||||
callsites@3.1.0:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
@ -1923,6 +1947,13 @@ packages:
|
||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
cheerio-select@2.1.0:
|
||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||
|
||||
cheerio@1.0.0:
|
||||
resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==}
|
||||
engines: {node: '>=18.17'}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@ -2048,6 +2079,9 @@ packages:
|
||||
css-in-js-utils@3.1.0:
|
||||
resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==}
|
||||
|
||||
css-select@5.1.0:
|
||||
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
|
||||
|
||||
css-tree@1.1.3:
|
||||
resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@ -2056,6 +2090,10 @@ packages:
|
||||
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||
|
||||
css-what@6.1.0:
|
||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
cssesc@3.0.0:
|
||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||
engines: {node: '>=4'}
|
||||
@ -2068,6 +2106,9 @@ packages:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
db0@0.1.4:
|
||||
resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==}
|
||||
peerDependencies:
|
||||
@ -2156,6 +2197,19 @@ packages:
|
||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domutils@3.1.0:
|
||||
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
||||
|
||||
dot-prop@8.0.2:
|
||||
resolution: {integrity: sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==}
|
||||
engines: {node: '>=16'}
|
||||
@ -2190,6 +2244,12 @@ packages:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
encoding-sniffer@0.2.0:
|
||||
resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==}
|
||||
|
||||
encoding@0.1.13:
|
||||
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
||||
|
||||
enhanced-resolve@5.17.1:
|
||||
resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@ -2549,6 +2609,10 @@ packages:
|
||||
fast-shallow-equal@1.0.0:
|
||||
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
|
||||
|
||||
fast-xml-parser@4.5.0:
|
||||
resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==}
|
||||
hasBin: true
|
||||
|
||||
fastest-stable-stringify@2.0.2:
|
||||
resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==}
|
||||
|
||||
@ -2597,6 +2661,9 @@ packages:
|
||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
flat-cache@6.1.0:
|
||||
resolution: {integrity: sha512-txr9o9zCbXvMOWZ1wZAID4U+CnYxdkSFqxxvd8Bs/a0ZidSs8V8diyv0CviKkPYEWHtbFJloF/QQO2WnkarnlA==}
|
||||
|
||||
flatted@3.3.1:
|
||||
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
|
||||
|
||||
@ -2748,9 +2815,16 @@ packages:
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
hookified@1.2.0:
|
||||
resolution: {integrity: sha512-32q0JrtZRAdCGeEa8IcBz4iABsrP4UiDrbFj/WADL1VyNHRvHdGs8MrYvaIZWMzbff1xdS3Tw8kBt6YvcSL7jQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
|
||||
htmlparser2@9.1.0:
|
||||
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
|
||||
|
||||
http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -2773,6 +2847,10 @@ packages:
|
||||
hyphenate-style-name@1.1.0:
|
||||
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@ -2997,6 +3075,9 @@ packages:
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
keyv@5.0.3:
|
||||
resolution: {integrity: sha512-WmefGWaWkWiWDkIasfHxpWmM1lych/LPtRmNj8jnIQVGLsAgFw73Vg9utZ7ss97/JwRlERABb/fSejTPY4hlZQ==}
|
||||
|
||||
klona@2.0.6:
|
||||
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -3351,6 +3432,15 @@ packages:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
parse5-htmlparser2-tree-adapter@7.0.0:
|
||||
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
||||
|
||||
parse5-parser-stream@7.1.2:
|
||||
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
|
||||
|
||||
parse5@7.1.2:
|
||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
||||
|
||||
parseurl@1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -3602,6 +3692,9 @@ packages:
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
|
||||
@ -3800,6 +3893,9 @@ packages:
|
||||
strip-literal@2.1.0:
|
||||
resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==}
|
||||
|
||||
strnum@1.0.5:
|
||||
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
|
||||
|
||||
stylis@4.3.4:
|
||||
resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==}
|
||||
|
||||
@ -3999,6 +4095,10 @@ packages:
|
||||
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
|
||||
engines: {node: '>=14.0'}
|
||||
|
||||
undici@6.19.8:
|
||||
resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
|
||||
engines: {node: '>=18.17'}
|
||||
|
||||
unenv@1.10.0:
|
||||
resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==}
|
||||
|
||||
@ -4205,6 +4305,14 @@ packages:
|
||||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
whatwg-mimetype@4.0.0:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
@ -4963,12 +5071,16 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@mapbox/node-pre-gyp@1.0.11':
|
||||
'@keyv/serialize@1.0.1':
|
||||
dependencies:
|
||||
buffer: 6.0.3
|
||||
|
||||
'@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
|
||||
dependencies:
|
||||
detect-libc: 2.0.3
|
||||
https-proxy-agent: 5.0.1
|
||||
make-dir: 3.1.0
|
||||
node-fetch: 2.7.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
nopt: 5.0.0
|
||||
npmlog: 5.0.1
|
||||
rimraf: 3.0.2
|
||||
@ -5686,9 +5798,9 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
'@vercel/nft@0.26.5':
|
||||
'@vercel/nft@0.26.5(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.11
|
||||
'@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
acorn: 8.12.1
|
||||
acorn-import-attributes: 1.9.5(acorn@8.12.1)
|
||||
@ -5967,6 +6079,11 @@ snapshots:
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
cacheable@1.8.0:
|
||||
dependencies:
|
||||
hookified: 1.2.0
|
||||
keyv: 5.0.3
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001663: {}
|
||||
@ -6000,6 +6117,29 @@ snapshots:
|
||||
|
||||
check-error@2.1.1: {}
|
||||
|
||||
cheerio-select@2.1.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-select: 5.1.0
|
||||
css-what: 6.1.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
|
||||
cheerio@1.0.0:
|
||||
dependencies:
|
||||
cheerio-select: 2.1.0
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
encoding-sniffer: 0.2.0
|
||||
htmlparser2: 9.1.0
|
||||
parse5: 7.1.2
|
||||
parse5-htmlparser2-tree-adapter: 7.0.0
|
||||
parse5-parser-stream: 7.1.2
|
||||
undici: 6.19.8
|
||||
whatwg-mimetype: 4.0.0
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
@ -6113,6 +6253,14 @@ snapshots:
|
||||
dependencies:
|
||||
hyphenate-style-name: 1.1.0
|
||||
|
||||
css-select@5.1.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.1.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
nth-check: 2.1.1
|
||||
|
||||
css-tree@1.1.3:
|
||||
dependencies:
|
||||
mdn-data: 2.0.14
|
||||
@ -6123,12 +6271,16 @@ snapshots:
|
||||
mdn-data: 2.0.30
|
||||
source-map-js: 1.2.1
|
||||
|
||||
css-what@6.1.0: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
db0@0.1.4: {}
|
||||
|
||||
debug@2.6.9:
|
||||
@ -6171,6 +6323,24 @@ snapshots:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
|
||||
domhandler@5.0.3:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domutils@3.1.0:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
dot-prop@8.0.2:
|
||||
dependencies:
|
||||
type-fest: 3.13.1
|
||||
@ -6193,6 +6363,16 @@ snapshots:
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
encoding-sniffer@0.2.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
whatwg-encoding: 3.1.1
|
||||
|
||||
encoding@0.1.13:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
optional: true
|
||||
|
||||
enhanced-resolve@5.17.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@ -6780,6 +6960,10 @@ snapshots:
|
||||
|
||||
fast-shallow-equal@1.0.0: {}
|
||||
|
||||
fast-xml-parser@4.5.0:
|
||||
dependencies:
|
||||
strnum: 1.0.5
|
||||
|
||||
fastest-stable-stringify@2.0.2: {}
|
||||
|
||||
fastq@1.17.1:
|
||||
@ -6824,6 +7008,12 @@ snapshots:
|
||||
flatted: 3.3.1
|
||||
keyv: 4.5.4
|
||||
|
||||
flat-cache@6.1.0:
|
||||
dependencies:
|
||||
cacheable: 1.8.0
|
||||
flatted: 3.3.1
|
||||
hookified: 1.2.0
|
||||
|
||||
flatted@3.3.1: {}
|
||||
|
||||
foreground-child@3.3.0:
|
||||
@ -6993,8 +7183,17 @@ snapshots:
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
hookified@1.2.0: {}
|
||||
|
||||
hosted-git-info@2.8.9: {}
|
||||
|
||||
htmlparser2@9.1.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
entities: 4.5.0
|
||||
|
||||
http-errors@2.0.0:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
@ -7018,6 +7217,10 @@ snapshots:
|
||||
|
||||
hyphenate-style-name@1.1.0: {}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
@ -7216,6 +7419,10 @@ snapshots:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
|
||||
keyv@5.0.3:
|
||||
dependencies:
|
||||
'@keyv/serialize': 1.0.1
|
||||
|
||||
klona@2.0.6: {}
|
||||
|
||||
knitwork@1.1.0: {}
|
||||
@ -7415,7 +7622,7 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
nitropack@2.9.7(magicast@0.3.5):
|
||||
nitropack@2.9.7(encoding@0.1.13)(magicast@0.3.5):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.3.4
|
||||
'@netlify/functions': 2.8.1
|
||||
@ -7428,7 +7635,7 @@ snapshots:
|
||||
'@rollup/plugin-terser': 0.4.4(rollup@4.22.4)
|
||||
'@rollup/pluginutils': 5.1.2(rollup@4.22.4)
|
||||
'@types/http-proxy': 1.17.15
|
||||
'@vercel/nft': 0.26.5
|
||||
'@vercel/nft': 0.26.5(encoding@0.1.13)
|
||||
archiver: 7.0.1
|
||||
c12: 1.11.2(magicast@0.3.5)
|
||||
chalk: 5.3.0
|
||||
@ -7511,9 +7718,11 @@ snapshots:
|
||||
|
||||
node-fetch-native@1.6.4: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
node-fetch@2.7.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
optionalDependencies:
|
||||
encoding: 0.1.13
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
@ -7666,6 +7875,19 @@ snapshots:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
parse5-htmlparser2-tree-adapter@7.0.0:
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
parse5: 7.1.2
|
||||
|
||||
parse5-parser-stream@7.1.2:
|
||||
dependencies:
|
||||
parse5: 7.1.2
|
||||
|
||||
parse5@7.1.2:
|
||||
dependencies:
|
||||
entities: 4.5.0
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
@ -7918,6 +8140,8 @@ snapshots:
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@ -8118,6 +8342,8 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 9.0.0
|
||||
|
||||
strnum@1.0.5: {}
|
||||
|
||||
stylis@4.3.4: {}
|
||||
|
||||
supports-color@5.5.0:
|
||||
@ -8290,6 +8516,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@fastify/busboy': 2.1.1
|
||||
|
||||
undici@6.19.8: {}
|
||||
|
||||
unenv@1.10.0:
|
||||
dependencies:
|
||||
consola: 3.2.3
|
||||
@ -8432,13 +8660,13 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vite-plugin-with-nitro@0.0.0-beta.4(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0)):
|
||||
vite-plugin-with-nitro@0.0.0-beta.4(encoding@0.1.13)(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0)):
|
||||
dependencies:
|
||||
esbuild: 0.24.0
|
||||
fast-glob: 3.3.2
|
||||
front-matter: 4.0.2
|
||||
h3: 1.12.0
|
||||
nitropack: 2.9.7(magicast@0.3.5)
|
||||
nitropack: 2.9.7(encoding@0.1.13)(magicast@0.3.5)
|
||||
vite: 5.4.8(@types/node@22.6.1)(terser@5.33.0)
|
||||
xmlbuilder2: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
@ -8538,6 +8766,12 @@ snapshots:
|
||||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
whatwg-mimetype@4.0.0: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
|
9
server/cache.ts
Normal file
9
server/cache.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { FlatCache } from "flat-cache"
|
||||
|
||||
// init
|
||||
export const cache = new FlatCache({
|
||||
ttl: 60 * 60 * 1000, // 1 hour
|
||||
lruSize: 10000, // 10,000 items
|
||||
expirationInterval: 5 * 1000 * 60, // 5 minutes
|
||||
persistInterval: 5 * 1000 * 60, // 5 minutes
|
||||
})
|
@ -1,10 +1,24 @@
|
||||
import { defineEventHandler, getQuery, getRouterParam, sendProxy } from "h3"
|
||||
import { defineEventHandler, getRouterParam } from "h3"
|
||||
import { fallback, sources } from "#/sources"
|
||||
// import { cache } from "#/cache"
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = getRouterParam(event, "id")
|
||||
const { latest } = getQuery(event)
|
||||
// https://api-hot.efefee.cn/weibo?cache=false
|
||||
// https://smzdk.top/api/${id}/new
|
||||
if (latest !== undefined) return await sendProxy(event, `https://api-hot.efefee.cn/${id}?cache=false`)
|
||||
return await sendProxy(event, `https://api-hot.efefee.cn/${id}?cache=true`)
|
||||
const id = getRouterParam(event, "id") as keyof typeof sources
|
||||
// const { latest } = getQuery(event)
|
||||
// console.log(id, latest)
|
||||
if (!id) throw new Error("Invalid source id")
|
||||
// if (!latest) {
|
||||
// const _ = cache.get(id)
|
||||
// if (_) return _
|
||||
// }
|
||||
|
||||
if (!sources[id]) {
|
||||
const _ = await fallback(id)
|
||||
// cache.set(id, _)
|
||||
return _
|
||||
} else {
|
||||
const _ = await sources[id]()
|
||||
// cache.set(id, _)
|
||||
return _
|
||||
}
|
||||
})
|
||||
|
41
server/sources/fallback.ts
Normal file
41
server/sources/fallback.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { OResponse } from "@shared/types"
|
||||
import { $fetch } from "ofetch"
|
||||
|
||||
export interface Res {
|
||||
code: number
|
||||
message: string
|
||||
name: string
|
||||
title: string
|
||||
subtitle: string
|
||||
total: number
|
||||
updateTime: string
|
||||
data: {
|
||||
title: string
|
||||
desc: string
|
||||
time?: string
|
||||
url: string
|
||||
mobileUrl: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export async function fallback(id: string): Promise<OResponse> {
|
||||
const res: Res = await $fetch(`https://smzdk.top/api/${id}/new`)
|
||||
if (res.code !== 200 || !res.data) throw new Error(res.message)
|
||||
return {
|
||||
status: "success",
|
||||
data: {
|
||||
name: res.title,
|
||||
type: res.subtitle,
|
||||
updateTime: res.updateTime,
|
||||
items: res.data.map(item => ({
|
||||
extra: {
|
||||
date: item.time,
|
||||
},
|
||||
id: item.url,
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
mobileUrl: item.mobileUrl,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { peopledaily } from "./peopledaily"
|
||||
import { weibo } from "./weibo"
|
||||
import { zaobao } from "./zaobao"
|
||||
|
||||
export { fallback } from "./fallback"
|
||||
|
||||
export const sources = {
|
||||
peopledaily,
|
||||
weibo,
|
||||
zaobao: () => zaobao("中国聚焦"),
|
||||
}
|
20
server/sources/peopledaily.ts
Normal file
20
server/sources/peopledaily.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { OResponse, RSS2JSON } from "@shared/types"
|
||||
import { rss2json } from "#/utils/rss2json"
|
||||
|
||||
export async function peopledaily(): Promise<OResponse> {
|
||||
const source = await rss2json("https://feedx.net/rss/people.xml")
|
||||
if (!source?.items.length) throw new Error("Cannot fetch data")
|
||||
return {
|
||||
status: "success",
|
||||
data: {
|
||||
name: "人民日报",
|
||||
type: "报纸",
|
||||
updateTime: Date.now(),
|
||||
items: source.items.slice(0, 30).map((item: RSS2JSON) => ({
|
||||
title: item.title,
|
||||
url: item.link,
|
||||
id: item.link,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import type { OResponse } from "@shared/types"
|
||||
import { $fetch } from "ofetch"
|
||||
|
||||
interface Res {
|
||||
ok: number // 1 is ok
|
||||
data: {
|
||||
realtime:
|
||||
{
|
||||
num: number // 看上去是个 id
|
||||
emoticon: string
|
||||
icon?: string // 热,新 icon url
|
||||
icon_width: number
|
||||
icon_height: number
|
||||
note: string
|
||||
small_icon_desc: string
|
||||
icon_desc?: string // 如果是 荐 ,就是广告
|
||||
topic_flag: number
|
||||
icon_desc_color: string
|
||||
flag: number
|
||||
word_scheme: string
|
||||
small_icon_desc_color: string
|
||||
realpos: number
|
||||
label_name: string
|
||||
word: string // 热搜词
|
||||
rank: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export async function weibo(): Promise<OResponse> {
|
||||
const url = "https://weibo.com/ajax/side/hotSearch"
|
||||
const res: Res = await $fetch(url)
|
||||
if (!res.ok || res.data.realtime.length === 0) throw new Error("Cannot fetch data")
|
||||
return {
|
||||
status: "success",
|
||||
data: {
|
||||
name: "微博热搜",
|
||||
updateTime: Date.now(),
|
||||
type: "热搜",
|
||||
items: res.data.realtime.filter(k => !k.icon_desc || k.icon_desc !== "荐").map((k) => {
|
||||
const keyword = k.word_scheme ? k.word_scheme : `#${k.word}#`
|
||||
return {
|
||||
id: k.num,
|
||||
title: k.word,
|
||||
extra: {
|
||||
icon: k.icon,
|
||||
},
|
||||
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(keyword)}`,
|
||||
mobileUrl: `https://m.weibo.cn/search?containerid=231522type%3D1%26q%3D${encodeURIComponent(keyword)}&_T_WM=16922097837&v_p=42`,
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
54
server/sources/zaobao.ts
Normal file
54
server/sources/zaobao.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Buffer } from "node:buffer"
|
||||
import { $fetch } from "ofetch"
|
||||
import * as cheerio from "cheerio"
|
||||
import iconv from "iconv-lite"
|
||||
import type { NewsItem, OResponse } from "@shared/types"
|
||||
import { tranformToUTC } from "#/utils/date"
|
||||
|
||||
const columns = [
|
||||
"人物记事",
|
||||
"观点评论",
|
||||
"中国聚焦",
|
||||
"香港澳门",
|
||||
"台湾新闻",
|
||||
"国际时政",
|
||||
"国际军事",
|
||||
"国际视野",
|
||||
] as const
|
||||
// type: "中国聚焦" | "人物记事" | "观点评论"
|
||||
export async function zaobao(type: typeof columns[number] = "中国聚焦"): Promise<OResponse> {
|
||||
const response = await $fetch("https://www.kzaobao.com/top.html", {
|
||||
responseType: "arrayBuffer",
|
||||
})
|
||||
const base = "https://www.kzaobao.com"
|
||||
const utf8String = iconv.decode(Buffer.from(response), "gb2312")
|
||||
const $ = cheerio.load(utf8String)
|
||||
const $main = $(`#cd0${columns.indexOf(type) + 1}`)
|
||||
const news: NewsItem[] = []
|
||||
$main.find("tr").each((_, el) => {
|
||||
const a = $(el).find("h3>a")
|
||||
// https://www.kzaobao.com/shiju/20241002/170659.html
|
||||
const url = a.attr("href")
|
||||
const title = a.text()
|
||||
if (url && title) {
|
||||
const date = $(el).find("td:nth-child(3)").text()
|
||||
news.push({
|
||||
url: base + url,
|
||||
title,
|
||||
id: url,
|
||||
extra: {
|
||||
date: date && tranformToUTC(date),
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
return {
|
||||
status: "success",
|
||||
data: {
|
||||
name: `联合早报`,
|
||||
type,
|
||||
updateTime: Date.now(),
|
||||
items: news,
|
||||
},
|
||||
}
|
||||
}
|
14
server/utils/date.ts
Normal file
14
server/utils/date.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import dayjs from "dayjs"
|
||||
import utcPlugin from "dayjs/plugin/utc"
|
||||
import timezonePlugin from "dayjs/plugin/timezone"
|
||||
|
||||
dayjs.extend(utcPlugin)
|
||||
dayjs.extend(timezonePlugin)
|
||||
|
||||
/**
|
||||
* 传入任意时区的时间(不携带时区),转换为 UTC 时间
|
||||
*/
|
||||
export function tranformToUTC(date: string, format?: string, timezone: string = "Asia/Shanghai"): number {
|
||||
if (!format) return dayjs.tz(date, timezone).valueOf()
|
||||
return dayjs.tz(date, format, timezone).valueOf()
|
||||
}
|
1
server/utils/index.ts
Normal file
1
server/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
import type { SourceInfo } from "@shared/types"
|
81
server/utils/rss2json.ts
Normal file
81
server/utils/rss2json.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { XMLParser } from "fast-xml-parser"
|
||||
import { $fetch } from "ofetch"
|
||||
|
||||
export async function rss2json(url: string) {
|
||||
if (!/^https?:\/\/[^\s$.?#].\S*/i.test(url)) return null
|
||||
|
||||
const data = await $fetch(url)
|
||||
|
||||
const xml = new XMLParser({
|
||||
attributeNamePrefix: "",
|
||||
textNodeName: "$text",
|
||||
ignoreAttributes: false,
|
||||
})
|
||||
|
||||
const result = xml.parse(data as string)
|
||||
|
||||
let channel = result.rss && result.rss.channel ? result.rss.channel : result.feed
|
||||
if (Array.isArray(channel)) channel = channel[0]
|
||||
|
||||
const rss = {
|
||||
title: channel.title ?? "",
|
||||
description: channel.description ?? "",
|
||||
link: channel.link && channel.link.href ? channel.link.href : channel.link,
|
||||
image: channel.image ? channel.image.url : channel["itunes:image"] ? channel["itunes:image"].href : "",
|
||||
category: channel.category || [],
|
||||
items: [],
|
||||
}
|
||||
|
||||
let items = channel.item || channel.entry || []
|
||||
if (items && !Array.isArray(items)) items = [items]
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const val = items[i]
|
||||
const media = {}
|
||||
|
||||
const obj = {
|
||||
id: val.guid && val.guid.$text ? val.guid.$text : val.id,
|
||||
title: val.title && val.title.$text ? val.title.$text : val.title,
|
||||
description: val.summary && val.summary.$text ? val.summary.$text : val.description,
|
||||
link: val.link && val.link.href ? val.link.href : val.link,
|
||||
author: val.author && val.author.name ? val.author.name : val["dc:creator"],
|
||||
published: val.created ? Date.parse(val.created) : val.pubDate ? Date.parse(val.pubDate) : Date.now(),
|
||||
created: val.updated ? Date.parse(val.updated) : val.pubDate ? Date.parse(val.pubDate) : val.created ? Date.parse(val.created) : Date.now(),
|
||||
category: val.category || [],
|
||||
content: val.content && val.content.$text ? val.content.$text : val["content:encoded"],
|
||||
enclosures: val.enclosure ? (Array.isArray(val.enclosure) ? val.enclosure : [val.enclosure]) : [],
|
||||
};
|
||||
|
||||
["content:encoded", "podcast:transcript", "itunes:summary", "itunes:author", "itunes:explicit", "itunes:duration", "itunes:season", "itunes:episode", "itunes:episodeType", "itunes:image"].forEach((s) => {
|
||||
// @ts-expect-error TODO
|
||||
if (val[s]) obj[s.replace(":", "_")] = val[s]
|
||||
})
|
||||
|
||||
if (val["media:thumbnail"]) {
|
||||
Object.assign(media, { thumbnail: val["media:thumbnail"] })
|
||||
obj.enclosures.push(val["media:thumbnail"])
|
||||
}
|
||||
|
||||
if (val["media:content"]) {
|
||||
Object.assign(media, { thumbnail: val["media:content"] })
|
||||
obj.enclosures.push(val["media:content"])
|
||||
}
|
||||
|
||||
if (val["media:group"]) {
|
||||
if (val["media:group"]["media:title"]) obj.title = val["media:group"]["media:title"]
|
||||
|
||||
if (val["media:group"]["media:description"]) obj.description = val["media:group"]["media:description"]
|
||||
|
||||
if (val["media:group"]["media:thumbnail"]) obj.enclosures.push(val["media:group"]["media:thumbnail"].url)
|
||||
|
||||
if (val["media:group"]["media:content"]) obj.enclosures.push(val["media:group"]["media:content"])
|
||||
}
|
||||
|
||||
Object.assign(obj, { media })
|
||||
|
||||
// @ts-expect-error TODO
|
||||
rss.items.push(obj)
|
||||
}
|
||||
|
||||
return rss
|
||||
}
|
@ -10,24 +10,34 @@ export interface Section {
|
||||
}
|
||||
|
||||
export interface NewsItem {
|
||||
id: string | number // unique
|
||||
title: string
|
||||
cover?: string
|
||||
author?: string
|
||||
desc?: string
|
||||
url: string
|
||||
timestamp?: number
|
||||
mobileUrl?: string
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
|
||||
// 路由数据
|
||||
export interface SourceInfo {
|
||||
name: string
|
||||
title: string
|
||||
type: string
|
||||
description?: string
|
||||
params?: Record<string, string | object>
|
||||
total: number
|
||||
link?: string
|
||||
updateTime: string
|
||||
data: NewsItem[]
|
||||
updateTime: number | string
|
||||
items: NewsItem[]
|
||||
}
|
||||
|
||||
export type OResponse = {
|
||||
status: "success" | "cache"
|
||||
data: SourceInfo
|
||||
} | {
|
||||
status: "error"
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface RSS2JSON {
|
||||
id?: string
|
||||
title: string
|
||||
description: string
|
||||
link: string
|
||||
published: number
|
||||
created: number
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { SourceID, SourceInfo } from "@shared/types"
|
||||
import type { OResponse, SourceID, SourceInfo } from "@shared/types"
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-react"
|
||||
import type { UseQueryResult } from "@tanstack/react-query"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
@ -6,7 +6,7 @@ import { relativeTime } from "@shared/utils"
|
||||
import clsx from "clsx"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
import { useAtom } from "jotai"
|
||||
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react"
|
||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react"
|
||||
import { sources } from "@shared/data"
|
||||
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"
|
||||
import { focusSourcesAtom, refetchSourceAtom } from "~/atoms"
|
||||
@ -45,7 +45,7 @@ export const CardWrapper = forwardRef<HTMLDivElement, ItemsProps>(({ id, isDragg
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
"flex flex-col bg-base border rounded-md h-500px",
|
||||
"flex flex-col bg-base border rounded-md h-450px border-gray-500/40",
|
||||
isDragged && "op-50",
|
||||
isOverlay ? "bg-glass" : "",
|
||||
)}
|
||||
@ -71,8 +71,12 @@ export function NewsCard({ id, inView, isOverlay, handleListeners }: NewsCardPro
|
||||
if (Date.now() - _refetchTime < 1000) {
|
||||
url = `/api/${_id}?latest`
|
||||
}
|
||||
const response = await fetch(url)
|
||||
return await response.json() as SourceInfo
|
||||
const response = await fetch(url).then(res => res.json())
|
||||
if (response.status === "error") {
|
||||
throw new Error(response.message)
|
||||
} else {
|
||||
return response.data as SourceInfo
|
||||
}
|
||||
},
|
||||
// refetch 时显示原有的数据
|
||||
placeholderData: prev => prev,
|
||||
@ -125,7 +129,7 @@ export function NewsCard({ id, inView, isOverlay, handleListeners }: NewsCardPro
|
||||
className={clsx("i-ph:arrow-clockwise", query.isFetching && "animate-spin")}
|
||||
onClick={manualRefetch}
|
||||
/>
|
||||
<button type="button" className={clsx(focusSources.includes(id) ? "i-ph:star-fill" : "i-ph:star")} onClick={addFocusList} />
|
||||
<button type="button" className={clsx(focusSources.includes(id) ? "i-ph:star-fill" : "i-ph:star", "color-primary")} onClick={addFocusList} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@ -154,7 +158,7 @@ function Num({ num }: { num: number }) {
|
||||
}
|
||||
|
||||
function NewsList({ query }: Query) {
|
||||
const items = query.data?.data
|
||||
const items = query.data?.items
|
||||
if (items?.length) {
|
||||
return (
|
||||
<>
|
||||
@ -162,12 +166,12 @@ function NewsList({ query }: Query) {
|
||||
<div key={item.title} className="flex gap-2 items-center">
|
||||
<Num num={i + 1} />
|
||||
<a href={item.url} target="_blank" className="my-1 w-full flex items-center justify-between flex-wrap">
|
||||
<span className="flex-1 mr-2">
|
||||
<span className="flex-1 mr-2 hover:(underline underline-offset-4)">
|
||||
{item.title}
|
||||
</span>
|
||||
{item.timestamp && (
|
||||
{item?.extra?.date && (
|
||||
<span className="text-xs text-gray-4/80">
|
||||
{relativeTime(item.timestamp)}
|
||||
{relativeTime(item.extra.date)}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
|
24
test/common.test.ts
Normal file
24
test/common.test.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { tranformToUTC } from "#/utils/date"
|
||||
|
||||
describe("transform Beijing time to UTC in different timezone", () => {
|
||||
const a = "2024/10/3 12:26:16"
|
||||
const b = 1727929576000
|
||||
it("in UTC", () => {
|
||||
Object.assign(process.env, { TZ: "UTC" })
|
||||
const date = tranformToUTC(a)
|
||||
expect(date).toBe(b)
|
||||
})
|
||||
|
||||
it("in Beijing", () => {
|
||||
Object.assign(process.env, { TZ: "Asia/Shanghai" })
|
||||
const date = tranformToUTC(a)
|
||||
expect(date).toBe(b)
|
||||
})
|
||||
|
||||
it("in New York", () => {
|
||||
Object.assign(process.env, { TZ: "America/New_York" })
|
||||
const date = tranformToUTC(a)
|
||||
expect(date).toBe(b)
|
||||
})
|
||||
})
|
@ -1,11 +0,0 @@
|
||||
import { metadata } from "@shared/data"
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
// 通过两次 diff 来找出数组的差异
|
||||
describe("data", () => {
|
||||
it.for(Object.entries(metadata))(` "%s" source list shoule be unique`, ([, { sourceList }]) => {
|
||||
if (sourceList) {
|
||||
expect(new Set(sourceList).size).toBe(sourceList.length)
|
||||
}
|
||||
})
|
||||
})
|
9
test/rss2json.test.ts
Normal file
9
test/rss2json.test.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { it } from "vitest"
|
||||
import { zaobao } from "#/sources/zaobao"
|
||||
|
||||
it.skip("res", {
|
||||
timeout: 10000,
|
||||
}, async () => {
|
||||
const res = await zaobao()
|
||||
console.log(res)
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": ["./tsconfig.base.json", "./dist/.nitro/types/tsconfig.json"],
|
||||
"extends": ["./tsconfig.base.json"],
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2020"],
|
||||
"baseUrl": ".",
|
||||
|
@ -22,14 +22,11 @@ export default defineConfig({
|
||||
srcDir: "server",
|
||||
alias: {
|
||||
"@shared": fileURLToPath(new URL("shared", import.meta.url)),
|
||||
"#": fileURLToPath(new URL("server", import.meta.url)),
|
||||
},
|
||||
runtimeConfig: {
|
||||
// apiPrefix: "",
|
||||
},
|
||||
typescript: {
|
||||
generateTsConfig: true,
|
||||
},
|
||||
minify: false,
|
||||
preset: process.env.VERCEL ? "vercel-edge" : "node-server",
|
||||
}),
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user