diff --git a/app/page.tsx b/app/page.tsx index 8e8615c..ec36807 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,8 +4,10 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import Image from "next/image"; import Link from "next/link"; import ShaderBackground from "@/components/ShaderBackground"; +import { getFollowerCounts, getCountForLink, formatCount } from "@/lib/followers"; -export default function Home() { +export default async function Home() { + const counts = await getFollowerCounts(); const jsonLd = { "@context": "https://schema.org", "@type": "ProfilePage", @@ -54,6 +56,8 @@ export default function Home() { const isStringIcon = typeof item.icon === "string"; const IconComponent = !isStringIcon ? item.icon : null; + const count = getCountForLink(item.name, counts); + return ( diff --git a/lib/followers/discord.ts b/lib/followers/discord.ts new file mode 100644 index 0000000..9423fa0 --- /dev/null +++ b/lib/followers/discord.ts @@ -0,0 +1,21 @@ +export async function getDiscordMemberCount(): Promise { + const token = process.env.DISCORD_BOT_TOKEN; + const guildId = process.env.DISCORD_GUILD_ID; + if (!token || !guildId) return null; + + try { + const res = await fetch( + `https://discord.com/api/v10/guilds/${encodeURIComponent(guildId)}?with_counts=true`, + { + headers: { Authorization: `Bot ${token}` }, + next: { revalidate: 300 }, + } + ); + if (!res.ok) return null; + + const data = await res.json(); + return data.approximate_member_count ?? null; + } catch { + return null; + } +} diff --git a/lib/followers/index.ts b/lib/followers/index.ts new file mode 100644 index 0000000..801bcec --- /dev/null +++ b/lib/followers/index.ts @@ -0,0 +1,48 @@ +import { getYoutubeSubscriberCount } from "./youtube"; +import { getRobloxFollowerCount } from "./roblox"; +import { getDiscordMemberCount } from "./discord"; + +export interface FollowerCounts { + youtube: number | null; + roblox: number | null; + discord: number | null; +} + +export interface FollowerInfo { + count: number; + label: string; +} + +export async function getFollowerCounts(): Promise { + const [youtube, roblox, discord] = await Promise.all([ + getYoutubeSubscriberCount(), + getRobloxFollowerCount(), + getDiscordMemberCount(), + ]); + + return { youtube, roblox, discord }; +} + +const followerLabels: Record = { + youtube: "ผู้ติดตาม", + roblox: "ผู้ติดตาม", + discord: "สมาชิก", +}; + +export function getCountForLink(name: string, counts: FollowerCounts): FollowerInfo | null { + const key = name.toLowerCase(); + for (const [platform, label] of Object.entries(followerLabels)) { + const count = counts[platform as keyof FollowerCounts]; + if (key.includes(platform) && count !== null) { + return { count, label }; + } + } + return null; +} + +export function formatCount(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; + return n.toString(); +} + diff --git a/lib/followers/roblox.ts b/lib/followers/roblox.ts new file mode 100644 index 0000000..0622bec --- /dev/null +++ b/lib/followers/roblox.ts @@ -0,0 +1,17 @@ +export async function getRobloxFollowerCount(): Promise { + const userId = process.env.ROBLOX_USER_ID; + if (!userId) return null; + + try { + const res = await fetch( + `https://friends.roblox.com/v1/users/${encodeURIComponent(userId)}/followers/count`, + { next: { revalidate: 300 } } + ); + if (!res.ok) return null; + + const data = await res.json(); + return data.count ?? null; + } catch { + return null; + } +} diff --git a/lib/followers/youtube.ts b/lib/followers/youtube.ts new file mode 100644 index 0000000..8ecf837 --- /dev/null +++ b/lib/followers/youtube.ts @@ -0,0 +1,19 @@ +export async function getYoutubeSubscriberCount(): Promise { + const apiKey = process.env.YOUTUBE_API_KEY; + const channelId = process.env.YOUTUBE_CHANNEL_ID; + if (!apiKey || !channelId) return null; + + try { + const res = await fetch( + `https://www.googleapis.com/youtube/v3/channels?part=statistics&id=${encodeURIComponent(channelId)}&key=${encodeURIComponent(apiKey)}`, + { next: { revalidate: 300 } } + ); + if (!res.ok) return null; + + const data = await res.json(); + const count = data.items?.[0]?.statistics?.subscriberCount; + return count ? Number(count) : null; + } catch { + return null; + } +}