|
2 | 2 | (async () => { |
3 | 3 | const repo = 'autoscrape-labs/pydoll' |
4 | 4 | const apiUrl = `https://api.github.com/repos/${repo}` |
| 5 | + let repoStarsCount = 0 |
| 6 | + |
| 7 | + // Simple localStorage cache with TTL |
| 8 | + const cacheGet = (key, maxAgeMs) => { |
| 9 | + try { |
| 10 | + const raw = localStorage.getItem(key) |
| 11 | + if (!raw) return null |
| 12 | + const parsed = JSON.parse(raw) |
| 13 | + if (!parsed || typeof parsed !== 'object') return null |
| 14 | + if (typeof parsed.t !== 'number') return null |
| 15 | + const now = Date.now() |
| 16 | + if (now - parsed.t > maxAgeMs) return null |
| 17 | + return parsed.v |
| 18 | + } catch (_) { |
| 19 | + return null |
| 20 | + } |
| 21 | + } |
| 22 | + const cacheSet = (key, value) => { |
| 23 | + try { |
| 24 | + localStorage.setItem(key, JSON.stringify({ t: Date.now(), v: value })) |
| 25 | + } catch (_) {} |
| 26 | + } |
| 27 | + const TTL = 5 * 60 * 1000 // 5 minutes |
5 | 28 |
|
6 | 29 | // Cursor glow effect (subtle follow) |
7 | 30 | const glow = document.getElementById('cursorGlow') |
|
34 | 57 | hero.addEventListener('mousemove', move, { passive: true }) |
35 | 58 | } |
36 | 59 |
|
| 60 | + // Stars, goal progress and latest stargazers |
37 | 61 | try { |
38 | | - const res = await fetch(apiUrl, { headers: { 'Accept': 'application/vnd.github+json' } }) |
39 | | - if (res.ok) { |
40 | | - const data = await res.json() |
41 | | - const stars = (data.stargazers_count ?? 0).toLocaleString('pt-BR') |
| 62 | + const cacheKey = `gh:repo:${repo}` |
| 63 | + let data = cacheGet(cacheKey, TTL) |
| 64 | + if (!data) { |
| 65 | + const res = await fetch(apiUrl, { headers: { 'Accept': 'application/vnd.github+json' } }) |
| 66 | + if (res.ok) { |
| 67 | + data = await res.json() |
| 68 | + cacheSet(cacheKey, data) |
| 69 | + } |
| 70 | + } |
| 71 | + if (data) { |
| 72 | + const starsCount = Number(data.stargazers_count ?? 0) |
| 73 | + repoStarsCount = starsCount |
| 74 | + const starsFmt = starsCount.toLocaleString('pt-BR') |
42 | 75 |
|
43 | 76 | const starCount = document.getElementById('starCount') |
44 | | - if (starCount) { |
45 | | - starCount.textContent = `${stars}★` |
46 | | - } |
47 | | - // forks removidos da UI |
| 77 | + if (starCount) starCount.textContent = `${starsFmt}★` |
| 78 | + |
| 79 | + // Update progress bar to 10k |
| 80 | + const GOAL = 10000 |
| 81 | + const pct = Math.max(0, Math.min(100, Math.round((starsCount / GOAL) * 100))) |
| 82 | + const bar = document.getElementById('starsProgressBar') |
| 83 | + const label = document.getElementById('starsProgressLabel') |
| 84 | + const pctLabel = document.getElementById('starsProgressPct') |
| 85 | + if (bar) bar.style.width = `${pct}%` |
| 86 | + if (label) label.textContent = `${starsFmt} / ${GOAL.toLocaleString('pt-BR')}` |
| 87 | + if (pctLabel) pctLabel.textContent = `${pct}%` |
48 | 88 | } |
49 | 89 | } catch (_) { |
50 | 90 | // noop: keep placeholders on failure |
51 | 91 | } |
52 | 92 |
|
| 93 | + // Fetch latest stargazers (last 10, newest first, fill from previous page if needed) |
| 94 | + try { |
| 95 | + const perPage = 10 |
| 96 | + const lastPage = Math.max(1, Math.ceil((repoStarsCount || 1) / perPage)) |
| 97 | + |
| 98 | + const fetchPage = async (page) => { |
| 99 | + const cacheKey = `gh:stargazers:${repo}:p${page}:pp${perPage}` |
| 100 | + let payload = cacheGet(cacheKey, TTL) |
| 101 | + if (!payload) { |
| 102 | + const res = await fetch(`https://api.github.com/repos/${repo}/stargazers?per_page=${perPage}&page=${page}`, { |
| 103 | + headers: { 'Accept': 'application/vnd.github.v3.star+json' } |
| 104 | + }) |
| 105 | + if (!res.ok) return [] |
| 106 | + payload = await res.json() |
| 107 | + cacheSet(cacheKey, payload) |
| 108 | + } |
| 109 | + if (!Array.isArray(payload)) return [] |
| 110 | + if (payload.length && (payload[0]?.user || payload[0]?.starred_at)) { |
| 111 | + return payload |
| 112 | + .map((it) => ({ |
| 113 | + login: it?.user?.login, |
| 114 | + avatar_url: it?.user?.avatar_url, |
| 115 | + html_url: it?.user?.html_url || (it?.user?.login ? `https://github.com/${it.user.login}` : '#'), |
| 116 | + starred_at: it?.starred_at ? Date.parse(it.starred_at) : 0 |
| 117 | + })) |
| 118 | + .filter((u) => u.login) |
| 119 | + } |
| 120 | + // Fallback if server ignores star+json |
| 121 | + return payload.map((u) => ({ |
| 122 | + login: u.login, |
| 123 | + avatar_url: u.avatar_url, |
| 124 | + html_url: u.html_url || (u.login ? `https://github.com/${u.login}` : '#'), |
| 125 | + starred_at: 0 |
| 126 | + })) |
| 127 | + } |
| 128 | + |
| 129 | + let entries = await fetchPage(lastPage) |
| 130 | + if (entries.length < perPage && lastPage > 1) { |
| 131 | + const prev = await fetchPage(lastPage - 1) |
| 132 | + entries = entries.concat(prev) |
| 133 | + } |
| 134 | + |
| 135 | + // Sort newest first and cap to perPage |
| 136 | + entries.sort((a, b) => b.starred_at - a.starred_at) |
| 137 | + entries = entries.slice(0, perPage) |
| 138 | + |
| 139 | + // Render |
| 140 | + const list = document.getElementById('stargazersList') |
| 141 | + if (list) { |
| 142 | + entries.forEach((u) => { |
| 143 | + const li = document.createElement('li') |
| 144 | + li.className = 'flex items-center gap-2' |
| 145 | + const a = document.createElement('a') |
| 146 | + a.href = u.html_url |
| 147 | + a.target = '_blank' |
| 148 | + a.rel = 'noopener' |
| 149 | + a.className = 'group inline-flex items-center gap-2 rounded-full border border-white/10 bg-slate-800/60 px-3 py-1.5 text-sm text-slate-200 hover:bg-slate-800' |
| 150 | + const img = document.createElement('img') |
| 151 | + img.src = u.avatar_url |
| 152 | + img.alt = u.login |
| 153 | + img.width = 22 |
| 154 | + img.height = 22 |
| 155 | + img.loading = 'lazy' |
| 156 | + img.decoding = 'async' |
| 157 | + img.className = 'h-[22px] w-[22px] rounded-full ring-1 ring-white/10' |
| 158 | + const span = document.createElement('span') |
| 159 | + span.textContent = u.login |
| 160 | + a.appendChild(img) |
| 161 | + a.appendChild(span) |
| 162 | + li.appendChild(a) |
| 163 | + list.appendChild(li) |
| 164 | + }) |
| 165 | + } |
| 166 | + } catch (_) { |
| 167 | + // ignore |
| 168 | + } |
| 169 | + |
53 | 170 | // Copy install command |
54 | 171 | const copyBtn = document.getElementById('copyBtn') |
55 | 172 | const installCmd = document.getElementById('installCmd') |
|
0 commit comments