feat: update README and various components for improved formatting and error handling; enhance login functionality with password encryption

This commit is contained in:
吳元皓 2025-05-12 10:17:21 +08:00
parent ee13960f0b
commit fd2ba525f9
12 changed files with 304 additions and 168 deletions

View File

@ -22,6 +22,7 @@ App Design: [Freeform](https://www.icloud.com/freeform/026AxB798cViZ9jJ2DkNsXUCQ
你會看到許多觀點,但不知道這些新聞為什麼會寫比較偏見的文章。
## Inspired by
- puter.com
- Perplexity
- Ground.news
@ -86,4 +87,5 @@ App Design: [Freeform](https://www.icloud.com/freeform/026AxB798cViZ9jJ2DkNsXUCQ
7. Open `http://localhost:3000` in your browser.
### For scaping
First, Run `ps1 clone-env.ps1` or `bash clone-env.sh` to clone the `.env` file to the `scraping` folder, then cd into the `scraping` folder. Run `python main.py` to start scraping in Google News.

View File

@ -1,53 +1,53 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted } from "vue";
const props = defineProps<{
title: string
initialX?: number
initialY?: number
width?: string
height?: string
}>()
title: string;
initialX?: number;
initialY?: number;
width?: string;
height?: string;
}>();
const emit = defineEmits(['close'])
const emit = defineEmits(["close"]);
const isDragging = ref(false)
const isDragging = ref(false);
const position = ref({
x: props.initialX || Math.floor(window.innerWidth / 2) - Math.random() * 200,
y: props.initialY || Math.floor(window.innerHeight / 2) - Math.random() * 10
})
const offset = ref({ x: 0, y: 0 })
y: props.initialY || Math.floor(window.innerHeight / 2) - Math.random() * 10,
});
const offset = ref({ x: 0, y: 0 });
const startDrag = (e: MouseEvent) => {
isDragging.value = true
isDragging.value = true;
offset.value = {
x: e.clientX - position.value.x,
y: e.clientY - position.value.y
}
}
y: e.clientY - position.value.y,
};
};
const doDrag = (e: MouseEvent) => {
if (isDragging.value) {
position.value = {
x: e.clientX - offset.value.x,
y: e.clientY - offset.value.y
}
}
y: e.clientY - offset.value.y,
};
}
};
const stopDrag = () => {
isDragging.value = false
}
isDragging.value = false;
};
onMounted(() => {
document.addEventListener('mousemove', doDrag)
document.addEventListener('mouseup', stopDrag)
})
document.addEventListener("mousemove", doDrag);
document.addEventListener("mouseup", stopDrag);
});
onUnmounted(() => {
document.removeEventListener('mousemove', doDrag)
document.removeEventListener('mouseup', stopDrag)
})
document.removeEventListener("mousemove", doDrag);
document.removeEventListener("mouseup", stopDrag);
});
</script>
<template>
@ -56,7 +56,7 @@ onUnmounted(() => {
left: `${position.x}px`,
top: `${position.y}px`,
width: props.width || '400px',
height: props.height || '300px'
height: props.height || '300px',
}"
class="fixed bg-white dark:bg-gray-800 rounded-md shadow-lg overflow-hidden flex flex-col"
>

View File

@ -1,14 +1,50 @@
<script setup lamng="ts">
const userAccount = ref("");
const userPassword = ref("");
const crypto = Crypto;
const submitUserPassword = async () => {
// Encrypt password during transit
const sha512Passwordify = crypto.createHash("sha512");
sha512Passwordify.update(userPassword.value);
const sha512Password = sha512Passwordify.digest("hex");
// Log user & password
console.log(userAccount.value);
console.log(sha512Password);
// Send data.
const sendData = fetch("/api/user/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: userAccount.value,
password: sha512Password,
}),
})
}
</script>
<template>
<div class="flex flex-col items-center justify-center h-full">
<form class="flex flex-col items-center justify-center h-full">
<div
class="flex flex-col items-center justify-center h-full"
>
<div class="text-xl mb-4 text-bold">Login / Register</div>
<input type="text" placeholder="Username" class="mb-2 p-2 border rounded" />
<input type="password" placeholder="Password" class="p-2 border rounded mb-2" />
<button class="bg-black text-white p-2 rounded transition duration-200">
<input
type="text"
placeholder="Username"
class="mb-2 p-2 border rounded"
/>
<input
type="password"
placeholder="Password"
class="p-2 border rounded mb-2"
/>
<button class="bg-black text-white p-2 rounded transition duration-200" @click="submitUserPassword">
Log In
</button>
</form>
</div>
</div>
</template>

View File

@ -1,4 +1,4 @@
version: '3.8'
version: "3.8"
services:
newsanalyze-service:

View File

@ -4,9 +4,16 @@ const { t } = useI18n();
</script>
<template>
<NuxtLayout>
<div class="flex flex-col justify-center align-center text-center absolute inset-0 h-screen">
<span class="text-7xl m-4 m-1 mb-0 text-center align-center justify-center">{{ error.statusCode }}</span>
<span class="text-2xl text-center align-center justify-center">{{ error.statusMessage }}</span>
<div
class="flex flex-col justify-center align-center text-center absolute inset-0 h-screen"
>
<span
class="text-7xl m-4 m-1 mb-0 text-center align-center justify-center"
>{{ error.statusCode }}</span
>
<span class="text-2xl text-center align-center justify-center">{{
error.statusMessage
}}</span>
</div>
<div class="h-screen"></div>
</NuxtLayout>

View File

@ -1,4 +1,3 @@
<script setup lang="ts">
// No layout
@ -28,7 +27,12 @@ import ProgressComponent from "~/components/ui/progress/Progress.vue";
import HoverCardComponent from "~/components/ui/hover-card/HoverCard.vue";
// Icons
import { ComputerDesktopIcon, UserIcon, LanguageIcon, ChevronRightIcon } from "@heroicons/vue/24/outline";
import {
ComputerDesktopIcon,
UserIcon,
LanguageIcon,
ChevronRightIcon,
} from "@heroicons/vue/24/outline";
// i18n
const { t, locale, locales } = useI18n();
@ -80,28 +84,28 @@ const openWindow = (windowName?: string) => {
}
console.log(windowName);
menuOpen.value = false;
}
};
const unMinWindow = (windowName?: string) => {
console.log(windowName);
}
};
// menus
const menuItems = [
{ name: "Hot News", windowName: "hotnews" },
{ name: "News", windowName: "news" },
{ name: "Sources", windowName: "sources" },
{ name: 'About This Website', windowName: "about"},
{ name: 'Settings', windowName: "settings"},
{ name: 'Leave', windowName: "leave"},
]
{ name: "About This Website", windowName: "about" },
{ name: "Settings", windowName: "settings" },
{ name: "Leave", windowName: "leave" },
];
const toggleMenu = () => {
menuOpen.value = !menuOpen.value
}
menuOpen.value = !menuOpen.value;
};
// Lang Menu
const toggleLangMenu = () => {
langMenuOpen.value = !langMenuOpen.value
}
langMenuOpen.value = !langMenuOpen.value;
};
</script>
<template>
<div
@ -109,23 +113,38 @@ const toggleLangMenu = () => {
>
<!--Menu container-->
<div class="flex flex-row g-2 text-gray-400 text-white z-9999">
<button @click="toggleMenu" class="w-8 h-8 text-white hover:text-blue-500 transition-all duration-100 flex flex-row">
<button
@click="toggleMenu"
class="w-8 h-8 text-white hover:text-blue-500 transition-all duration-100 flex flex-row"
>
<ComputerDesktopIcon />
</button>
<span class="ml-1 mr-2 text-[20px]">|</span>
<!--navbar icons for min and max application window-->
<button class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100">
</button>
<div v-for="item in currentNavBar" :key="item.name" class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-100 px-4 py-2 cursor-pointer">
<button @click="unMinWindow(item.windowAssociated)" class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100">
<button
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
></button>
<div
v-for="item in currentNavBar"
:key="item.name"
class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-100 px-4 py-2 cursor-pointer"
>
<button
@click="unMinWindow(item.windowAssociated)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
<span>{{ item.name }}</span>
<span v-if="item.flash" class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
<span v-if="item.icon" :class="item.icon">
</span>
<span
v-if="item.flash"
class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"
></span>
<span v-if="item.icon" :class="item.icon"> </span>
</button>
</div>
</div>
<div class="text-center align-middle justify-center text-white">{{ currentDate }}</div>
<div class="text-center align-middle justify-center text-white">
{{ currentDate }}
</div>
</div>
<div class="w-full h-[2.5em]"></div>
<!--Menu-->
@ -133,9 +152,15 @@ const toggleLangMenu = () => {
enter-active-class="animate__animated animate__fadeInDown animate_fast03"
leave-active-class="animate__animated animate__fadeOutUp animate_fast03"
>
<div class="m-2 p-2 bg-gray-800 shadow-lg w-fit rounded-[10px] v-9998" v-if="menuOpen">
<div
class="m-2 p-2 bg-gray-800 shadow-lg w-fit rounded-[10px] v-9998"
v-if="menuOpen"
>
<div v-for="item in menuItems" :key="item.name" class="">
<button @click="openWindow(item.windowName)" class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100">
<button
@click="openWindow(item.windowName)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
<span>{{ item.name }}</span>
<ChevronRightIcon class="w-4 h-4 justify-center align-center" />
</button>
@ -146,8 +171,7 @@ const toggleLangMenu = () => {
<div
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-1]"
id="desktop"
>
</div>
></div>
<slot />
<!--Footer-->
<div
@ -156,8 +180,12 @@ const toggleLangMenu = () => {
<div class="">
<!--Lang-->
<span>Lang: </span>
<span class="text-lg">{{ t("localeflag") }}</span>&nbsp;
<button class="w-4 h-4 hover:text-blue-200 transition-all duration-100" @click="toggleLangMenu">
<span class="text-lg">{{ t("localeflag") }}</span
>&nbsp;
<button
class="w-4 h-4 hover:text-blue-200 transition-all duration-100"
@click="toggleLangMenu"
>
<LanguageIcon />
</button>
</div>
@ -170,8 +198,13 @@ const toggleLangMenu = () => {
<span class="text-sm">{{ new Date().getFullYear() }} &copy yh</span>
</div>
<div class="">
<button @click="openWindow('login')" class="w-8 h-8 text-gray-400 flex flex-row">
<UserIcon class="w-8 h-8 text-gray-400 hover:text-blue-500 transition-all duration-100" />
<button
@click="openWindow('login')"
class="w-8 h-8 text-gray-400 flex flex-row"
>
<UserIcon
class="w-8 h-8 text-gray-400 hover:text-blue-500 transition-all duration-100"
/>
</button>
</div>
</div>

View File

@ -9,9 +9,15 @@ export default defineNuxtConfig({
"/go/**": { ssr: true },
"/find/**": { ssr: true },
// Send ZIP bombs to troll bots
"/wp-admin/**": { redirect: "https://s3.yhw.tw/data/def-zip-bomb/wp-admin.php.zip" },
"/xmlrpc.php": { redirect: "https://s3.yhw.tw/data/def-zip-bomb/xmlrpc.php.zip" },
"/wp-login.php": { redirect: "https://s3.yhw.tw/data/def-zip-bomb/wp-login.php.zip" },
"/wp-admin/**": {
redirect: "https://s3.yhw.tw/data/def-zip-bomb/wp-admin.php.zip",
},
"/xmlrpc.php": {
redirect: "https://s3.yhw.tw/data/def-zip-bomb/xmlrpc.php.zip",
},
"/wp-login.php": {
redirect: "https://s3.yhw.tw/data/def-zip-bomb/wp-login.php.zip",
},
},
css: ["~/styles/main.css"],

View File

@ -1,4 +1,3 @@
<script setup lang="ts">
// No layout
definePageMeta({
@ -38,9 +37,13 @@ import DialogComponent from "~/components/ui/dialog/Dialog.vue";
import ProgressComponent from "~/components/ui/progress/Progress.vue";
import HoverCardComponent from "~/components/ui/hover-card/HoverCard.vue";
// Icons
import { ComputerDesktopIcon, UserIcon, LanguageIcon, ChevronRightIcon } from "@heroicons/vue/24/outline";
import {
ComputerDesktopIcon,
UserIcon,
LanguageIcon,
ChevronRightIcon,
} from "@heroicons/vue/24/outline";
// i18n
const { t, locale, locales } = useI18n();
@ -94,28 +97,28 @@ const openWindow = (windowName?: string) => {
}
console.log(windowName);
menuOpen.value = false;
}
};
const unMinWindow = (windowName?: string) => {
console.log(windowName);
}
};
// menus
const menuItems = [
{ name: "Hot News", windowName: "hotnews" },
{ name: "News", windowName: "news" },
{ name: "Sources", windowName: "sources" },
{ name: 'About This Website', windowName: "about"},
{ name: 'Settings', windowName: "settings"},
{ name: 'Leave', windowName: "leave"},
]
{ name: "About This Website", windowName: "about" },
{ name: "Settings", windowName: "settings" },
{ name: "Leave", windowName: "leave" },
];
const toggleMenu = () => {
menuOpen.value = !menuOpen.value
}
menuOpen.value = !menuOpen.value;
};
// Lang Menu
const toggleLangMenu = () => {
langMenuOpen.value = !langMenuOpen.value
}
langMenuOpen.value = !langMenuOpen.value;
};
// values
const activeWindows = ref<associAppWindowInterface>([]);
@ -123,7 +126,9 @@ const activeWindows = ref<associAppWindowInterface>([]);
// ?opemapp= component
const openApp = ref(false);
const openAppId = ref();
watch(() => route.query.openapp, (newVal) => {
watch(
() => route.query.openapp,
(newVal) => {
if (newVal) {
openApp.value = true;
openAppId.value = newVal;
@ -133,17 +138,28 @@ watch(() => route.query.openapp, (newVal) => {
query: {},
});
}
});
},
);
const associAppWindow = [
{ name: "hotnews", id: "1", title: "Hot News", component: HotNewsWindow, width: "700px", height: "500px" },
{
name: "hotnews",
id: "1",
title: "Hot News",
component: HotNewsWindow,
width: "700px",
height: "500px",
},
{ name: "login", id: "2", title: "Login", component: LoginWindow },
]
];
const findAndOpenWindow = (windowName: string) => {
const app = associAppWindow.find((app) => app.name === windowName);
// Prevent dual logins
if (windowName === "login" && activeWindows.value.some((window) => window.name === "login")) {
if (
windowName === "login" &&
activeWindows.value.some((window) => window.name === "login")
) {
return;
}
if (app) {
@ -161,7 +177,9 @@ const findAndOpenWindow = (windowName: string) => {
};
const closeWindow = (windowId: string) => {
activeWindows.value = activeWindows.value.filter((window) => window.id !== windowId);
activeWindows.value = activeWindows.value.filter(
(window) => window.id !== windowId,
);
console.log("activeWindows.value", activeWindows.value);
};
</script>
@ -171,25 +189,38 @@ const closeWindow = (windowId: string) => {
>
<!--Menu container-->
<div class="flex flex-row g-2 text-gray-400 text-white z-9999">
<button @click="toggleMenu" class="w-8 h-8 text-white hover:text-blue-500 transition-all duration-100 flex flex-row">
<button
@click="toggleMenu"
class="w-8 h-8 text-white hover:text-blue-500 transition-all duration-100 flex flex-row"
>
<ComputerDesktopIcon />
</button>
<span class="ml-1 mr-2 text-[20px]">|</span>
<!--navbar icons for min and max application window-->
<button
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
></button>
<div
v-for="item in currentNavBar"
:key="item.name"
class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-100 px-4 py-2 cursor-pointer"
>
<button
@click="unMinWindow(item.windowAssociated)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
</button>
<div v-for="item in currentNavBar" :key="item.name" class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-100 px-4 py-2 cursor-pointer">
<button @click="unMinWindow(item.windowAssociated)" class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100">
<span>{{ item.name }}</span>
<span v-if="item.flash" class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
<span v-if="item.icon" :class="item.icon">
</span>
<span
v-if="item.flash"
class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"
></span>
<span v-if="item.icon" :class="item.icon"> </span>
</button>
</div>
</div>
<div class="text-center align-middle justify-center text-white">{{ currentDate }}</div>
<div class="text-center align-middle justify-center text-white">
{{ currentDate }}
</div>
</div>
<div class="w-full h-[2.5em]"></div>
<!--Menu-->
@ -197,9 +228,15 @@ const closeWindow = (windowId: string) => {
enter-active-class="animate__animated animate__fadeInDown animate_fast03"
leave-active-class="animate__animated animate__fadeOutUp animate_fast03"
>
<div class="m-2 p-2 bg-gray-800 shadow-lg w-fit rounded-[10px] v-9998" v-if="menuOpen">
<div
class="m-2 p-2 bg-gray-800 shadow-lg w-fit rounded-[10px] v-9998"
v-if="menuOpen"
>
<div v-for="item in menuItems" :key="item.name" class="">
<button @click="openWindow(item.windowName)" class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100">
<button
@click="openWindow(item.windowName)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
<span>{{ item.name }}</span>
<ChevronRightIcon class="w-4 h-4 justify-center align-center" />
</button>
@ -210,8 +247,7 @@ const closeWindow = (windowId: string) => {
<div
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-1]"
id="desktop"
>
</div>
></div>
<div>
<DraggableWindow
v-for="window in activeWindows"
@ -221,7 +257,10 @@ const closeWindow = (windowId: string) => {
:width="window.width"
:height="window.height"
>
<Component :is="window.component" @error="console.error('Error:', $event)" />
<Component
:is="window.component"
@error="console.error('Error:', $event)"
/>
</DraggableWindow>
</div>
<!--Footer-->
@ -231,8 +270,12 @@ const closeWindow = (windowId: string) => {
<div class="">
<!--Lang-->
<span>Lang: </span>
<span class="text-lg">{{ t("localeflag") }}</span>&nbsp;
<button class="w-4 h-4 hover:text-blue-200 transition-all duration-100" @click="toggleLangMenu">
<span class="text-lg">{{ t("localeflag") }}</span
>&nbsp;
<button
class="w-4 h-4 hover:text-blue-200 transition-all duration-100"
@click="toggleLangMenu"
>
<LanguageIcon />
</button>
</div>
@ -245,8 +288,13 @@ const closeWindow = (windowId: string) => {
<span class="text-sm">{{ new Date().getFullYear() }} &copy yh</span>
</div>
<div class="">
<button @click="openWindow('login')" class="w-8 h-8 text-gray-400 flex flex-row">
<UserIcon class="w-8 h-8 text-gray-400 hover:text-blue-500 transition-all duration-100" />
<button
@click="openWindow('login')"
class="w-8 h-8 text-gray-400 flex flex-row"
>
<UserIcon
class="w-8 h-8 text-gray-400 hover:text-blue-500 transition-all duration-100"
/>
</button>
</div>
</div>

View File

@ -1,3 +1 @@
<template>
目前沒有手機版本
</template>
<template>目前沒有手機版本</template>

View File

@ -1,25 +1,31 @@
# Status
## setn.py
Working
## tvbs.py
Working
## taisounds.py
Working
## cna.py
Broken
Error: `Error: 'utf-8' codec can't decode byte 0x83 in position 0: invalid start byte`
## chinatimes.py
Broken
Error: `Error: 'utf-8' codec can't decode byte 0xa3 in position 0: invalid start byte`
## twreporter.py
Broken
Error: `Error: 'utf-8' codec can't decode byte 0xc0 in position 2: invalid start byte`