mirror of
https://github.com/hpware/news-analyze.git
synced 2025-06-23 15:51:01 +08:00
Make a poc for the api for line today & made some docs for myself and
others so others also can use the api
This commit is contained in:
parent
1680945186
commit
ba1b3afa6f
68
about/scraping_line_today_home.md
Normal file
68
about/scraping_line_today_home.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Scraping line today home
|
||||
|
||||
This took me some time, but they use a fancy system for pulling news data.
|
||||
|
||||
## Main endpoint
|
||||
For local Taiwan news they use this url: https://today.line.me/_next/data/v1/tw/v3/tab/domestic.json?tabs=domestic
|
||||
|
||||
From the _next? I thought that is static? I mean it maybe is, it is just providing with the URLs that the client will be fetching to the server, which is a bit fun.
|
||||
|
||||
Here is a JSON snippet:
|
||||
```json
|
||||
{
|
||||
"id": "682b0cef1b1269f8dec93e60",
|
||||
"type": "HIGHLIGHT",
|
||||
"containerStyle": "Header",
|
||||
"name": "國內話題:新北重大車禍",
|
||||
"source": "LISTING",
|
||||
"header": {
|
||||
"title": "新北重大死傷車禍",
|
||||
"hasCompositeTitle": false,
|
||||
"subTitle": "一輛小客車19日下午撞上放學人群,造成多名學童、大人送醫,至少3死10多傷,肇事的78歲男子當場昏迷。"
|
||||
},
|
||||
"listings": [
|
||||
{
|
||||
"id": "1feef7d2-3acc-495d-becd-3ef4de6a92ce",
|
||||
"offset": 0,
|
||||
"length": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
We can ignore everything else, other than the strange UUID in the json. Well, this is the key we need to fetch their api 🤩
|
||||
|
||||
## Fetch news URLs
|
||||
|
||||
Here is the fancy URL:
|
||||
`https://today.line.me/api/v6/listings/{the-uuid-you-got-in-the-listings-json-file}/?country=tw&offset=0&length=24`
|
||||
|
||||
This api can be used for fetching the news from them, however, there is an issue, the max length is only just 24 (yes, I tried it only can return 24 when requesting for 1000)
|
||||
|
||||
|
||||
And viewing the JSON, oh would you look at that.
|
||||
```JSON
|
||||
{
|
||||
"id": "262862833",
|
||||
"title": "派駐芬蘭遭白委扯焦慮症 林昶佐現身喊話",
|
||||
"publisher": "太報",
|
||||
"publisherId": "101366",
|
||||
"publishTimeUnix": 1747670221000,
|
||||
"contentType": "GENERAL",
|
||||
"thumbnail": {
|
||||
"type": "IMAGE",
|
||||
"hash": "0hvoq7de5NKUBMTjcMHM5WF3QYJTF_KDNJbixudj4ddXJnYm4ecX16Iz0edWwydjsTbH9vdm5IJ3EyKjtBeA"
|
||||
},
|
||||
"url": {
|
||||
"hash": "8nlkYeV"
|
||||
},
|
||||
"categoryId": 100262,
|
||||
"categoryName": "國內",
|
||||
"shortDescription": "前立委林昶佐(右二)將出任駐芬蘭代表,民眾黨立委林憶君卻質疑罹患焦慮症不適合去北歐。翻攝畫面前立委林昶佐將接任駐芬蘭代表,民眾黨立委林憶君今(5/19)質詢指出,林林昶佐曾患焦慮症,北歐國家日常短,病症容易發作,質疑是否適合。林昶佐晚間現身直播節目,向病友喊話,要對自己有信心,「絕對可以回復到正常生活,包括工作」。林憶君指出,1990年芬蘭是全球自殺率最高國家,而且北歐國家的日照很短,病症容易發作..."
|
||||
},
|
||||
```
|
||||
The url hash is just what we needed to use my scraper :D
|
||||
|
||||
You can query it by using: https://news.yuanhau.com/api/news/get/lt/8nlkYeV (Also videos are in the list, so avoid that) or just try this https://today.line.me/tw/v2/article/8nlkYeV
|
||||
|
||||
and that's it, I've bypassed Line's attempt to block people like me. :)
|
5
bun.lock
5
bun.lock
@ -19,6 +19,7 @@
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"argon2": "^0.43.0",
|
||||
"axios": "^1.9.0",
|
||||
"bootstrap-icons": "^1.12.1",
|
||||
"cheerio": "^1.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@ -850,6 +851,8 @@
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
|
||||
|
||||
"axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="],
|
||||
|
||||
"b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
@ -1262,6 +1265,8 @@
|
||||
|
||||
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
|
||||
|
59
components/app/popup.vue
Normal file
59
components/app/popup.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
const title = computed(() => props.title || "Popup Window");
|
||||
|
||||
const dragging = ref(false);
|
||||
const position = ref({
|
||||
x: Math.floor(window.innerWidth / 2 - parseInt(props.width || "400") / 2),
|
||||
y: Math.floor(window.innerHeight / 2 - parseInt(props.height || "200") / 2),
|
||||
});
|
||||
|
||||
const offset = ref({ x: 0, y: 0 });
|
||||
|
||||
const doDrag = useThrottleFn((e: MouseEvent) => {
|
||||
if (!dragging.value) return;
|
||||
requestAnimationFrame(() => {
|
||||
position.value = {
|
||||
x: Math.max(
|
||||
0,
|
||||
Math.min(window.innerWidth - 400, e.clientX - offset.value.x),
|
||||
),
|
||||
y: Math.max(
|
||||
0,
|
||||
Math.min(window.innerHeight - 300, e.clientY - offset.value.y),
|
||||
),
|
||||
};
|
||||
});
|
||||
}, 16);
|
||||
|
||||
const startDrag = (e: MouseEvent) => {
|
||||
dragging.value = true;
|
||||
offset.value = {
|
||||
x: e.clientX - position.value.x,
|
||||
y: e.clientY - position.value.y,
|
||||
};
|
||||
document.addEventListener("mousemove", doDrag);
|
||||
document.addEventListener("mouseup", stopDrag);
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
dragging.value = false;
|
||||
document.removeEventListener("mousemove", doDrag);
|
||||
document.removeEventListener("mouseup", stopDrag);
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col rounded-xl gray-500/80 backdrop-blur-sm">
|
||||
<div
|
||||
class="flex flex-row absolute inset-x-0 top-0 h-8 bg-gray-600/80"
|
||||
></div>
|
||||
<div class="">Yo a popup!</div>
|
||||
<button @click="">OK!</button>
|
||||
</div>
|
||||
</template>
|
@ -79,7 +79,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
<template>
|
||||
<blurPageBeforeLogin>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex flex-col h-[100%] w-full">
|
||||
<div>
|
||||
<div
|
||||
class="justify-center align-center text-center flex flex-col sticky top-0 pt-2 min-h-0 border rounded-2xl gray-500/80 backdrop-blur-sm"
|
||||
@ -110,7 +110,7 @@ onMounted(async () => {
|
||||
>
|
||||
<div
|
||||
v-for="message in messages"
|
||||
class="max-w-[80%] rounded-lg p-3 bg-gray-300/70 rounded-xl"
|
||||
class="max-w-[80%] p-3 bg-gray-300/70 rounded-xl"
|
||||
>
|
||||
<div v-if="message.user" class="flex flex-row gap-2">
|
||||
<User class="w-5 h-5" />{{ message.message }}
|
||||
@ -120,8 +120,9 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-[75px]"></div>
|
||||
<div
|
||||
class="space-x-2 sticky bottom-0 border-t p-2 min-h-0 border rounded-xl gray-500/80 backdrop-blur-sm"
|
||||
class="space-x-2 absolute bottom-2 inset-x-4 border-t p-2 min-h-0 border rounded-xl gray-500/80 backdrop-blur-sm"
|
||||
>
|
||||
<div class="text-black w-full flex flex-row">
|
||||
<Input
|
||||
|
@ -5,7 +5,10 @@ const { data: favData, error, pending } = useFetch("/api/user/fav", {});
|
||||
</script>
|
||||
<template>
|
||||
<BlurPageBeforeLogin>
|
||||
<div>{{ favData }}</div>
|
||||
<!--Testing only!!!-->
|
||||
<div>
|
||||
<div v-for="items in favData.items">
|
||||
{{ items }}
|
||||
</div>
|
||||
</div>
|
||||
</BlurPageBeforeLogin>
|
||||
</template>
|
||||
|
@ -28,6 +28,7 @@
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"argon2": "^0.43.0",
|
||||
"axios": "^1.9.0",
|
||||
"bootstrap-icons": "^1.12.1",
|
||||
"cheerio": "^1.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
@ -471,7 +471,9 @@ watchEffect((cleanupFn) => {
|
||||
v-else
|
||||
>
|
||||
<!--Menu container-->
|
||||
<div class="flex flex-row g-2 text-gray-400 z-9999 selection:opacity-0">
|
||||
<div
|
||||
class="flex flex-row g-2 rounded-xl gray-500/80 backdrop-blur-sm z-9999 selection:opacity-0"
|
||||
>
|
||||
<button
|
||||
@click="toggleMenu"
|
||||
class="w-8 h-8 text-white hover:text-blue-500 transition-all duration-100 flex flex-row"
|
||||
|
@ -154,7 +154,7 @@ useSeoMeta({
|
||||
id="cards"
|
||||
>
|
||||
<div
|
||||
class="px-10 bg-gray-900/70 w-[300px] h-[200px] group rounded-xl shadow-lg hover:shadow-sky-600/90 backdrop-blur-sm border border-gray-800 hover:border-gray-600/70 transition-all duration-700 justify-center align-middle flex flex-col"
|
||||
class="px-10 bg-gray-900/70 w-[300px] h-[200px] group rounded-xl shadow-lg hover:shadow-sky-600/90 hover:-translate-y-3 backdrop-blur-sm border border-gray-800 hover:border-gray-600/70 transition-all duration-700 justify-center align-middle flex flex-col"
|
||||
v-for="item in cards"
|
||||
:key="item.title"
|
||||
>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
const localePath = useLocalePath();
|
||||
// Import Icons
|
||||
import { SearchXIcon } from "lucide-vue-next";
|
||||
import { SearchXIcon, CircleSlash2Icon } from "lucide-vue-next";
|
||||
// Array
|
||||
const tools = [
|
||||
{
|
||||
@ -10,6 +10,12 @@ const tools = [
|
||||
icon: SearchXIcon,
|
||||
go: localePath("/tools/checkweirdkeywords"),
|
||||
},
|
||||
{
|
||||
name: "無廣告新聞",
|
||||
content: "提供無廣告的LINE Today 新聞",
|
||||
icon: CircleSlash2Icon,
|
||||
go: localePath("/tools/freelinetoday"),
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
@ -22,7 +28,7 @@ const tools = [
|
||||
>
|
||||
<NuxtLink :to="item.go" v-for="item in tools">
|
||||
<div
|
||||
class="px-10 bg-gray-900/70 w-[300px] h-[200px] group rounded-xl shadow-lg hover:shadow-sky-700/90 backdrop-blur-sm border border-gray-800 hover:border-gray-600/70 transition-all duration-700 justify-center align-middle flex flex-col"
|
||||
class="px-10 bg-gray-900/70 w-[300px] h-[200px] group rounded-xl shadow-lg hover:shadow-sky-700/90 hover:-translate-y-3 backdrop-blur-sm border border-gray-800 hover:border-gray-600/70 transition-all duration-700 justify-center align-middle flex flex-col"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
|
0
public/datainfo/linetodayjsondata.json
Normal file
0
public/datainfo/linetodayjsondata.json
Normal file
23
server/fetchapi/lt_home.ts
Normal file
23
server/fetchapi/lt_home.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Check /about/scraping_line_today_home.md for more info or https://news.yuanhau.com/datainfo/linetodayjsondata.json
|
||||
async function getLineTodayData(type: string) {
|
||||
try {
|
||||
const buildUrl = `https://today.line.me/_next/data/v1/tw/v3/tab/${type}.json?tabs=${type}`;
|
||||
const req = await fetch(buildUrl, {
|
||||
headers: {
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
Accept: "application/json",
|
||||
"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",
|
||||
},
|
||||
});
|
||||
const res = await req.json();
|
||||
const data = res.getPageData?.[type];
|
||||
return res;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(await getLineTodayData("domestic"));
|
||||
|
||||
//export default getLineTodayData;
|
35
server/fetchapi/run.txt
Normal file
35
server/fetchapi/run.txt
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
[0mpageProps[2m:[0m {
|
||||
[0m__lang[2m:[0m [0m[32m[0m[32m"tw"[0m[0m[0m[2m,[0m
|
||||
[0m__namespaces[2m:[0m {
|
||||
[0mcommon[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0mforYou[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0mvideo[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0mquiz[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0mpoll[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
}[0m[2m,[0m
|
||||
[0mruntimeConfig[2m:[0m {
|
||||
[0mcountryConfig[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0mCOMMIT_SHA[2m:[0m [0m[32m[0m[32m"fb546888"[0m[0m[0m[2m,[0m
|
||||
[0mDEPLOY_ENV[2m:[0m [0m[32m[0m[32m"release"[0m[0m[0m[2m,[0m
|
||||
[0mSENTRY_DSN[2m:[0m [0m[32m[0m[32m"https://8ca685e4ca2f4b2ba32b4459381f91b8@sentry-uit.line-apps.com/475"[0m[0m[0m[2m,[0m
|
||||
}[0m[2m,[0m
|
||||
[0m_sentryTraceData[2m:[0m [0m[32m[0m[32m"5c81d62ef22d861e5555ee0e170bd82b-a6d22f47579e751a-1"[0m[0m[0m[2m,[0m
|
||||
[0m_sentryBaggage[2m:[0m [0m[32m[0m[32m"sentry-environment=release,sentry-release=release%40fb546888,sentry-public_key=8ca685e4ca2f4b2ba32b4459381f91b8,sentry-trace_id=5c81d62ef22d861e5555ee0e170bd82b,sentry-transaction=getServerSideProps%20%2Fv3%2Ftab%2F%5B%5B...tabs%5D%5D,sentry-sampled=true"[0m[0m[0m[2m,[0m
|
||||
[0marticleDedupeState[2m:[0m {
|
||||
[0mpool[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0mserverPool[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
}[0m[2m,[0m
|
||||
[0mfallback[2m:[0m {
|
||||
[0mgetTabsData[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0m[32m"getPageData,domestic"[0m[2m:[0m [0m[36m[Object ...][0m[0m[2m,[0m
|
||||
[0m[32m"5e4ceeda408f3c5ca031940d_{\"offset\":5,\"length\":3}"[0m[2m:[0m [
|
||||
[0m[36m[Object ...][0m[0m[2m,[0m [0m[36m[Object ...][0m[0m[2m,[0m [0m[36m[Object ...][0m
|
||||
][0m[2m,[0m
|
||||
[0m[32m"5e4cee49408f3c5ca031940b_{\"offset\":0,\"length\":5}"[0m[2m:[0m [
|
||||
[0m[36m[Object ...][0m[0m[2m,[0m [0m[36m[Object ...][0m[0m[2m,[0m [0m[36m[Object ...][0m[0m[2m,[0m [0m[36m[Object ...][0m[0m[2m,[0m [0m[36m[Object ...][0m
|
||||
][0m[2m,[0m
|
||||
}[0m[2m,[0m
|
||||
}[0m[2m,[0m
|
||||
[0m__N_SSP[2m:[0m [0m[33mtrue[0m[0m[2m,[0m
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user