feat: add dynamic social media follower/member counts to profile links
This commit is contained in:
+10
-1
@@ -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 (
|
||||
<Button
|
||||
key={index}
|
||||
@@ -82,6 +86,11 @@ export default function Home() {
|
||||
{item.desc && <span className="text-xs text-zinc-500 dark:text-zinc-400">{item.desc}</span>}
|
||||
</div>
|
||||
</div>
|
||||
{count !== null && (
|
||||
<span className="text-xs font-medium text-zinc-500 dark:text-zinc-400 shrink-0">
|
||||
{formatCount(count.count)} {count.label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export async function getDiscordMemberCount(): Promise<number | null> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<FollowerCounts> {
|
||||
const [youtube, roblox, discord] = await Promise.all([
|
||||
getYoutubeSubscriberCount(),
|
||||
getRobloxFollowerCount(),
|
||||
getDiscordMemberCount(),
|
||||
]);
|
||||
|
||||
return { youtube, roblox, discord };
|
||||
}
|
||||
|
||||
const followerLabels: Record<keyof FollowerCounts, string> = {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export async function getRobloxFollowerCount(): Promise<number | null> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export async function getYoutubeSubscriberCount(): Promise<number | null> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user