Merge pull request #5 from hpware/beta
Some checks failed
Build and Push Latest Image / build-and-push (push) Has been cancelled

Merge Beta features.
This commit is contained in:
yuanhau 2025-06-11 13:47:16 +08:00 committed by GitHub
commit d3f713fc69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 82 additions and 210 deletions

View file

@ -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)
![](https://hackatime-badge.hackclub.com/U087ATD163V/news-analyize) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/hpware/news-analyze?utm_source=oss&utm_medium=github&utm_campaign=hpware%2Fnews-analyze&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) ![LICENSE](https://img.shields.io/github/license/hpware/news-analyze?style=flat) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/hpware/news-analyze/build_docker_image.yml) ![](https://hackatime-badge.hackclub.com/U087ATD163V/news-analyize) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/hpware/news-analyze?utm_source=oss&utm_medium=github&utm_campaign=hpware%2Fnews-analyze&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) ![LICENSE](https://img.shields.io/github/license/hpware/news-analyze?style=flat) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/hpware/news-analyze/build_docker_image.yml)
@ -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
## 預覽系統:
### 首頁: ### 首頁:
![](/.github/README/home.png) ![](/.github/README/home.png)
### 桌面程式: ### 桌面程式:
![](/.github/README/desktop.png) ![](/.github/README/desktop.png)
## 如何在我的電腦上運行? ## 如何在我的電腦上運行測試環境?
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 :))

View file

@ -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

View 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>

View file

@ -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") }}

View file

@ -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 {

View file

@ -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>

View file

@ -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

View file

@ -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,