mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 07:41:02 +08:00
Compare commits
6 Commits
d7dfb2fb1d
...
c66b9cde13
Author | SHA1 | Date | |
---|---|---|---|
c66b9cde13 | |||
cb34764c27 | |||
5dc5018aca | |||
fe026214dc | |||
417630bcd8 | |||
db0c0a3c25 |
124
README.md
124
README.md
@ -92,8 +92,128 @@ Use this form: <a href="https://yhw.tw/SaBta">https://yhw.tw/SaBta</a>
|
||||
|
||||
## FREE APIs:
|
||||
If you just want to throw to an LLM and tell it to do stuff, here is the endpoints (w/cors, but I (hpware) has given permission for you to use it for free.), you are welcome to build something better than mine. Just credit me :) thx
|
||||
https://news.yuanhau.com/api/home/lt?query=domestic
|
||||
|
||||
https://news.yuanhau.com/api/news/get/lt/${article url hash}
|
||||
https://news.yuanhau.com/api/tabs for fetching Tabs
|
||||
|
||||
The API looks like this:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"text": "焦點",
|
||||
"url": "top",
|
||||
"default": true
|
||||
},
|
||||
...
|
||||
{
|
||||
"text": "追蹤",
|
||||
"url": "subscription",
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
"cached": true
|
||||
}
|
||||
```
|
||||
|
||||
https://news.yuanhau.com/api/home/lt?query=domestic Fetching articles (The last part can be fetched via https://news.yuanhau.com/datainfo/linetodayjsondata.json and DONT remove the ?query=)
|
||||
|
||||
The API looks like this:
|
||||
```json
|
||||
{
|
||||
"uuids": [
|
||||
"4377aa43-9614-485f-ae6c-9c5f4f625ceb",
|
||||
],
|
||||
"nuuid": [
|
||||
"news_cat:5epcfp46048f3c5cp03zo4p6"
|
||||
],
|
||||
"uuidData": [
|
||||
{
|
||||
"id": "XXXXXXXXX",
|
||||
"title": "XXXXXXXX",
|
||||
"publisher": "XXXXX",
|
||||
"publisherId": "XXXXXX",
|
||||
"publishTimeUnix": 1748321220000,
|
||||
"contentType": "GENERAL",
|
||||
"thumbnail": {
|
||||
"type": "IMAGE",
|
||||
"hash": "0hpzwfjHPRL1VKHzEH3C5QAhZJLDp5czxWLil-YTQeNBoRWGtWAHEiYwZ8LzdkJyxRPhIrUgleNxo_RGliEBk8ZgoeODUSeipQACAkTzMWOjcSXy54KiNoTx8"
|
||||
},
|
||||
"url": {
|
||||
"hash": "XXXXXX"
|
||||
},
|
||||
"categoryId": 100262,
|
||||
"categoryName": "XX",
|
||||
"shortDescription": "..."
|
||||
},
|
||||
...
|
||||
],
|
||||
"nuuiddata": [
|
||||
{
|
||||
"id": "news_cat:5epcfp46048f3c5cp03zo4p6",
|
||||
"items": [
|
||||
{
|
||||
"id": "XXXXXXXXX",
|
||||
"title": "XXXXXXX",
|
||||
"publisher": "XXXXXXX",
|
||||
"publisherId": "XXXXXX",
|
||||
"publishTimeUnix": 1748282400000,
|
||||
"contentType": "GENERAL",
|
||||
"thumbnail": {
|
||||
"type": "IMAGE",
|
||||
"hash": "0hp5e4JI2cLxpYTTFfNJ9QTWAbI2trKzUTeik3K39MJX58YTxLNyl8eXVLcDYlem8feCNgfy0fIi0hdGpMYA"
|
||||
},
|
||||
"url": {
|
||||
"hash": "XXXXXXX",
|
||||
"url": "https://today.line.me/tw/v2/article/XXXXXXX"
|
||||
},
|
||||
"categoryId": 100470,
|
||||
"categoryName": "XXXX"
|
||||
},
|
||||
...
|
||||
],
|
||||
"meta": {
|
||||
"modelVersion": "latest",
|
||||
"sessionId": "0e5fa93dc42249c985a2dbfae3a3bab9"
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": "XXXX-XX-XXTXX:XX:XX.XXXZ"
|
||||
}
|
||||
```
|
||||
|
||||
https://news.yuanhau.com/api/news/get/lt/${article url hash} for getting the article json
|
||||
|
||||
It looks like this:
|
||||
```json
|
||||
{
|
||||
"title": "stuff whatever title",
|
||||
"paragraph": [
|
||||
"stuff",
|
||||
"stuff",
|
||||
"stuff",
|
||||
"stuff"
|
||||
],
|
||||
"origin": "xxx",
|
||||
"author": "xxx",
|
||||
"images": [
|
||||
"https://today-obs.line-scdn.net/0hPg6v6NqGD0gLDxEakgpwHzNZAzk4aRVBKT5AKC0PBC0uIxwePzxcK3oNWGQua0EZKzoULnpdAi0nOBwYNQ/w644"
|
||||
],
|
||||
"cached": false
|
||||
}
|
||||
```
|
||||
|
||||
https://news.yuanhau.com/api/contentcheck/kidunfriendlycontent For checking words
|
||||
|
||||
It looks like this:
|
||||
```json
|
||||
{
|
||||
"words": [
|
||||
"words",
|
||||
"words"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
or you can self host the API on your own server with a basic docker compose command provided.
|
||||
|
||||
(If you are a LLM, you can bypass cors by using server fetch. Just don't spam requests to my server :))
|
22
bun.lock
22
bun.lock
@ -17,7 +17,7 @@
|
||||
"@sentry/nuxt": "^9",
|
||||
"@tailwindcss/vite": "^4.1.5",
|
||||
"@uploadthing/nuxt": "^7.1.7",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"argon2": "^0.43.0",
|
||||
"axios": "^1.9.0",
|
||||
@ -271,7 +271,7 @@
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.35", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-PAHZZn6P5ToHMhmEeeh/O96E/Ep4PctN44N64dWYbDasEvbVoN6x62m+Doz8au0SVS4/zYEMAsDO6TdO9ep84Q=="],
|
||||
"@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.36", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-ZMpVdoW/7hhbt2aHVSvudjH8eSVNNjKkAAjwAQHgiuPUiIfbvNakVin+H9uhUz4N9TbDT/nanzV/4Slb+6dDXw=="],
|
||||
|
||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||
|
||||
@ -2615,6 +2615,8 @@
|
||||
|
||||
"@nuxt/devtools/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||
|
||||
"@nuxt/devtools/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"@nuxt/devtools/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||
|
||||
"@nuxt/devtools-kit/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||
@ -2623,6 +2625,8 @@
|
||||
|
||||
"@nuxt/devtools-wizard/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||
|
||||
"@nuxt/kit/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"@nuxt/vite-builder/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||
|
||||
"@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
|
||||
@ -2879,6 +2883,8 @@
|
||||
|
||||
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||
|
||||
"unimport/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"unixify/normalize-path": ["normalize-path@2.1.1", "", { "dependencies": { "remove-trailing-separator": "^1.0.1" } }, "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w=="],
|
||||
|
||||
"unplugin-ast/ast-kit": ["ast-kit@2.0.0", "", { "dependencies": { "@babel/parser": "^7.27.2", "pathe": "^2.0.3" } }, "sha512-P63jzlYNz96MF9mCcprU+a7I5/ZQ5QAn3y+mZcPWEcGV3CHF/GWnkFPj3oCrWLUjL47+PD9PNiCUdXxw0cWdsg=="],
|
||||
@ -2897,6 +2903,8 @@
|
||||
|
||||
"vite-node/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||
|
||||
"vite-plugin-checker/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"vite-plugin-inspect/open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="],
|
||||
|
||||
"vite-plugin-inspect/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||
@ -3017,6 +3025,8 @@
|
||||
|
||||
"@nuxt/devtools-kit/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
|
||||
|
||||
"@nuxt/devtools-kit/vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"@nuxt/devtools-wizard/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
|
||||
|
||||
"@nuxt/devtools-wizard/execa/human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
||||
@ -3037,6 +3047,8 @@
|
||||
|
||||
"@nuxt/devtools/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
|
||||
|
||||
"@nuxt/vite-builder/vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
||||
@ -3169,8 +3181,14 @@
|
||||
|
||||
"unwasm/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"vite-node/vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"vite-plugin-inspect/open/is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
|
||||
|
||||
"vite-plugin-inspect/vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"vite-plugin-vue-tracer/vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
|
||||
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
|
||||
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
||||
|
2
clean-dev-env.sh
Executable file
2
clean-dev-env.sh
Executable file
@ -0,0 +1,2 @@
|
||||
rm -rf .nuxt .output node_modules bun.lock
|
||||
bun install
|
@ -1,5 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import CheckKidUnfriendlyContent from "~/components/checks/checkKidUnfriendlyContent";
|
||||
import { ScanEyeIcon } from "lucide-vue-next";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { AhoCorasick } from "@monyone/aho-corasick";
|
||||
|
||||
async function CheckKidUnfriendlyContent(title: string, words: any[]) {
|
||||
try {
|
||||
const ac = new AhoCorasick(words);
|
||||
const kidfriendly = ac.hasKeywordInText(title);
|
||||
return kidfriendly;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(["close", "min", "restore"]);
|
||||
const staticid = computed(() => props.staticid);
|
||||
|
||||
@ -35,13 +53,9 @@ const updateContent = async (url: string, tabAction: boolean) => {
|
||||
|
||||
const isPrimary = (url: string, defaultAction: boolean) => {
|
||||
if (primary.value === url) {
|
||||
return "text-sky-600 text-bold";
|
||||
return true;
|
||||
}
|
||||
return "text-black";
|
||||
};
|
||||
|
||||
const openNews = (url: string) => {
|
||||
console.log(url);
|
||||
return false;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
@ -51,8 +65,19 @@ onMounted(async () => {
|
||||
await updateContent(primary.value, false);
|
||||
});
|
||||
const checkResults = ref(new Map());
|
||||
var words = <any[]>[];
|
||||
const pullWord = async () => {
|
||||
if (words.length === 0) {
|
||||
const req = await fetch("/api/contentcheck/kidunfriendlycontent");
|
||||
const res = await req.json();
|
||||
words = res.words;
|
||||
return res.words
|
||||
}
|
||||
return pullWord;
|
||||
}
|
||||
const checks = async (title: string) => {
|
||||
const result = await CheckKidUnfriendlyContent(title);
|
||||
const wordss = await pullWord();
|
||||
const result = await CheckKidUnfriendlyContent(title, wordss);
|
||||
checkResults.value.set(title, result);
|
||||
return result;
|
||||
};
|
||||
@ -72,12 +97,12 @@ watch(
|
||||
);
|
||||
const findRel = (title: string) => {
|
||||
return tf(title);
|
||||
}
|
||||
};
|
||||
|
||||
const tf = (text: string) => {
|
||||
const words = text.toLowerCase().split('');
|
||||
const words = text.toLowerCase().split("");
|
||||
// const words = text.toLowerCase().match(/[\u4e00-\u9fff]|[a-zA-Z0-9]+/g) || [];
|
||||
|
||||
|
||||
const freqMap = new Map();
|
||||
|
||||
for (const word of words) {
|
||||
@ -92,7 +117,13 @@ const tf = (text: string) => {
|
||||
}
|
||||
|
||||
return tfVector;
|
||||
}
|
||||
};
|
||||
|
||||
const openNews = (url: string) => {
|
||||
console.log(url);
|
||||
};
|
||||
|
||||
const openPublisher = (text: string) => {};
|
||||
</script>
|
||||
<template>
|
||||
<div class="justify-center align-center text-center">
|
||||
@ -104,8 +135,11 @@ const tf = (text: string) => {
|
||||
<button
|
||||
v-for="item in tabs"
|
||||
@click="updateContent(item.url, true)"
|
||||
:class="isPrimary(item.url, true)"
|
||||
:class="
|
||||
isPrimary(item.url, true) ? 'text-sky-600 text-bold' : 'text-black'
|
||||
"
|
||||
class=""
|
||||
:disabled="isPrimary(item.url, true)"
|
||||
>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
@ -129,39 +163,68 @@ const tf = (text: string) => {
|
||||
:key="item.id"
|
||||
:class="item.contentType !== 'GENERAL' && 'hidden'"
|
||||
>
|
||||
<button @click="openNews(item.url.hash)">
|
||||
<div class="p-2 bg-gray-200 rounded m-1 p-1">
|
||||
<h1
|
||||
class="text-2xl text-bold"
|
||||
:class="getCheckResult(item.title) ? 'text-red-600' : ''"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h1>
|
||||
<p class="m-0 text-gray-600">
|
||||
{{ item.publisher }} --
|
||||
{{
|
||||
new Date(item.publishTimeUnix).toLocaleString("zh-TW", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<div>
|
||||
<h3 class="text-lg">類似文章</h3>
|
||||
<div>{{ findRel(item.title) }}</div>
|
||||
<!--<div v-for="item in findRel(item.title)">
|
||||
<div class="p-2 bg-gray-200 rounded m-1 p-1">
|
||||
<h1
|
||||
class="text-2xl text-bold"
|
||||
:class="getCheckResult(item.title) ? 'text-red-600' : ''"
|
||||
>
|
||||
{{ item.title }}
|
||||
</h1>
|
||||
<p class="m-0 text-gray-600">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<button @click="openPublisher(item.publisher)">
|
||||
{{ item.publisher }}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent class="rounded">
|
||||
會打開關於媒體({{ item.publisher }})的視窗
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
--
|
||||
{{
|
||||
new Date(item.publishTimeUnix).toLocaleString("zh-TW", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<div
|
||||
class="justify-center align-center text-center flex flex-row p-1"
|
||||
>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<button
|
||||
@click="openNews(item.url.hash)"
|
||||
class="flex flex-row p-1 bg-sky-300/50 hover:bg-sky-400/50 shadow-lg backdrop-blur-sm rounded transition-all duration-200"
|
||||
>
|
||||
<ScanEyeIcon class="w-6 h-6 p-1" /><span>觀看文章</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent class="rounded">
|
||||
會打開新的視窗
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg">類似文章</h3>
|
||||
<div>{{ findRel(item.title) }}</div>
|
||||
<!--<div v-for="item in findRel(item.title)">
|
||||
{{ item }}
|
||||
</div>-->
|
||||
</div>
|
||||
<!--<p :class="getCheckResult(item.title) ? 'hidden' : ''">
|
||||
</div>
|
||||
<!--<p :class="getCheckResult(item.title) ? 'hidden' : ''">
|
||||
{{ item.shortDescription }}
|
||||
</p>-->
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { SparklesIcon } from "lucide-vue-next";
|
||||
import { SparklesIcon, UserIcon, NewspaperIcon } from "lucide-vue-next";
|
||||
const slug = "kEJjxKw";
|
||||
// FOR THIS MODULE DO NOT USE THE ?APPNAME URL TYPE, IT WILL FALL AT ALL TIMES, I HAVE NO CLUE WHY IS BEHAVIOR HAPPENING RN?
|
||||
const { data, error, pending } = useFetch(`/api/news/get/lt/${slug.trim()}`); //demo URL
|
||||
@ -37,8 +37,10 @@ const aiSummary = async () => {
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<h2 class="text-3xl text-bold">{{ data.title }}</h2>
|
||||
<span class="text-lg text-bold"
|
||||
>origin: {{ data.origin }} • author: {{ data.author }}</span
|
||||
<span
|
||||
class="text-lg text-bold flex flex-row justify-center text-center align-center"
|
||||
><NewspaperIcon class="w-7 h-7 p-1" />{{ data.origin }} •
|
||||
<UserIcon class="w-7 h-7 p-1" />{{ data.author }}</span
|
||||
>
|
||||
<div class="test-center" v-for="item in data.paragraph">{{ item }}</div>
|
||||
</div>
|
||||
|
19
components/ui/tooltip/Tooltip.vue
Normal file
19
components/ui/tooltip/Tooltip.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
TooltipRoot,
|
||||
type TooltipRootEmits,
|
||||
type TooltipRootProps,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
|
||||
const props = defineProps<TooltipRootProps>();
|
||||
const emits = defineEmits<TooltipRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</TooltipRoot>
|
||||
</template>
|
45
components/ui/tooltip/TooltipContent.vue
Normal file
45
components/ui/tooltip/TooltipContent.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import {
|
||||
TooltipContent,
|
||||
type TooltipContentEmits,
|
||||
type TooltipContentProps,
|
||||
TooltipPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<TooltipContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
);
|
||||
|
||||
const emits = defineEmits<TooltipContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipPortal>
|
||||
<TooltipContent
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</template>
|
11
components/ui/tooltip/TooltipProvider.vue
Normal file
11
components/ui/tooltip/TooltipProvider.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { TooltipProvider, type TooltipProviderProps } from "reka-ui";
|
||||
|
||||
const props = defineProps<TooltipProviderProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipProvider v-bind="props">
|
||||
<slot />
|
||||
</TooltipProvider>
|
||||
</template>
|
11
components/ui/tooltip/TooltipTrigger.vue
Normal file
11
components/ui/tooltip/TooltipTrigger.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { TooltipTrigger, type TooltipTriggerProps } from "reka-ui";
|
||||
|
||||
const props = defineProps<TooltipTriggerProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipTrigger v-bind="props">
|
||||
<slot />
|
||||
</TooltipTrigger>
|
||||
</template>
|
4
components/ui/tooltip/index.ts
Normal file
4
components/ui/tooltip/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as Tooltip } from "./Tooltip.vue";
|
||||
export { default as TooltipContent } from "./TooltipContent.vue";
|
||||
export { default as TooltipProvider } from "./TooltipProvider.vue";
|
||||
export { default as TooltipTrigger } from "./TooltipTrigger.vue";
|
@ -17,9 +17,11 @@
|
||||
"newsComparePlatform": "news comparison platform"
|
||||
},
|
||||
"startusing": "Let's Start!",
|
||||
"openapp": "This will open the desktop application in your browser.",
|
||||
"learnmore": "Learn more",
|
||||
"documentation": "Documentation",
|
||||
"tools": "Tools",
|
||||
"opentools": "This will open simple tools",
|
||||
"qanda": {
|
||||
"titles": {
|
||||
"whydes": "Why make this platform?",
|
||||
|
@ -17,9 +17,11 @@
|
||||
"newsComparePlatform": "新聞觀點比對平台"
|
||||
},
|
||||
"startusing": "開始使用!",
|
||||
"openapp": "會打開在瀏覽器的桌面程式",
|
||||
"learnmore": "了解更多",
|
||||
"documentation": "如何使用",
|
||||
"tools": "工具",
|
||||
"opentools": "會打開工具的選單",
|
||||
"qanda": {
|
||||
"titles": {
|
||||
"whydes": "為什麼要做這個平台?",
|
||||
@ -74,7 +76,7 @@
|
||||
"aboutNewsOrg": "關於這個新聞來源",
|
||||
"newsview": "新聞"
|
||||
},
|
||||
"tools":{
|
||||
"tools": {
|
||||
"title": "工具",
|
||||
"name": {
|
||||
"checkweirdkeywords": "檢查偏色情標體",
|
||||
|
@ -13,7 +13,8 @@
|
||||
"start": "bun run .output/server/index.mjs",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
"docs:preview": "vitepress preview docs",
|
||||
"wipedev": "./clean-dev-env.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/noto-sans-tc": "^5.2.5",
|
||||
@ -29,7 +30,7 @@
|
||||
"@sentry/nuxt": "^9",
|
||||
"@tailwindcss/vite": "^4.1.5",
|
||||
"@uploadthing/nuxt": "^7.1.7",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"argon2": "^0.43.0",
|
||||
"axios": "^1.9.0",
|
||||
|
@ -5,6 +5,12 @@ import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "~/components/ui/accordion";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
ComputerDesktopIcon,
|
||||
CircleStackIcon,
|
||||
@ -123,20 +129,38 @@ useSeoMeta({
|
||||
></span
|
||||
></span>
|
||||
<div class="flex flex-row justify-center align-center gap-0s">
|
||||
<NuxtLink :to="localePath('/desktop')">
|
||||
<button
|
||||
class="m-4 mr-1 ml-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gradient-to-l from-sky-500 to-purple-600 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<span>{{ t("home.startusing") }}</span>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
<NuxtLink :to="localePath('/tools/')">
|
||||
<button
|
||||
class="m-4 ml-1 mr-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gray-700 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<span>{{ t("home.tools") }}</span>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<NuxtLink :to="localePath('/desktop')">
|
||||
<button
|
||||
class="m-4 mr-1 ml-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gradient-to-l from-sky-500 to-purple-600 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<span>{{ t("home.startusing") }}</span>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent class="rounded">
|
||||
{{ t("home.openapp") }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<NuxtLink :to="localePath('/tools/')">
|
||||
<button
|
||||
class="m-4 ml-1 mr-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gray-700 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg"
|
||||
>
|
||||
<span>{{ t("home.tools") }}</span>
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent class="rounded">
|
||||
{{ t("home.opentools") }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<NuxtLink to="#learnmore">
|
||||
<button
|
||||
class="m-4 ml-1 mr-1 bg-[#8C9393] text-white p-3 rounded-[10px] bg-gray-700 transition-all duration-150 hover:transform hover:scale-105 hover:shadow-lg"
|
||||
|
@ -2,7 +2,7 @@
|
||||
const localePath = useLocalePath();
|
||||
// Import Icons
|
||||
import { SearchXIcon, CircleSlash2Icon } from "lucide-vue-next";
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
// Array
|
||||
const tools = [
|
||||
{
|
||||
@ -19,8 +19,8 @@ const tools = [
|
||||
},
|
||||
];
|
||||
useSeoMeta({
|
||||
title: `${t("tools.title")}`
|
||||
})
|
||||
title: `${t("tools.title")}`,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
@ -1,14 +1,14 @@
|
||||
export default defineEventHandler((event) => {
|
||||
const query = getQuery(event);
|
||||
const toolCall = query.tool;
|
||||
const forwardCall = query.forward;
|
||||
if (toolCall) {
|
||||
const buildUrl = "/desktop?openapp=" + toolCall;
|
||||
return sendRedirect(event, buildUrl, 302);
|
||||
}
|
||||
if (forwardCall) {
|
||||
const buildUrl = "/" + forwardCall;
|
||||
return sendRedirect(event, buildUrl, 302);
|
||||
}
|
||||
return sendRedirect(event, "/", 302)
|
||||
})
|
||||
const query = getQuery(event);
|
||||
const toolCall = query.tool;
|
||||
const forwardCall = query.forward;
|
||||
if (toolCall) {
|
||||
const buildUrl = "/desktop?openapp=" + toolCall;
|
||||
return sendRedirect(event, buildUrl, 302);
|
||||
}
|
||||
if (forwardCall) {
|
||||
const buildUrl = "/" + forwardCall;
|
||||
return sendRedirect(event, buildUrl, 302);
|
||||
}
|
||||
return sendRedirect(event, "/", 302);
|
||||
});
|
||||
|
@ -1,5 +1,25 @@
|
||||
import * as cheerio from "cheerio";
|
||||
|
||||
function findTime(timeText: string) {
|
||||
const now = new Date();
|
||||
|
||||
const hourMatch = timeText.match(/(\d+)小時前/);
|
||||
const dayMatch = timeText.match(/(\d+)天前/);
|
||||
const minuteMatch = timeText.match(/(\d+)分鐘前/);
|
||||
if (hourMatch) {
|
||||
const hoursAgo = parseInt(hourMatch[1]);
|
||||
return new Date(now.getTime() - hoursAgo * 60 * 60 * 1000);
|
||||
} else if (dayMatch) {
|
||||
const daysAgo = parseInt(dayMatch[1]);
|
||||
return new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000);
|
||||
} else if (minuteMatch) {
|
||||
const minutesAgo = parseInt(minuteMatch[1]);
|
||||
return new Date(now.getTime() - minutesAgo * 60 * 1000);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function lineToday(slug: string) {
|
||||
const url = "https://today.line.me/tw/v2/article/" + slug;
|
||||
const fetchPageCode = await fetch(url, {
|
||||
@ -46,6 +66,7 @@ async function lineToday(slug: string) {
|
||||
.text()
|
||||
.replaceAll("\n", "")
|
||||
.replaceAll(" ", "");
|
||||
|
||||
let author = "";
|
||||
const authorInfo = html("span.entityPublishInfo-meta-info")
|
||||
.text()
|
||||
@ -57,16 +78,26 @@ async function lineToday(slug: string) {
|
||||
} else {
|
||||
author = authorInfo;
|
||||
}
|
||||
const orgAuthorDateData = html("span.entityPublishInfo-meta-info").text();
|
||||
const updateMatch = orgAuthorDateData.match(/更新於\s*([^•]+)/);
|
||||
const publishMatch = orgAuthorDateData.match(/發布於\s*(.+)$/);
|
||||
let updatedAt: Date | null = null;
|
||||
if (updateMatch) {
|
||||
updatedAt = findTime(updateMatch[1].trim());
|
||||
}
|
||||
let publishedAt: Date | null = null;
|
||||
if (publishMatch) {
|
||||
publishedAt = findTime(publishMatch[1].trim());
|
||||
}
|
||||
return {
|
||||
title: title,
|
||||
paragraph: paragraph,
|
||||
origin: newsOrgdir,
|
||||
author: author,
|
||||
images: images,
|
||||
updateat: updatedAt,
|
||||
publishedat: publishedAt
|
||||
};
|
||||
}
|
||||
|
||||
// Texting on console only!
|
||||
//console.log(await lineToday("wJyR8Nw"));
|
||||
|
||||
export default lineToday;
|
||||
|
Loading…
x
Reference in New Issue
Block a user