diff --git a/.github/workflows/sync-qq-groups.yml b/.github/workflows/sync-qq-groups.yml new file mode 100644 index 0000000..689bc66 --- /dev/null +++ b/.github/workflows/sync-qq-groups.yml @@ -0,0 +1,49 @@ +name: Sync QQ Groups + +on: + repository_dispatch: + types: [sync-qq-groups] + workflow_dispatch: + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + token: ${{ secrets.PAT_TOKEN }} + show-progress: false + + - name: Fetch index.json and update README + run: | + DATA=$(curl -sf https://end.maafw.com/index.json) || exit 0 + + USER_GROUP=$(echo "$DATA" | jq -r '.qq_groups.user.number // empty') + USER_LINK=$(echo "$DATA" | jq -r '.qq_groups.user.link // empty') + DEV_GROUP=$(echo "$DATA" | jq -r '.qq_groups.dev.number // empty') + DEV_LINK=$(echo "$DATA" | jq -r '.qq_groups.dev.link // empty') + + if [ -z "$USER_GROUP" ] || [ -z "$USER_LINK" ] || [ -z "$DEV_GROUP" ] || [ -z "$DEV_LINK" ]; then + echo "Missing fields in index.json, skipping" + exit 0 + fi + + sed -i "s|\[用户 QQ 群\](https://qm.qq.com/q/[^)]*): [0-9]*|[用户 QQ 群]($USER_LINK): $USER_GROUP|" README.md + sed -i "s|\[开发 QQ 群\](https://qm.qq.com/q/[^)]*): [0-9]*|[开发 QQ 群]($DEV_LINK): $DEV_GROUP|" README.md + + - name: Check for changes + id: diff + run: | + git diff --quiet && echo "changed=false" >> "$GITHUB_OUTPUT" || echo "changed=true" >> "$GITHUB_OUTPUT" + + - name: Commit and push + if: steps.diff.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "chore: sync QQ group info in README" + git push diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 7b0d82f..2ba1aff 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -3,10 +3,12 @@ import { motion } from "framer-motion"; import { useTranslation } from "react-i18next"; import Image from "next/image"; -import { FRIEND_LINKS, GITHUB_URLS, QQ_GROUPS } from "../constants"; +import { FRIEND_LINKS, GITHUB_URLS } from "../constants"; +import { useQQGroups } from "../hooks/useQQGroups"; export default function Footer() { const { t } = useTranslation(); + const qqGroups = useQQGroups(); // 性能优化:使用固定速度,移除滚动速度检测 const marqueeDuration = 200; @@ -51,22 +53,22 @@ export default function Footer() { diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 2fe8138..2e75cc5 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -7,10 +7,12 @@ import { Book, Languages, Users } from "lucide-react"; import { Button } from "./ui/Button"; import { useTranslation } from "react-i18next"; import { ThemeToggle } from "./ThemeToggle"; -import { GITHUB_URLS, QQ_GROUPS } from "../constants"; +import { GITHUB_URLS } from "../constants"; +import { useQQGroups } from "../hooks/useQQGroups"; export default function Header() { const { t, i18n } = useTranslation(); + const qqGroups = useQQGroups(); const toggleLanguage = () => { const newLang = i18n.language === "zh" ? "en" : "zh"; @@ -52,7 +54,7 @@ export default function Header() { {t("header.docs")} CACHE_TTL) return null; + return data; + } catch { + return null; + } +} + +function writeCache(data: QQGroups) { + try { + localStorage.setItem(CACHE_KEY, JSON.stringify({ data, ts: Date.now() })); + } catch { + // storage full or unavailable + } +} + +export function useQQGroups() { + const [groups, setGroups] = useState(fallback); + + const fetchGroups = useCallback(async () => { + const cached = readCache(); + if (cached) { + setGroups(cached); + return; + } + try { + const res = await fetch("https://end.maafw.com/index.json"); + if (!res.ok) return; + const data = await res.json(); + const qq = data.qq_groups; + if ( + qq?.user?.number && + qq?.user?.link && + qq?.dev?.number && + qq?.dev?.link + ) { + const result: QQGroups = { user: qq.user, dev: qq.dev }; + setGroups(result); + writeCache(result); + } + } catch { + // fallback to constants + } + }, []); + + useEffect(() => { + (async () => { + await fetchGroups(); + })(); + }, [fetchGroups]); + + return groups; +}