├── .env.example
├── Assets
├── dark.png
├── image.png
├── white.png
├── image-1.png
└── image-2.png
├── README.md
└── weekly_commits
/.env.example:
--------------------------------------------------------------------------------
1 | GITHUB_USERNAME=
2 | GITHUB_PAT=
--------------------------------------------------------------------------------
/Assets/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ad1822/weekly-github-waybar-module/HEAD/Assets/dark.png
--------------------------------------------------------------------------------
/Assets/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ad1822/weekly-github-waybar-module/HEAD/Assets/image.png
--------------------------------------------------------------------------------
/Assets/white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ad1822/weekly-github-waybar-module/HEAD/Assets/white.png
--------------------------------------------------------------------------------
/Assets/image-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ad1822/weekly-github-waybar-module/HEAD/Assets/image-1.png
--------------------------------------------------------------------------------
/Assets/image-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ad1822/weekly-github-waybar-module/HEAD/Assets/image-2.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
🟩🟩⬛🟩⬛
3 |
4 |
5 | # GitHub Weekly Contributions Widget for Waybar
6 |
7 | A terminal or bar integration script that fetches your **GitHub contribution activity for the current week (Sunday to Today)** using the GitHub GraphQL API and renders a **colored heatmap** with detailed tooltips.
8 |
9 | Designed for seamless integration with status bars like **Waybar**, **Polybar**, or any custom desktop widget.
10 |
11 | ---
12 |
13 | ## Features
14 |
15 | - Pulls real-time contribution data from GitHub's GraphQL API.
16 | - Displays a **7-day contribution heatmap** (Sunday–Saturday layout).
17 | - Uses a **color-coded square** (■) system similar to GitHub's own UI.
18 | - Provides a **detailed tooltip** with:
19 | - Date-wise contribution breakdown.
20 | - Total contributions in the week.
21 |
22 | ---
23 |
24 | ## Color Levels
25 |
26 | #### Dark Theme (By Default, If no argv passed)
27 | | Contributions | Color Hex | Meaning |
28 | |---------------|------------|--------------------|
29 | | 0 | `#161b22` | No contributions |
30 | | 1–3 | `#0e4429` | Low activity |
31 | | 4–6 | `#006d32` | Moderate activity |
32 | | 7–9 | `#26a641` | High activity |
33 | | 10+ | `#39d353` | Very high activity |
34 |
35 | 
36 |
37 | #### Light Theme
38 | | Contributions | Color Hex | Meaning |
39 | |---------------|------------|--------------------|
40 | | 0 | `#ebedf0` | No contributions |
41 | | 1–3 | `#9be9a8` | Low activity |
42 | | 4–6 | `#40c463` | Moderate activity |
43 | | 7–9 | `#30a14e` | High activity |
44 | | 10+ | `#216e39` | Very high activity |
45 |
46 | 
47 |
48 |
49 | ## Example
50 |
51 |
52 | | Preview |
53 | |---------------------------------|
54 | |  |
55 | | |
56 | |  |
57 | | |
58 | |  |
59 |
60 |
61 | ---
62 |
63 | ## Setup
64 |
65 | ### 1. Clone the Repository
66 |
67 | ```bash
68 | git clone https://github.com/ad1822/weekly-github-waybar-module.git
69 | cd weekly-github-waybar-module
70 | ````
71 |
72 | ---
73 |
74 | ### 2. GitHub Authentication
75 |
76 | To make this module work, you need to provide:
77 |
78 | * Your **GitHub username**
79 | * A **Fine-Grained Personal Access Token (PAT)**
80 |
81 | * Scope: `Repository access → All repositories`
82 | * Minimum required permissions for reading contribution data
83 |
84 | ➡ **Generate your token here:**
85 | [https://github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new)
86 |
87 | ---
88 |
89 | ### 3. Create `.env` File
90 |
91 | Inside the project directory, create a `.env` file with the following content:
92 |
93 | ```env
94 | GITHUB_USERNAME=your_github_username
95 | GITHUB_PAT=ghp_yourGeneratedTokenHere
96 | ```
97 | ---
98 |
99 | ### 4. Install Python Dependencies
100 |
101 | ```bash
102 | pip install -r requirements.txt
103 | ```
104 | > Requires Python 3.7 or higher.
105 | > Dependencies: `requests`, `python-dotenv`
106 |
107 | ---
108 |
109 | ### 5. Waybar Integration
110 |
111 | Add the following block to your Waybar `config.jsonc`:
112 |
113 | ```jsonc
114 | "custom/gh_heatmap": {
115 | "exec": "sleep 1 & ~/.config/waybar/scripts/weekly_commits DARK/LIGHT",
116 | "return-type": "json",
117 | "interval": 2400,
118 | "tooltip": true,
119 | "on-click": "xdg-open https://github.com/",
120 | "on-click-right": "~/.config/waybar/scripts/weekly_commits DARK/LIGHT"
121 | }
122 | ```
123 | - On `click` of that module, Your github profile open in browser
124 | - On `right-click` of that module, You can refresh module, So it can fetch latest commit data
125 |
126 | Then add styling in your `style.css`:
127 |
128 | ```css
129 | #custom-gh_heatmap {
130 | color: #39d353;
131 | background: rgba(30, 30, 46,0.89); // Put your own background color
132 | border-radius: 6px;
133 | margin-right: 2px;
134 | padding: 0px 8px;
135 | }
136 | ```
137 |
138 | > Ensure the script is executable:
139 | >
140 | > ```bash
141 | > chmod +x ~/.config/waybar/scripts/weekly_commits
142 | > ```
143 |
144 | 👉 Check out my custom Hyprland dotfiles: [**hyprdots**](https://github.com/ad1822/hyprdots)
145 | If you like it, consider giving it a ⭐ — it helps!
--------------------------------------------------------------------------------
/weekly_commits:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import requests
4 | import datetime
5 | import json
6 | from collections import OrderedDict
7 | import os
8 | from dotenv import load_dotenv
9 | import sys
10 | import time
11 |
12 | load_dotenv()
13 |
14 | RETRY_DELAY = 2
15 |
16 | MAX_RETRIES = 5
17 | RETRY_DELAY = 3 # seconds
18 |
19 | output = {}
20 | success = False
21 |
22 |
23 | GITHUB_USERNAME = os.getenv("GITHUB_USERNAME")
24 | GITHUB_PAT = os.getenv("GITHUB_PAT")
25 |
26 | if not GITHUB_USERNAME or not GITHUB_PAT:
27 | raise ValueError("GITHUB_USERNAME or GITHUB_PAT not set in .env")
28 |
29 |
30 | today = datetime.date.today()
31 | start_of_week = today - datetime.timedelta(days=(today.weekday() + 1) % 7)
32 | from_date = start_of_week.isoformat() + "T00:00:00Z"
33 | to_date = today.isoformat() + "T23:59:59Z"
34 |
35 | query = """
36 | query($user: String!, $from: DateTime!, $to: DateTime!) {
37 | user(login: $user) {
38 | contributionsCollection(from: $from, to: $to) {
39 | contributionCalendar {
40 | weeks {
41 | contributionDays {
42 | date
43 | contributionCount
44 | weekday
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | """
52 |
53 | variables = {
54 | "user": GITHUB_USERNAME,
55 | "from": from_date,
56 | "to": to_date
57 | }
58 |
59 | headers = {
60 | "Authorization": f"Bearer {GITHUB_PAT}",
61 | "Accept": "application/vnd.github.v4.idl"
62 | }
63 |
64 | COLOR_SCHEME_DARK = {
65 | 0: "#202329", # No contributions
66 | 1: "#0e4429", # 1-3 commits
67 | 2: "#006d32", # 4-6 commits
68 | 3: "#26a641", # 7-9 commits
69 | 4: "#39d353" # 10+ commits
70 | }
71 |
72 | COLOR_SCHEME_LIGHT = {
73 | 0: "#ebedf0", # No contributions
74 | 1: "#9be9a8", # 1-3 commits
75 | 2: "#40c463", # 4-6 commits
76 | 3: "#30a14e", # 7-9 commits
77 | 4: "#216e39" # 10+ commits
78 | }
79 |
80 |
81 | theme = sys.argv[1].upper() if len(sys.argv) > 1 else "DARK"
82 | if theme not in ["DARK", "LIGHT"]:
83 | print(f"Unknown theme '{theme}', defaulting to DARK.")
84 | theme = "DARK"
85 |
86 | def get_color(count, theme):
87 | """Return heatmap color based on commit count and theme."""
88 | scheme = COLOR_SCHEME_DARK if theme == "DARK" else COLOR_SCHEME_LIGHT
89 |
90 | if count == 0:
91 | return scheme[0]
92 | elif 1 <= count <= 3:
93 | return scheme[1]
94 | elif 4 <= count <= 6:
95 | return scheme[2]
96 | elif 7 <= count <= 9:
97 | return scheme[3]
98 | else:
99 | return scheme[4]
100 |
101 | def get_weekday_name(weekday):
102 | return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][(weekday + 1) % 7]
103 |
104 |
105 | for attempt in range(MAX_RETRIES):
106 | try:
107 | if attempt > 0:
108 | time.sleep(RETRY_DELAY)
109 |
110 | response = requests.post(
111 | "https://api.github.com/graphql",
112 | json={"query": query, "variables": variables},
113 | headers=headers,
114 | timeout=10
115 | )
116 | response.raise_for_status()
117 | data = response.json()
118 |
119 | weeks = data["data"]["user"]["contributionsCollection"]["contributionCalendar"]["weeks"]
120 |
121 | contributions = OrderedDict()
122 | for i in range(7):
123 | day = start_of_week + datetime.timedelta(days=i)
124 | contributions[day] = 0
125 |
126 | for week in weeks:
127 | for day in week["contributionDays"]:
128 | date = datetime.date.fromisoformat(day["date"])
129 | if start_of_week <= date <= today:
130 | contributions[date] = day["contributionCount"]
131 |
132 | dots = []
133 | tooltip_lines = []
134 | total_contributions = 0
135 |
136 | for date, count in contributions.items():
137 | color = get_color(count, theme)
138 | dots.append(f'■')
139 | weekday = get_weekday_name(date.weekday())
140 | tooltip_lines.append(f"{weekday} ({date.day}/{date.month}): {count} contribution{'s' if count != 1 else ''}")
141 | total_contributions += count
142 |
143 | output = {
144 | "text": " ".join(dots),
145 | "tooltip": "\n".join([
146 | f"GitHub Contributions ({start_of_week.strftime('%d/%m')} to {today.strftime('%d/%m')})",
147 | f"Total: {total_contributions}"
148 | ] + tooltip_lines),
149 | "class": "github-contributions",
150 | "alt": "GitHub Contributions"
151 | }
152 |
153 | success = True
154 | break # exit retry loop on success
155 |
156 | except requests.exceptions.RequestException as e:
157 | if attempt == MAX_RETRIES - 1:
158 | output = {
159 | "text": "⚠",
160 | "tooltip": f"GitHub API error: {str(e)}",
161 | "class": "error",
162 | "alt": "GitHub Error"
163 | }
164 |
165 | except Exception as e:
166 | output = {
167 | "text": "⚠",
168 | "tooltip": f"Error: {str(e)}",
169 | "class": "error",
170 | "alt": "Error"
171 | }
172 | break # break if it's not a retryable error
173 |
174 |
175 | print(json.dumps(output))
--------------------------------------------------------------------------------