Source code of my website
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

✨ : add script to fetch plausible analytics data

+139
+7
scripts/mise.toml
··· 1 + [tools] 2 + go = { version = "1.21" } 3 + 4 + [tasks.fetch] 5 + description = 'Fetch data from plausible' 6 + run = "go run plausible_fetch.go -site codeka.io -range all -out ../data/plausible-stats/visits.json" 7 +
+132
scripts/plausible_fetch.go
··· 1 + package main 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "flag" 8 + "fmt" 9 + "io" 10 + "net/http" 11 + "os" 12 + "time" 13 + ) 14 + 15 + // APIRequest defines the body we send to Plausible. 16 + type APIRequest struct { 17 + SiteID string `json:"site_id"` 18 + Metrics []string `json:"metrics"` 19 + DateRange string `json:"date_range"` 20 + Dimensions []string `json:"dimensions"` 21 + } 22 + 23 + // APIResult matches items under response.results 24 + // Example item: {"metrics": [270], "dimensions": ["/path/"]} 25 + type APIResult struct { 26 + Metrics []int64 `json:"metrics"` 27 + Dimensions []string `json:"dimensions"` 28 + } 29 + 30 + // APIResponse is the top-level structure returned by Plausible. 31 + type APIResponse struct { 32 + Results []APIResult `json:"results"` 33 + } 34 + 35 + // OutputEntry is the simplified shape we want to write: page/visits. 36 + type OutputEntry struct { 37 + Page string `json:"page"` 38 + Visits int64 `json:"visits"` 39 + } 40 + 41 + func main() { 42 + var ( 43 + apiURL = flag.String("url", "https://plausible.io/api/v2/query", "Plausible API endpoint") 44 + siteID = flag.String("site", "dummy.site", "site_id parameter") 45 + date = flag.String("range", "7d", "date_range parameter (e.g., 7d, 30d, month") 46 + outFile = flag.String("out", "plausible-pages.json", "output JSON file path") 47 + ) 48 + flag.Parse() 49 + 50 + apiKey := os.Getenv("PLAUSIBLE_API_KEY") 51 + if apiKey == "" { 52 + fmt.Fprintln(os.Stderr, "error: PLAUSIBLE_API_KEY environment variable is not set") 53 + os.Exit(1) 54 + } 55 + 56 + reqBody := APIRequest{ 57 + SiteID: *siteID, 58 + Metrics: []string{ 59 + "pageviews", 60 + }, 61 + DateRange: *date, 62 + Dimensions: []string{ 63 + "event:page", 64 + }, 65 + } 66 + payload, err := json.Marshal(reqBody) 67 + if err != nil { 68 + fmt.Fprintf(os.Stderr, "marshal request: %v\n", err) 69 + os.Exit(1) 70 + } 71 + 72 + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 73 + defer cancel() 74 + 75 + req, err := http.NewRequestWithContext(ctx, http.MethodPost, *apiURL, bytes.NewReader(payload)) 76 + if err != nil { 77 + fmt.Fprintf(os.Stderr, "build request: %v\n", err) 78 + os.Exit(1) 79 + } 80 + req.Header.Set("Authorization", "Bearer "+apiKey) 81 + req.Header.Set("Content-Type", "application/json") 82 + 83 + client := &http.Client{Timeout: 20 * time.Second} 84 + resp, err := client.Do(req) 85 + if err != nil { 86 + fmt.Fprintf(os.Stderr, "http request: %v\n", err) 87 + os.Exit(1) 88 + } 89 + defer resp.Body.Close() 90 + 91 + if resp.StatusCode < 200 || resp.StatusCode >= 300 { 92 + b, _ := io.ReadAll(resp.Body) 93 + fmt.Fprintf(os.Stderr, "http error: %s\n%s\n", resp.Status, string(b)) 94 + os.Exit(1) 95 + } 96 + 97 + var apiResp APIResponse 98 + if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { 99 + fmt.Fprintf(os.Stderr, "decode response: %v\n", err) 100 + os.Exit(1) 101 + } 102 + 103 + // Transform: results -> [{page, visits}] 104 + out := make([]OutputEntry, 0, len(apiResp.Results)) 105 + for _, r := range apiResp.Results { 106 + var page string 107 + if len(r.Dimensions) > 0 { 108 + page = r.Dimensions[0] 109 + } 110 + var visits int64 111 + if len(r.Metrics) > 0 { 112 + visits = r.Metrics[0] 113 + } 114 + out = append(out, OutputEntry{Page: page, Visits: visits}) 115 + } 116 + 117 + f, err := os.Create(*outFile) 118 + if err != nil { 119 + fmt.Fprintf(os.Stderr, "create output: %v\n", err) 120 + os.Exit(1) 121 + } 122 + defer f.Close() 123 + 124 + enc := json.NewEncoder(f) 125 + enc.SetIndent("", " ") 126 + if err := enc.Encode(out); err != nil { 127 + fmt.Fprintf(os.Stderr, "write output: %v\n", err) 128 + os.Exit(1) 129 + } 130 + 131 + fmt.Printf("wrote %d entries to %s\n", len(out), *outFile) 132 + }