Merge branch 'astro'

This commit is contained in:
ccbikai
2025-11-15 19:50:36 +08:00
15 changed files with 7455 additions and 4298 deletions

View File

@@ -1 +1 @@
v20
v22

25
AGENTS.md Normal file
View File

@@ -0,0 +1,25 @@
# Repository Guidelines
## Project Structure & Modules
The codebase is built on Astro 4. Routes live in `src/pages`, shared UI in `src/components`, layouts in `src/layouts`, and Telegram/RSS logic in `src/lib`. Styles and static assets sit under `src/styles` and `src/assets`. Everything placed in `public/` is served as-is by Astro. Helper utilities and automation scripts belong to `api/` and `scripts/`. Build artifacts are emitted to `dist/`; always clean up previous outputs before deploying to avoid stale files.
## Build, Test & Dev Commands
Always use `pnpm`. Run `pnpm install` to install dependencies and register simple-git-hooks when `.git` exists. `pnpm dev`/`pnpm start` launch hot reload on port 4321, `pnpm build` creates the deployable SSR bundle, `pnpm preview` validates the build in the production-like adapter, and `pnpm lint` / `pnpm lint:fix` check or auto-fix ESLint issues.
## Code Style & Naming
ESLint relies on `@antfu/eslint-config`, `eslint-plugin-astro`, and `eslint-plugin-format`, using two-space indents, single quotes, and automatic trailing commas per syntax. Name Astro components as `PascalCase.astro`, scripts and helpers as `kebab-case.ts`, environment variables with uppercase snake case, and keep route paths kebab-cased. Make sure `pnpm lint` is clean before committing, and add brief clarifying comments when Telegram API or RSS handling might confuse readers.
## Testing Guidance
There is no unit-test framework yet. Minimum validation is passing ESLint plus a manual `pnpm preview`. When adding modules, prefer injectable pure functions inside `src/lib` to ease future Vitest adoption. List covered user paths in the PR description (e.g., "channel without TAG" or "RSS Beautify disabled") and mention the commands you ran.
## PR & Commit Guidelines
Follow Conventional Commits (`feat:`, `fix:`, `refactor:`, etc.) and explain the reason and impact in the body. Each PR should include: 1) background or linked issue, 2) notes on config/env changes, 3) local validation output or screenshots, and 4) UI screenshots from `pnpm preview` when relevant. Keep PR scope focused; split into multiple commits if it simplifies rollbacks.
## Security & Config Notes
Deployments depend on `.env` settings such as `CHANNEL`, `LOCALE`, Sentry credentials, and social account URLs. Never commit real tokens—use `.env.example` or platform variables (Vercel/Cloudflare/etc.). To try different Telegram proxies, copy `.env` to `.env.local`; Astro will load it during `astro dev`. Cloudflare/Netlify/Vercel Node adapters live in `astro.config.mjs`, so confirm the target platform supports SSR before changing them.

1
CLAUDE.md Symbolic link
View File

@@ -0,0 +1 @@
AGENTS.md

View File

@@ -1,11 +1,11 @@
import process from 'node:process'
import { defineConfig } from 'astro/config'
import vercel from '@astrojs/vercel/serverless'
import cloudflare from '@astrojs/cloudflare'
import netlify from '@astrojs/netlify'
import node from '@astrojs/node'
import { provider } from 'std-env'
import vercel from '@astrojs/vercel'
import sentry from '@sentry/astro'
import { defineConfig } from 'astro/config'
import { provider } from 'std-env'
const providers = {
vercel: vercel({

View File

@@ -3,7 +3,6 @@ import antfu from '@antfu/eslint-config'
export default antfu({
formatters: true,
astro: true,
ignores: ['src/assets/style.css'],
rules: {
'no-console': ['error', { allow: ['info', 'warn', 'error'] }],
},

View File

@@ -3,7 +3,7 @@
"type": "module",
"version": "0.1.7",
"private": true,
"packageManager": "pnpm@9.9.0",
"packageManager": "pnpm@10.22.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
@@ -15,37 +15,38 @@
"postinstall": "test -d .git && simple-git-hooks || true"
},
"dependencies": {
"@astrojs/rss": "^4.0.7",
"@sentry/astro": "^8.27.0",
"@astrojs/rss": "^4.0.13",
"@sentry/astro": "^10.25.0",
"cheerio": "1.0.0-rc.12",
"dayjs": "^1.11.13",
"dayjs": "^1.11.19",
"flourite": "^1.3.0",
"lru-cache": "^11.0.0",
"ofetch": "^1.3.4",
"prismjs": "^1.29.0",
"lru-cache": "^11.2.2",
"ofetch": "^1.5.1",
"prismjs": "^1.30.0",
"prismjs-components-importer": "^0.2.0",
"sanitize-html": "^2.13.0"
"sanitize-html": "^2.17.0"
},
"devDependencies": {
"@antfu/eslint-config": "^3.0.0",
"@astrojs/cloudflare": "^11.0.4",
"@astrojs/netlify": "^5.5.1",
"@astrojs/node": "^8.3.3",
"@astrojs/vercel": "^7.8.0",
"@types/prismjs": "^1.26.4",
"astro": "^4.15.1",
"astro-eslint-parser": "^1.0.2",
"@antfu/eslint-config": "^6.2.0",
"@astrojs/cloudflare": "^12.6.10",
"@astrojs/netlify": "^6.6.0",
"@astrojs/node": "^9.5.0",
"@astrojs/vercel": "^9.0.0",
"@types/prismjs": "^1.26.5",
"astro": "^5.15.7",
"astro-eslint-parser": "^1.2.2",
"astro-seo": "^0.8.4",
"autoprefixer": "^10.4.20",
"cssnano": "^7.0.5",
"autoprefixer": "^10.4.22",
"cssnano": "^7.1.2",
"eslint": "9.5.0",
"eslint-plugin-astro": "^1.2.3",
"eslint-plugin-format": "^0.1.2",
"lint-staged": "^15.2.9",
"postcss-nesting": "^13.0.0",
"eslint-plugin-astro": "^1.5.0",
"eslint-plugin-format": "^1.0.2",
"lint-staged": "^16.2.6",
"postcss-nesting": "^13.0.2",
"prettier-plugin-astro": "^0.14.1",
"simple-git-hooks": "^2.11.1",
"std-env": "^3.7.0"
"simple-git-hooks": "^2.13.1",
"std-env": "^3.10.0",
"typescript": "^5.9.3"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"

11602
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

8
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,8 @@
onlyBuiltDependencies:
- '@parcel/watcher'
- '@sentry/cli'
- esbuild
- protobufjs
- sharp
- simple-git-hooks
- workerd

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 232.5-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20.2832" height="19.9316">
<g>
<rect height="19.9316" opacity="0" width="20.2832" x="0" y="0"/>
<path d="M19.9219 9.96094C19.9219 15.4004 15.4102 19.9219 9.96094 19.9219C4.52148 19.9219 0 15.4004 0 9.96094C0 4.51172 4.51172 0 9.95117 0C15.4004 0 19.9219 4.51172 19.9219 9.96094ZM12.998 6.08398L8.82812 12.7832L6.8457 10.2246C6.60156 9.90234 6.38672 9.81445 6.10352 9.81445C5.66406 9.81445 5.32227 10.1758 5.32227 10.6152C5.32227 10.8398 5.41016 11.0547 5.55664 11.25L8.00781 14.2578C8.26172 14.5996 8.53516 14.7363 8.86719 14.7363C9.19922 14.7363 9.48242 14.5801 9.6875 14.2578L14.2773 7.03125C14.3945 6.82617 14.5215 6.60156 14.5215 6.38672C14.5215 5.92773 14.1211 5.63477 13.6914 5.63477C13.4375 5.63477 13.1836 5.79102 12.998 6.08398Z" fill="#000000"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

11
src/assets/circle.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 175.5-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="19.9219" height="19.9316">
<g>
<rect height="19.9316" opacity="0" width="19.9219" x="0" y="0"/>
<path d="M9.96094 19.9219C15.4102 19.9219 19.9219 15.4004 19.9219 9.96094C19.9219 4.51172 15.4004 0 9.95117 0C4.51172 0 0 4.51172 0 9.96094C0 15.4004 4.52148 19.9219 9.96094 19.9219ZM9.96094 18.2617C5.35156 18.2617 1.66992 14.5703 1.66992 9.96094C1.66992 5.35156 5.3418 1.66016 9.95117 1.66016C14.5605 1.66016 18.2617 5.35156 18.2617 9.96094C18.2617 14.5703 14.5703 18.2617 9.96094 18.2617Z" fill="black" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 835 B

View File

@@ -5,15 +5,14 @@
--box-border-radius: 3px;
--media-border-radius: 8px;
--dot-size: 8px;
--shadows: 0 1px 2px rgba(0, 0, 0, 0.02), 0 2px 4px rgba(0, 0, 0, 0.02),
0 4px 8px rgba(0, 0, 0, 0.02), 0 8px 16px rgba(0, 0, 0, 0.02);
--shadows:
0 1px 2px rgba(0, 0, 0, 0.02), 0 2px 4px rgba(0, 0, 0, 0.02), 0 4px 8px rgba(0, 0, 0, 0.02),
0 8px 16px rgba(0, 0, 0, 0.02);
--box-margin: 10px;
--border-color: rgba(0, 0, 0, 0.05);
--link-color: var(--highlight-color);
--icon-hover-filter: invert(51%) sepia(0%) saturate(0%) hue-rotate(23deg)
brightness(90%) contrast(90%);
--icon-secondary-filter: invert(97%) sepia(0%) saturate(0%) hue-rotate(129deg)
brightness(86%) contrast(88%);
--icon-hover-filter: invert(51%) sepia(0%) saturate(0%) hue-rotate(23deg) brightness(90%) contrast(90%);
--icon-secondary-filter: invert(97%) sepia(0%) saturate(0%) hue-rotate(129deg) brightness(86%) contrast(88%);
--cell-background-color: #fff;
--code-background-color: #f9f9f9;
--secondary-color: #999;

View File

@@ -1,9 +1,9 @@
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import updateLocale from 'dayjs/plugin/updateLocale'
import localizedFormat from 'dayjs/plugin/localizedFormat'
import utc from 'dayjs/plugin/utc'
import relativeTime from 'dayjs/plugin/relativeTime'
import timezone from 'dayjs/plugin/timezone'
import updateLocale from 'dayjs/plugin/updateLocale'
import utc from 'dayjs/plugin/utc'
import 'dayjs/locale/af'
import 'dayjs/locale/am'

View File

@@ -1,9 +1,9 @@
import { $fetch } from 'ofetch'
import * as cheerio from 'cheerio'
import { LRUCache } from 'lru-cache'
import flourite from 'flourite'
import prism from '../prism'
import { LRUCache } from 'lru-cache'
import { $fetch } from 'ofetch'
import { getEnv } from '../env'
import prism from '../prism'
const cache = new LRUCache({
ttl: 1000 * 60 * 5, // 5 minutes
@@ -55,13 +55,15 @@ function getVideo($, item, { staticProxy, index }) {
video?.attr('src', staticProxy + video?.attr('src'))
?.attr('controls', true)
?.attr('preload', index > 15 ? 'auto' : 'metadata')
?.attr('playsinline', true).attr('webkit-playsinline', true)
?.attr('playsinline', true)
.attr('webkit-playsinline', true)
const roundVideo = $(item).find('.tgme_widget_message_roundvideo_wrap video')
roundVideo?.attr('src', staticProxy + roundVideo?.attr('src'))
?.attr('controls', true)
?.attr('preload', index > 15 ? 'auto' : 'metadata')
?.attr('playsinline', true).attr('webkit-playsinline', true)
?.attr('playsinline', true)
.attr('webkit-playsinline', true)
return $.html(video) + $.html(roundVideo)
}
@@ -106,9 +108,7 @@ function modifyHTMLContent($, content, { index } = {}) {
})
$(content).find('tg-spoiler')?.each((_index, spoiler) => {
const id = `spoiler-${index}-${_index}`
$(spoiler)?.attr('id', id)
?.wrap('<label class="spoiler-button"></label>')
?.before(`<input type="checkbox" />`)
$(spoiler)?.attr('id', id)?.wrap('<label class="spoiler-button"></label>')?.before(`<input type="checkbox" />`)
})
$(content).find('pre').each((_index, pre) => {
try {

View File

@@ -1,7 +1,7 @@
import rss from '@astrojs/rss'
import sanitizeHtml from 'sanitize-html'
import { getChannelInfo } from '../lib/telegram'
import { getEnv } from '../lib/env'
import { getChannelInfo } from '../lib/telegram'
export async function GET(Astro) {
const { SITE_URL } = Astro.locals