Compare commits

...

3 Commits

9 changed files with 106 additions and 25 deletions

View File

@ -10,6 +10,8 @@ Reverse engineering documentation: [about](/about/)
## Before deploying, please know this: ## Before deploying, please know this:
This code is absolutly NOT designed to be spinned up at Vercel or Netlify, it has the scraping system now inside of the main website code, oh also the entire "caching feature" is based in memory, so please don't use those platforms, for Zeabur your cost might be expensive. idk, I haven't tried it yet. The web url: https://news.yuanhau.com is hosted on my own infra, you should too. Please get a server off of yahoo 拍賣, 蝦皮 or eBay to do so. This code is absolutly NOT designed to be spinned up at Vercel or Netlify, it has the scraping system now inside of the main website code, oh also the entire "caching feature" is based in memory, so please don't use those platforms, for Zeabur your cost might be expensive. idk, I haven't tried it yet. The web url: https://news.yuanhau.com is hosted on my own infra, you should too. Please get a server off of yahoo 拍賣, 蝦皮 or eBay to do so.
## 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.
## Why? ## Why?

View File

@ -2,7 +2,7 @@
const { t } = useI18n(); const { t } = useI18n();
</script> </script>
<template> <template>
<div class="gap-2 flex flex-row justift-center align-center text-center"> <div class="gap-2 flex flex-row justify-center align-center text-center">
<!--版權資訊--> <!--版權資訊-->
<span class="text-sm">1.0.0</span> <span class="text-sm">1.0.0</span>
<span class="text-sm">|</span> <span class="text-sm">|</span>

View File

@ -32,7 +32,7 @@ const props = defineProps<{
<hr /> <hr />
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-xl">版權資訊</span> <span class="text-xl">版權資訊</span>
<copyrightInfo /> <copyrightInfo class="justify-center align-center text-center" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -241,7 +241,7 @@ const openPublisher = (text: string) => {
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<button @click="openPublisher(item.publisher)"> <button @click="openPublisher(item.publisherId)">
{{ item.publisher }} {{ item.publisher }}
</button> </button>
</TooltipTrigger> </TooltipTrigger>

View File

@ -69,9 +69,7 @@ const aiSummary = async () => {
/>Activate />Activate
</button> </button>
<div v-else> <div v-else>
<div v-if="!summaryText"> <div v-if="!summaryText">Loading...</div>
Loading...
</div>
<div v-else>{{ summaryText }}</div> <div v-else>{{ summaryText }}</div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ArrowBigRightDashIcon, BadgeCheckIcon, BadgeXIcon, PanelTopIcon, RssIcon, NewspaperIcon, BotMessageSquareIcon, CombineIcon } from "lucide-vue-next"; import {
ArrowBigRightDashIcon,
BadgeCheckIcon,
BadgeXIcon,
PanelTopIcon,
RssIcon,
NewspaperIcon,
BotMessageSquareIcon,
CombineIcon,
} from "lucide-vue-next";
const apis = [ const apis = [
{ {
icon: ArrowBigRightDashIcon, icon: ArrowBigRightDashIcon,
@ -40,7 +49,8 @@ const apis = [
icon: BotMessageSquareIcon, icon: BotMessageSquareIcon,
apiroute: "/api/ai/chat/[slug]", apiroute: "/api/ai/chat/[slug]",
name: "A Chating API", name: "A Chating API",
content: "This is for the desktop app & talk about news articles. Using Groq's free tier.", content:
"This is for the desktop app & talk about news articles. Using Groq's free tier.",
caching: false, caching: false,
openAccess: false, openAccess: false,
}, },
@ -56,14 +66,15 @@ const apis = [
</script> </script>
<template> <template>
<div class="h-4"></div> <div class="h-4"></div>
<div <div class="justify-center align-center text-center flex flex-col">
class="justify-center align-center text-center flex flex-col"
>
<h1 class="text-4xl text-bold m-2">APIs</h1> <h1 class="text-4xl text-bold m-2">APIs</h1>
<div class="items flex flex-row flex-wrap gap-2 justify-center align-center"> <div
class="items flex flex-row flex-wrap gap-2 justify-center align-center"
>
<div <div
class="px-10 bg-gray-900/70 w-[400px] h-[300px] group rounded-xl hover:-translate-y-1 shadow-lg hover:shadow-sky-600/90 backdrop-blur-sm border border-gray-800 hover:border-sky-600/70 transition-all duration-700 justify-center align-middle flex flex-col text-left" class="px-10 bg-gray-900/70 w-[400px] h-[300px] group rounded-xl hover:-translate-y-1 shadow-lg hover:shadow-sky-600/90 backdrop-blur-sm border border-gray-800 hover:border-sky-600/70 transition-all duration-700 justify-center align-middle flex flex-col text-left"
v-for="item in apis"> v-for="item in apis"
>
<component <component
:is="item.icon" :is="item.icon"
class="w-8 h-8 text-white group-hover:text-sky-500 transition-colors duration-300" class="w-8 h-8 text-white group-hover:text-sky-500 transition-colors duration-300"
@ -72,13 +83,27 @@ const apis = [
<h2>API: {{ item.apiroute }}</h2> <h2>API: {{ item.apiroute }}</h2>
<p class="text-sm">{{ item.content || "N/A" }}</p> <p class="text-sm">{{ item.content || "N/A" }}</p>
<div class="gap-0 m-1"> <div class="gap-0 m-1">
<div class="text-md flex flex-row gap-2 text-center p-2">Caching: <div class="text-md flex flex-row gap-2 text-center p-2">
<BadgeCheckIcon v-if="item.caching" class="w-7 h-7 p-1 group-hover:text-green-300 transition-all duration-200"/> Caching:
<BadgeXIcon v-else class="w-7 h-7 p-1 group-hover:text-red-400 transition-all duration-200"/> <BadgeCheckIcon
v-if="item.caching"
class="w-7 h-7 p-1 group-hover:text-green-300 transition-all duration-200"
/>
<BadgeXIcon
v-else
class="w-7 h-7 p-1 group-hover:text-red-400 transition-all duration-200"
/>
</div> </div>
<div class="text-md flex flex-row gap-2 text-center p-2">Open Access: <div class="text-md flex flex-row gap-2 text-center p-2">
<BadgeCheckIcon v-if="item.caching" class="w-7 h-7 p-1 group-hover:text-green-300 transition-all duration-200"/> Open Access:
<BadgeXIcon v-else class="w-7 h-7 p-1 group-hover:text-red-400 transition-all duration-200"/> <BadgeCheckIcon
v-if="item.caching"
class="w-7 h-7 p-1 group-hover:text-green-300 transition-all duration-200"
/>
<BadgeXIcon
v-else
class="w-7 h-7 p-1 group-hover:text-red-400 transition-all duration-200"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,48 @@
import * as cheerio from "cheerio";
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, "slug");
const buildUrl = "https://today.line.me/tw/v3/publisher/" + slug;
try {
const req = await fetch(buildUrl, {
headers: {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
Accept: "*",
"Accept-Language": "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
Connection: "keep-alive",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
"Cache-Control": "max-age=0",
},
});
const data = await req.text();
const html = cheerio.load(data);
const newsOrgName = html("div.profileHead")
.text()
.replace(/.css-.*\}/, "");
const description = html("p.description").text();
const logo =
html("div.editor div figure img").attr("srcset") ||
html("div.editor div figure img").attr("src") ||
"";
const articles = [];
const otherArticles = html("section.moduleContainer div");
for (const item in otherArticles) {
}
return {
name: newsOrgName,
description: description,
logo: logo,
articles: []
};
} catch (e) {
console.log(e);
return {
error: "SERVER_SIDE_ERROR",
};
}
});

View File

@ -89,6 +89,13 @@ async function lineToday(slug: string) {
if (publishMatch) { if (publishMatch) {
publishedAt = findTime(publishMatch[1].trim()); publishedAt = findTime(publishMatch[1].trim());
} }
const findPublisherUrl =
html("a.entityPublishInfo-avatarLink").attr("href") || "";
const publisherIdMatch = findPublisherUrl.match(/[0-9]{6}/);
const publisherId = publisherIdMatch ? publisherIdMatch[0] : "";
console.log(publisherId);
return { return {
title: title, title: title,
paragraph: paragraph, paragraph: paragraph,
@ -97,6 +104,7 @@ async function lineToday(slug: string) {
images: images, images: images,
updateat: updatedAt, updateat: updatedAt,
publishedat: publishedAt, publishedat: publishedAt,
publisherId: publisherId,
}; };
} }