mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 13:04:23 +00:00
Merge pull request #5 from hpware/beta
Some checks failed
Build and Push Latest Image / build-and-push (push) Has been cancelled
Some checks failed
Build and Push Latest Image / build-and-push (push) Has been cancelled
Merge Beta features.
This commit is contained in:
commit
d3f713fc69
8 changed files with 82 additions and 210 deletions
177
README.ZH_TW.md
177
README.ZH_TW.md
|
@ -1,5 +1,7 @@
|
||||||
# 新聞解析 / News Analyze
|
# 新聞解析 / News Analyze
|
||||||
|
|
||||||
|
NOTE: 大多數的資訊都還是會會在英文版,中文版就是我想翻譯的時候寫的 (已經簡短)
|
||||||
|
|
||||||
[English Version](/README.md) [繁體中文版](/README.ZH_TW.md)
|
[English Version](/README.md) [繁體中文版](/README.ZH_TW.md)
|
||||||
|
|
||||||
   
|
   
|
||||||
|
@ -10,17 +12,16 @@ UI設計圖: [PDF 檔案](/design.pdf)
|
||||||
|
|
||||||
Reverse engineering 文檔: [about](/about/)
|
Reverse engineering 文檔: [about](/about/)
|
||||||
|
|
||||||
部署(英文): [via docker compose](/deploy.md)
|
部署(英文檔案): [via docker compose](/deploy.md)
|
||||||
|
|
||||||
## Demo:
|
## Demo:
|
||||||
你可以使用以下的連結來**立即**使用: https://yhw.tw/news?goto=desktop
|
正式版: https://yhw.tw/news
|
||||||
|
|
||||||
|
測試版: https://newsbeta.20090526.xyz (對我就是買了 一堆數字.xyz 的人)
|
||||||
|
|
||||||
## 在部署之前,請先知道:
|
## 在部署之前,請先知道:
|
||||||
此程式碼絕對不是為在 Vercel 或 Netlify 上啟動而設計的,它現在在主網站程式碼中具有crawling,而且整個「快取功能」都基於Ram,所以請不要使用這些平台,對於 Zeabur 來說,您的成本一定會比較貴一點。網址:https://news.yuanhau.com 託管在我自己的infra上,你也應該這麼做。可以在Yahoo拍賣、蝦皮取得伺服器來執行這個程式。
|
此程式碼絕對不是為在 Vercel 或 Netlify 上啟動而設計的,它現在在主網站程式碼中具有crawling,而且整個「快取功能」都基於Ram,所以請不要使用這些平台,對於 Zeabur 來說,您的成本一定會比較貴一點。網址:https://news.yuanhau.com 託管在我自己的infra上,你也應該這麼做。可以在Yahoo拍賣、蝦皮取得伺服器來執行這個程式。
|
||||||
|
|
||||||
## Note for developing
|
|
||||||
The desktop enviroment is super unstable when even using a beefy computer, even so, the desktop will lag when opening the newsView, like it's just hates being in a dev env. Prod app works tho, so you can demo it using `bun run build && bun run preview` for demoing. Please don't file a issue request for this matter. If you have the fix, please contribute using Github PRs.
|
|
||||||
|
|
||||||
## 如果要開發,你需要
|
## 如果要開發,你需要
|
||||||
- 一個 Postgres 資料庫 (你可以用 Zeabur 跑開發用資料庫,可以用我的 [優惠連結(?](https://zeabur.com/referral?referralCode=hpware),你可以拿到大約150塊的試用金額
|
- 一個 Postgres 資料庫 (你可以用 Zeabur 跑開發用資料庫,可以用我的 [優惠連結(?](https://zeabur.com/referral?referralCode=hpware),你可以拿到大約150塊的試用金額
|
||||||
- 一個 Groq 的 API
|
- 一個 Groq 的 API
|
||||||
|
@ -42,47 +43,14 @@ The desktop enviroment is super unstable when even using a beefy computer, even
|
||||||
|
|
||||||
你會看到許多觀點,但不知道這些新聞為什麼會寫比較偏見的文章。
|
你會看到許多觀點,但不知道這些新聞為什麼會寫比較偏見的文章。
|
||||||
|
|
||||||
## 靈感來自
|
## 預覽網站系統:
|
||||||
|
|
||||||
- puter.com
|
|
||||||
- Perplexity
|
|
||||||
- Ground.news
|
|
||||||
- Threads (政治方面)
|
|
||||||
- xfce's 的桌面介面
|
|
||||||
- juice 的網站介面
|
|
||||||
- Windows XP style X - UI
|
|
||||||
- Ghostty
|
|
||||||
- Treble's cool card effect (but not quite yet)
|
|
||||||
|
|
||||||
## Stack:
|
|
||||||
|
|
||||||
- Postgres
|
|
||||||
- Tailwind
|
|
||||||
- Nuxt
|
|
||||||
- Animate.css
|
|
||||||
- GSAP
|
|
||||||
- Minio S3
|
|
||||||
- Nuxt i18n
|
|
||||||
- BunJS
|
|
||||||
- Groq
|
|
||||||
- Custom Infra
|
|
||||||
- Docker
|
|
||||||
- Docker Compose
|
|
||||||
- GitHub Actions
|
|
||||||
- Line Today (非正式 APIs)
|
|
||||||
- Cheerio
|
|
||||||
- Sentry
|
|
||||||
- Umami Analytics
|
|
||||||
- Prettier
|
|
||||||
|
|
||||||
## 預覽系統:
|
|
||||||
### 首頁:
|
### 首頁:
|
||||||

|

|
||||||
|
|
||||||
### 桌面程式:
|
### 桌面程式:
|
||||||

|

|
||||||
|
|
||||||
## 如何在我的電腦上運行?
|
## 如何在我的電腦上運行測試環境?
|
||||||
|
|
||||||
1. 第一, 把 `.env.example` 改名到 `.env` 並填空白處
|
1. 第一, 把 `.env.example` 改名到 `.env` 並填空白處
|
||||||
2. 使用 `bun install` 來安裝需要的套件。
|
2. 使用 `bun install` 來安裝需要的套件。
|
||||||
|
@ -109,131 +77,4 @@ API 資訊: https://news.yuanhau.com/apis
|
||||||
|
|
||||||
如果您只是想將其交給AI並叫他幫你做網站,這裡是可以免費使用的 API,歡迎你做比我的更好的東西。請給我credit就好ㄌ:)
|
如果您只是想將其交給AI並叫他幫你做網站,這裡是可以免費使用的 API,歡迎你做比我的更好的東西。請給我credit就好ㄌ:)
|
||||||
|
|
||||||
LLM Line
|
[File](https://github.com/hpware/news-analyze?tab=readme-ov-file#free-apis)
|
||||||
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
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 DON'T 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 :))
|
|
||||||
|
|
|
@ -17,7 +17,9 @@ Goals before the next devlog: [Markdown file](/GOALS_BEFORE_NEXT_DEVLOG.md)
|
||||||
Video Guide: [YouTube](https://youtu.be/8P3qgVm6m6g)
|
Video Guide: [YouTube](https://youtu.be/8P3qgVm6m6g)
|
||||||
|
|
||||||
## Demo:
|
## Demo:
|
||||||
https://yhw.tw/news
|
Production (Latest Docker Image): https://yhw.tw/news
|
||||||
|
|
||||||
|
Beta (Beta Docekr Image): https://newsbeta.20090526.xyz
|
||||||
|
|
||||||
## Video Guide
|
## Video Guide
|
||||||
|
|
||||||
|
|
15
components/app/windows/onBoarding.vue
Normal file
15
components/app/windows/onBoarding.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<!--YouTube Embed-->
|
||||||
|
<div class="justify-center absolute inset-0 flex flex-col">
|
||||||
|
<iframe
|
||||||
|
width="560"
|
||||||
|
height="315"
|
||||||
|
src="https://www.youtube-nocookie.com/embed/8P3qgVm6m6g?si=0t8eR0wtWv6b3REE"
|
||||||
|
title="YouTube video player"
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
referrerpolicy="strict-origin-when-cross-origin"
|
||||||
|
allowfullscreen
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -174,42 +174,37 @@ const submitUserPassword = async () => {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<form
|
||||||
class="justify-center align-center text-center absloute inset-0 p-1"
|
class="flex flex-col items-center justify-center h-full align-center text-center absloute inset-0 p-1 bg-gray-200/50 backdrop-blur-sm text-black w-full absolute align-middle z-[20]"
|
||||||
|
@submit.prevent="submitUserPassword"
|
||||||
v-if="!isLoggedIn"
|
v-if="!isLoggedIn"
|
||||||
>
|
>
|
||||||
<form
|
<span class="text-2xl text-bold mb-0">{{ t("settings.login") }}</span>
|
||||||
class="flex flex-col items-center justify-center h-full"
|
<span class="mb-4 text-sm mt-0"> {{ t("settings.loginmessage") }}</span>
|
||||||
@submit.prevent="submitUserPassword"
|
<div class="">
|
||||||
v-if="!success"
|
<Input
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('settings.placeholder.user')"
|
||||||
|
class="mb-2 p-2 border rounded"
|
||||||
|
v-model="userAccount"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
:placeholder="t('settings.placeholder.password')"
|
||||||
|
class="p-2 border rounded mb-2"
|
||||||
|
v-model="userPassword"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span v-if="error" class="text-red-600 text-xs m-2"
|
||||||
|
>Error: {{ errormsg }}</span
|
||||||
>
|
>
|
||||||
<span class="text-2xl text-bold mb-0">{{ t("settings.login") }}</span>
|
<button class="bg-black text-white p-2 rounded transition duration-200">
|
||||||
<span class="mb-4 text-sm mt-0"> {{ t("settings.loginmessage") }}</span>
|
{{ t("settings.loginButton") }}
|
||||||
<div class="">
|
</button>
|
||||||
<Input
|
</form>
|
||||||
type="text"
|
<div class="justify-center align-center text-center">
|
||||||
:placeholder="t('settings.placeholder.user')"
|
|
||||||
class="mb-2 p-2 border rounded"
|
|
||||||
v-model="userAccount"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
:placeholder="t('settings.placeholder.password')"
|
|
||||||
class="p-2 border rounded mb-2"
|
|
||||||
v-model="userPassword"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span v-if="error" class="text-red-600 text-xs m-2"
|
|
||||||
>Error: {{ errormsg }}</span
|
|
||||||
>
|
|
||||||
<button class="bg-black text-white p-2 rounded transition duration-200">
|
|
||||||
{{ t("settings.loginButton") }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="justify-center align-center text-center" v-if="isLoggedIn">
|
|
||||||
<h1 class="text-3xl text-bold p-2">
|
<h1 class="text-3xl text-bold p-2">
|
||||||
{{ t("settings.greet")
|
{{ t("settings.greet")
|
||||||
}}{{ user || userData.userAccount || t("settings.defaultname") }}
|
}}{{ user || userData.userAccount || t("settings.defaultname") }}
|
||||||
|
|
|
@ -26,7 +26,7 @@ const printData = (content: any, userinput?: boolean, error?: boolean) => {
|
||||||
|
|
||||||
const displayHelp = () => {
|
const displayHelp = () => {
|
||||||
const helpContent =
|
const helpContent =
|
||||||
"Here are the commands for the Terminal \n\n execute [app]: This command opens an application in the [app] slot. \n article [id]: This command will open a LINE Today article in a window. \n about: This displays the about text window \n clear/clean: Wipe the terminal log. \n help: This help text system :D \n\n For more info, please view the documentation: https://news.yuanhau.com/docs";
|
"Here are the commands for the Terminal \n\n execute [app]: This command opens an application in the [app] slot. \n article [id]: This command will open a LINE Today article in a window. \n about: This displays the about text window \n clear/clean: Wipe the terminal log. \n help: This help text system :D";
|
||||||
printData(helpContent);
|
printData(helpContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ const cleanTTY = () => {
|
||||||
const openArticle = (inputContent: string) => {
|
const openArticle = (inputContent: string) => {
|
||||||
const match = inputContent.match(/^article\s+[a-zA-Z0-9]{7}$/);
|
const match = inputContent.match(/^article\s+[a-zA-Z0-9]{7}$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const articleId = match[1].trim();
|
const articleId = match[0].trim();
|
||||||
emit("openArticles", articleId);
|
emit("openArticles", articleId);
|
||||||
printData(`Opening article ${articleId}...`);
|
printData(`Opening article ${articleId}...`);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,6 +7,16 @@ const error = ref(false);
|
||||||
const errorMsg = ref("");
|
const errorMsg = ref("");
|
||||||
const emit = defineEmits(["windowopener", "error", "loadValue"]);
|
const emit = defineEmits(["windowopener", "error", "loadValue"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return {
|
||||||
|
userAccount: fetchViaSQL[0].username,
|
||||||
|
firstName: fetchViaSQL[0].firstName,
|
||||||
|
requested_action: "CONTINUE",
|
||||||
|
current_spot: "KEEP_LOGIN",
|
||||||
|
email: fetchViaSQL[0].email,
|
||||||
|
avatarURL: fetchViaSQL[0].avatarurl,
|
||||||
|
};
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
// 喔 我沒有加 await :( 難怪有問題
|
// 喔 我沒有加 await :( 難怪有問題
|
||||||
const { data, error: sendError } = await useFetch(
|
const { data, error: sendError } = await useFetch(
|
||||||
|
@ -18,10 +28,12 @@ try {
|
||||||
if (data.requested_action === "LOGOUT_USER") {
|
if (data.requested_action === "LOGOUT_USER") {
|
||||||
logoutUser();
|
logoutUser();
|
||||||
}
|
}
|
||||||
if (false) {
|
if (data.requested_action === "CONTINUE") {
|
||||||
allowed.value = true;
|
if (data.userAccount && data.userAccount.length !== 0) {
|
||||||
} else {
|
allowed.value = true;
|
||||||
allowed.value = false;
|
} else {
|
||||||
|
allowed.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = true;
|
error.value = true;
|
||||||
|
@ -35,7 +47,6 @@ try {
|
||||||
>
|
>
|
||||||
<div v-if="!allowed && !error" class="m-2">
|
<div v-if="!allowed && !error" class="m-2">
|
||||||
{{ t("error") }}
|
{{ t("error") }}
|
||||||
<button>Update</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error" class="m-2">
|
<div v-if="error" class="m-2">
|
||||||
<span>{{ errorMsg ? errorMsg : "" }} {{ t("systemerror") }}</span>
|
<span>{{ errorMsg ? errorMsg : "" }} {{ t("systemerror") }}</span>
|
||||||
|
|
|
@ -60,6 +60,7 @@ import NewsViewWindow from "~/components/app/windows/newsView.vue";
|
||||||
import SettingsWindow from "~/components/app/windows/settings.vue";
|
import SettingsWindow from "~/components/app/windows/settings.vue";
|
||||||
import PrivacyPolicyWindow from "~/components/app/windows/privacypolicy.vue";
|
import PrivacyPolicyWindow from "~/components/app/windows/privacypolicy.vue";
|
||||||
import TOSWindow from "~/components/app/windows/tos.vue";
|
import TOSWindow from "~/components/app/windows/tos.vue";
|
||||||
|
import onBoardingWindow from "~/components/app/windows/onBoarding.vue";
|
||||||
|
|
||||||
// Import Icons
|
// Import Icons
|
||||||
import {
|
import {
|
||||||
|
@ -203,6 +204,13 @@ const associAppWindow = [
|
||||||
component: TOSWindow,
|
component: TOSWindow,
|
||||||
translatable: false,
|
translatable: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "onboard",
|
||||||
|
id: "14",
|
||||||
|
title: "OnBoarding",
|
||||||
|
component: onBoardingWindow,
|
||||||
|
translatable: false,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// OnBoarding
|
// OnBoarding
|
||||||
|
|
|
@ -2,7 +2,7 @@ import sql from "~/server/components/postgres";
|
||||||
import getUserTokenMinusSQLInjection from "~/server/components/getUserToken";
|
import getUserTokenMinusSQLInjection from "~/server/components/getUserToken";
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// Check user data.
|
// Check user data.
|
||||||
const user = getUserTokenMinusSQLInjection(event);
|
const user = await getUserTokenMinusSQLInjection(event);
|
||||||
if (user.error.length !== 0) {
|
if (user.error.length !== 0) {
|
||||||
return {
|
return {
|
||||||
error: user.error,
|
error: user.error,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue