1+ const fs = require ( 'fs' ) ;
2+ const path = require ( 'path' ) ;
3+
4+ const REPO = process . env . REPO ;
5+ const GITHUB_TOKEN = process . env . TRAFFIC_TOKEN ;
6+ const METRICS_FILE = 'metrics.json' ;
7+
8+ if ( ! GITHUB_TOKEN || ! REPO ) {
9+ console . error ( 'Error: TRAFFIC_TOKEN and REPO environment variables must be set.' ) ;
10+ process . exit ( 1 ) ;
11+ }
12+
13+ if ( typeof fetch !== 'function' ) {
14+ console . error ( 'Error: global fetch is not available. Use Node.js 20 or later.' ) ;
15+ process . exit ( 1 ) ;
16+ }
17+
18+ async function getLast14DaysTraffic ( ) {
19+ const response = await fetch ( `https://api.github.com/repos/${ REPO } /traffic/views` , {
20+ headers : {
21+ Accept : 'application/vnd.github+json' ,
22+ Authorization : `Bearer ${ GITHUB_TOKEN } ` ,
23+ 'User-Agent' : 'visitor-counter'
24+ }
25+ } ) ;
26+
27+ if ( ! response . ok ) {
28+ const errorText = await response . text ( ) ;
29+ throw new Error (
30+ `Failed to fetch traffic data: ${ response . status } ${ response . statusText } \n${ errorText } `
31+ ) ;
32+ }
33+
34+ const data = await response . json ( ) ;
35+ return data . views . map ( ( item ) => ( {
36+ date : item . timestamp . slice ( 0 , 10 ) ,
37+ count : item . count ,
38+ uniques : item . uniques
39+ } ) ) ;
40+ }
41+
42+ function readMetrics ( ) {
43+ if ( ! fs . existsSync ( METRICS_FILE ) ) {
44+ return [ ] ;
45+ }
46+
47+ try {
48+ const raw = fs . readFileSync ( METRICS_FILE , 'utf-8' ) ;
49+ const parsed = JSON . parse ( raw ) ;
50+ return Array . isArray ( parsed ) ? parsed : [ ] ;
51+ } catch {
52+ console . error ( 'metrics.json is not valid JSON. Starting fresh.' ) ;
53+ return [ ] ;
54+ }
55+ }
56+
57+ function writeMetrics ( metrics ) {
58+ fs . writeFileSync ( METRICS_FILE , JSON . stringify ( metrics , null , 2 ) ) ;
59+ console . log ( `metrics.json updated with ${ metrics . length } days` ) ;
60+ }
61+
62+ function mergeMetrics ( existing , fetched ) {
63+ const byDate = new Map ( ) ;
64+
65+ for ( const entry of existing ) {
66+ byDate . set ( entry . date , entry ) ;
67+ }
68+
69+ for ( const entry of fetched ) {
70+ byDate . set ( entry . date , entry ) ;
71+ }
72+
73+ return [ ...byDate . values ( ) ] . sort ( ( left , right ) => left . date . localeCompare ( right . date ) ) ;
74+ }
75+
76+ function calculateTotalViews ( metrics ) {
77+ return metrics . reduce ( ( sum , entry ) => sum + entry . count , 0 ) ;
78+ }
79+
80+ function findMarkdownFiles ( dir ) {
81+ let results = [ ] ;
82+ const entries = fs . readdirSync ( dir , { withFileTypes : true } ) ;
83+
84+ for ( const entry of entries ) {
85+ const fullPath = path . join ( dir , entry . name ) ;
86+ if ( entry . isDirectory ( ) ) {
87+ results = results . concat ( findMarkdownFiles ( fullPath ) ) ;
88+ continue ;
89+ }
90+
91+ if ( entry . isFile ( ) && entry . name . endsWith ( '.md' ) ) {
92+ results . push ( fullPath ) ;
93+ }
94+ }
95+
96+ return results ;
97+ }
98+
99+ function updateMarkdownBadges ( totalViews ) {
100+ const refreshDate = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ;
101+ const badgeRegex = / < ! - - S T A R T B A D G E - - > [ \s \S ] * ?< ! - - E N D B A D G E - - > / g;
102+ const badgeBlock = `<!-- START BADGE -->
103+ <div align="center">
104+ <img src="https://img.shields.io/badge/Total%20views-${ totalViews } -limegreen" alt="Total views">
105+ <p>Refresh Date: ${ refreshDate } </p>
106+ </div>
107+ <!-- END BADGE -->` ;
108+
109+ for ( const file of findMarkdownFiles ( '.' ) ) {
110+ const content = fs . readFileSync ( file , 'utf-8' ) ;
111+ if ( ! badgeRegex . test ( content ) ) {
112+ continue ;
113+ }
114+
115+ const updated = content . replace ( badgeRegex , badgeBlock ) ;
116+ fs . writeFileSync ( file , updated ) ;
117+ console . log ( `Updated badge in ${ file } ` ) ;
118+ }
119+ }
120+
121+ ( async ( ) => {
122+ try {
123+ const fetched = await getLast14DaysTraffic ( ) ;
124+ const existing = readMetrics ( ) ;
125+ const merged = mergeMetrics ( existing , fetched ) ;
126+ writeMetrics ( merged ) ;
127+
128+ const totalViews = calculateTotalViews ( merged ) ;
129+ updateMarkdownBadges ( totalViews ) ;
130+ } catch ( error ) {
131+ console . error ( error ) ;
132+ process . exit ( 1 ) ;
133+ }
134+ } ) ( ) ;
0 commit comments