yasserrmd commited on
Commit
88048c6
Β·
verified Β·
1 Parent(s): f2813ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -129
app.py CHANGED
@@ -5,24 +5,39 @@ from datetime import datetime, timedelta
5
  import time
6
  import os
7
 
8
- def get_contribution_count(username, github_token, days=30):
9
  """
10
- Get accurate contribution count using GitHub GraphQL API
11
  """
12
  if not github_token:
13
- return 0
14
 
15
- # Calculate date range
16
  end_date = datetime.now()
17
  start_date = end_date - timedelta(days=days)
18
 
 
 
 
 
 
19
  query = """
20
  query($username: String!, $from: DateTime!, $to: DateTime!) {
21
  user(login: $username) {
22
  contributionsCollection(from: $from, to: $to) {
23
  contributionCalendar {
24
  totalContributions
 
 
 
 
 
 
25
  }
 
 
 
 
26
  }
27
  }
28
  }
@@ -30,8 +45,8 @@ def get_contribution_count(username, github_token, days=30):
30
 
31
  variables = {
32
  "username": username,
33
- "from": start_date.isoformat(),
34
- "to": end_date.isoformat()
35
  }
36
 
37
  headers = {
@@ -43,17 +58,39 @@ def get_contribution_count(username, github_token, days=30):
43
  response = requests.post(
44
  'https://api.github.com/graphql',
45
  json={'query': query, 'variables': variables},
46
- headers=headers
 
47
  )
48
 
49
  if response.status_code == 200:
50
  data = response.json()
51
- if 'data' in data and data['data']['user']:
52
- return data['data']['user']['contributionsCollection']['contributionCalendar']['totalContributions']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  except Exception as e:
54
- print(f"Error fetching contributions for {username}: {e}")
55
-
56
- return 0
57
 
58
  def is_uae_location(location):
59
  """
@@ -104,16 +141,16 @@ def is_uae_location(location):
104
 
105
  return False
106
 
107
- def fetch_github_users(github_token=None, max_users=200, min_followers=1, days=30):
108
  """
109
- Fetch GitHub users from UAE - Country-focused search
110
  """
111
  # Read token from environment variable if not provided
112
  if not github_token:
113
  github_token = os.getenv('GITHUB_TOKEN')
114
 
115
  if not github_token:
116
- return pd.DataFrame(), "❌ GitHub token is required for accurate contribution counts. Please provide a token."
117
 
118
  headers = {
119
  'Accept': 'application/vnd.github.v3+json',
@@ -123,35 +160,34 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=1, days=3
123
  all_users = {} # Use dict to avoid duplicates by login
124
  status_updates = []
125
  total_api_calls = 0
 
126
 
127
  try:
128
- # PRIORITY 1: Country-level searches (most reliable)
129
  country_searches = [
130
  'United+Arab+Emirates',
131
  'UAE',
132
- 'U.A.E',
133
  '"United Arab Emirates"',
134
- '"UAE"',
135
- 'Emirates',
136
  ]
137
 
138
- status_updates.append("πŸ” Phase 1: Searching by country (United Arab Emirates)...")
 
139
 
140
  for search_term in country_searches:
141
- if total_api_calls >= 200:
142
  break
143
 
144
- # Search with different sorting methods to catch different users
145
  sort_methods = [
146
- ('followers', 'desc'), # Most followed
147
- ('repositories', 'desc'), # Most repos
148
- ('joined', 'desc'), # Recently joined
149
  ]
150
 
151
  for sort_by, order in sort_methods:
152
- if total_api_calls >= 200:
153
  break
154
-
155
  search_url = f'https://api.github.com/search/users?q=location:{search_term}+followers:>={min_followers}&sort={sort_by}&order={order}&per_page=100'
156
 
157
  response = requests.get(search_url, headers=headers)
@@ -161,31 +197,35 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=1, days=3
161
  data = response.json()
162
  users = data.get('items', [])
163
 
164
- status_updates.append(f" πŸ“ Found {len(users)} users with search '{search_term}' sorted by {sort_by}")
165
 
166
  for user in users:
167
  if user['login'] not in all_users:
168
- if total_api_calls >= 200:
169
  break
170
-
171
  # Fetch detailed user info
172
- user_url = user['url']
173
- user_response = requests.get(user_url, headers=headers)
174
  total_api_calls += 1
175
 
176
  if user_response.status_code == 200:
177
  user_data = user_response.json()
178
-
179
- # Verify location is UAE-related
180
  user_location = user_data.get('location', '')
181
 
182
  if is_uae_location(user_location):
183
- # Get accurate contribution count
184
- contributions = get_contribution_count(user['login'], github_token, days)
 
 
 
 
 
 
 
185
 
186
  all_users[user['login']] = {
187
  'login': user_data.get('login', ''),
188
- 'name': user_data.get('name', user_data.get('login', '')),
189
  'avatar': user_data.get('avatar_url', ''),
190
  'followers': user_data.get('followers', 0),
191
  'public_repos': user_data.get('public_repos', 0),
@@ -193,64 +233,56 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=1, days=3
193
  'location': user_data.get('location', ''),
194
  'bio': user_data.get('bio', ''),
195
  'company': user_data.get('company', ''),
 
196
  }
197
 
198
- if len(all_users) % 10 == 0:
199
- status_updates.append(f" βœ… Collected {len(all_users)} unique UAE users so far...")
200
 
201
- time.sleep(0.5) # Rate limiting
202
 
203
- time.sleep(1) # Rate limiting between searches
204
 
205
  elif response.status_code == 403:
206
- status_updates.append(f"⚠️ Rate limit reached after {total_api_calls} calls")
207
  break
208
  elif response.status_code == 422:
209
- status_updates.append(f" ⚠️ Search term '{search_term}' invalid, skipping...")
210
  continue
211
  else:
212
- status_updates.append(f" ❌ Error with search '{search_term}': {response.status_code}")
213
 
214
- # PRIORITY 2: City-level searches as supplementary (if we need more users)
215
- if len(all_users) < max_users * 1.5 and total_api_calls < 180:
216
- status_updates.append(f"πŸ” Phase 2: Supplementary city-level search...")
217
 
218
- city_searches = [
219
- 'Dubai',
220
- 'Abu+Dhabi',
221
- 'Sharjah',
222
- 'Ajman',
223
- ]
224
 
225
- for city in city_searches:
226
- if total_api_calls >= 200 or len(all_users) >= max_users * 2:
227
  break
228
 
229
- search_url = f'https://api.github.com/search/users?q=location:{city}+followers:>={min_followers}&sort=followers&order=desc&per_page=100'
230
-
231
  response = requests.get(search_url, headers=headers)
232
  total_api_calls += 1
233
 
234
  if response.status_code == 200:
235
- data = response.json()
236
- users = data.get('items', [])
237
 
238
- for user in users[:30]: # Limit to top 30 per city
239
- if user['login'] not in all_users and total_api_calls < 200:
240
- user_url = user['url']
241
- user_response = requests.get(user_url, headers=headers)
242
  total_api_calls += 1
243
 
244
  if user_response.status_code == 200:
245
  user_data = user_response.json()
246
- user_location = user_data.get('location', '')
247
-
248
- if is_uae_location(user_location):
249
- contributions = get_contribution_count(user['login'], github_token, days)
250
 
251
  all_users[user['login']] = {
252
  'login': user_data.get('login', ''),
253
- 'name': user_data.get('name', user_data.get('login', '')),
254
  'avatar': user_data.get('avatar_url', ''),
255
  'followers': user_data.get('followers', 0),
256
  'public_repos': user_data.get('public_repos', 0),
@@ -258,28 +290,27 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=1, days=3
258
  'location': user_data.get('location', ''),
259
  'bio': user_data.get('bio', ''),
260
  'company': user_data.get('company', ''),
 
261
  }
262
 
263
- time.sleep(0.5)
264
-
265
- time.sleep(1)
266
 
267
- # Convert dict to list
268
  users_list = list(all_users.values())
 
269
 
270
- status_updates.append(f"πŸ“Š Total unique UAE users found: {len(users_list)}")
271
-
272
- # Sort by CONTRIBUTIONS FIRST (descending), then by followers (descending)
273
  users_list.sort(key=lambda x: (x['contributions'], x['followers']), reverse=True)
274
 
275
- # Take top contributors based on max_users
276
  top_users = users_list[:max_users]
277
 
278
- # Add rank based on contribution order
279
  for i, user in enumerate(top_users, 1):
280
  user['rank'] = i
281
 
282
- # Convert to DataFrame
283
  df = pd.DataFrame(top_users)
284
 
285
  if not df.empty:
@@ -290,8 +321,15 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=1, days=3
290
  status_message = f"""βœ… Successfully fetched top {len(df)} contributors (last {days} days)
291
  πŸ“Š Total unique users found: {len(all_users)}
292
  πŸ”§ API calls made: {total_api_calls}
293
- 🌍 Search strategy: Country-first (UAE/United Arab Emirates)
294
- """ + "\n".join(status_updates[-8:])
 
 
 
 
 
 
 
295
  return display_df, status_message
296
  else:
297
  return pd.DataFrame(), "⚠️ No users found\n" + "\n".join(status_updates)
@@ -303,7 +341,6 @@ def search_users(df, search_term):
303
  """Filter users based on search term"""
304
  if df is None or df.empty:
305
  return df
306
-
307
  if not search_term:
308
  return df
309
 
@@ -319,17 +356,18 @@ def search_users(df, search_term):
319
  with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as app:
320
 
321
  gr.Markdown("""
322
- # πŸ‡¦πŸ‡ͺ Top 200 GitHub Contributors in UAE
323
- ### Country-Focused Search with Accurate Contribution Counts
 
 
324
 
325
- **Search Strategy:** Prioritizes "United Arab Emirates" and "UAE" country-level searches,
326
- supplemented by major city searches. This ensures comprehensive coverage.
327
  """)
328
 
329
  with gr.Row():
330
  token_input = gr.Textbox(
331
- label="GitHub Personal Access Token (Required)",
332
- placeholder="ghp_xxxxxxxxxxxx or set GITHUB_TOKEN env var",
333
  type="password",
334
  scale=3
335
  )
@@ -344,14 +382,14 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
344
 
345
  with gr.Row():
346
  min_followers_input = gr.Slider(
347
- label="Minimum Followers (0 = most comprehensive)",
348
  minimum=0,
349
  maximum=50,
350
  value=0,
351
  step=1
352
  )
353
  days_input = gr.Slider(
354
- label="Days to Count Contributions",
355
  minimum=7,
356
  maximum=90,
357
  value=30,
@@ -359,22 +397,20 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
359
  )
360
 
361
  with gr.Row():
362
- fetch_btn = gr.Button("πŸš€ Fetch Top Contributors from UAE", variant="primary", size="lg")
363
 
364
- status_msg = gr.Textbox(label="Status & Progress", interactive=False, lines=6)
365
 
366
  with gr.Row():
367
  search_box = gr.Textbox(
368
- label="πŸ” Search by Name, Username, or Location",
369
- placeholder="Type to search...",
370
  scale=4
371
  )
372
  clear_btn = gr.Button("Clear", scale=1)
373
 
374
- # Store the full dataframe
375
  full_data = gr.State(value=pd.DataFrame())
376
 
377
- # Display dataframe
378
  data_display = gr.Dataframe(
379
  headers=["Rank", "Name", "Username", "Contributions", "Followers", "Public Repos", "Location", "GitHub Profile"],
380
  datatype=["number", "str", "str", "number", "number", "number", "str", "str"],
@@ -384,41 +420,43 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
384
 
385
  gr.Markdown("""
386
  ---
387
- **Improved Search Strategy:**
388
-
389
- βœ… **Phase 1: Country-Level Search (Primary)**
390
- - Searches: "United Arab Emirates", "UAE", "U.A.E", "Emirates"
391
- - Multiple sort orders: followers, repositories, recently joined
392
- - Catches users regardless of city
393
-
394
- βœ… **Phase 2: City-Level Search (Supplementary)**
395
- - Searches: Dubai, Abu Dhabi, Sharjah, Ajman
396
- - Only if needed to reach target count
397
-
398
- βœ… **Smart Location Verification**
399
- - Validates location field contains UAE/Emirates/city keywords
400
- - Filters out false positives
401
-
402
- βœ… **Comprehensive Coverage**
403
- - Multiple search variations and sort methods
404
- - Automatic deduplication
405
- - Up to 200 API calls for thorough search
406
-
407
- **Ranking:**
408
- - Primary: Contributions in last N days (accurate count from GitHub GraphQL)
409
- - Secondary: Follower count
410
-
411
- **Token Setup:**
412
- 1. [Generate token](https://github.com/settings/tokens) with `read:user` scope
413
- 2. Paste above or set `GITHUB_TOKEN` environment variable
414
-
415
- **Tips:**
416
- - Set "Minimum Followers" to 0 for most comprehensive results
417
- - Increase "Max Contributors" to 300-500 for broader coverage
418
- - Takes 2-5 minutes depending on settings
 
 
 
419
  """)
420
 
421
- # Event handlers
422
  def fetch_and_display(token, max_users, min_followers, days):
423
  df, msg = fetch_github_users(token if token else None, int(max_users), int(min_followers), int(days))
424
  return df, df, msg
@@ -449,6 +487,5 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
449
  outputs=[search_box, data_display]
450
  )
451
 
452
- # Launch the app
453
  if __name__ == "__main__":
454
  app.launch()
 
5
  import time
6
  import os
7
 
8
+ def get_contribution_count_graphql(username, github_token, days=30):
9
  """
10
+ Get ACCURATE contribution count using GitHub GraphQL API with proper date range
11
  """
12
  if not github_token:
13
+ return 0, "No token provided"
14
 
15
+ # Calculate exact date range
16
  end_date = datetime.now()
17
  start_date = end_date - timedelta(days=days)
18
 
19
+ # Format dates in ISO format with timezone
20
+ from_date = start_date.strftime('%Y-%m-%dT%H:%M:%SZ')
21
+ to_date = end_date.strftime('%Y-%m-%dT%H:%M:%SZ')
22
+
23
+ # GraphQL query - get detailed contribution data
24
  query = """
25
  query($username: String!, $from: DateTime!, $to: DateTime!) {
26
  user(login: $username) {
27
  contributionsCollection(from: $from, to: $to) {
28
  contributionCalendar {
29
  totalContributions
30
+ weeks {
31
+ contributionDays {
32
+ contributionCount
33
+ date
34
+ }
35
+ }
36
  }
37
+ totalCommitContributions
38
+ totalIssueContributions
39
+ totalPullRequestContributions
40
+ totalPullRequestReviewContributions
41
  }
42
  }
43
  }
 
45
 
46
  variables = {
47
  "username": username,
48
+ "from": from_date,
49
+ "to": to_date
50
  }
51
 
52
  headers = {
 
58
  response = requests.post(
59
  'https://api.github.com/graphql',
60
  json={'query': query, 'variables': variables},
61
+ headers=headers,
62
+ timeout=10
63
  )
64
 
65
  if response.status_code == 200:
66
  data = response.json()
67
+
68
+ # Check for errors in response
69
+ if 'errors' in data:
70
+ return 0, f"GraphQL Error: {data['errors']}"
71
+
72
+ if 'data' in data and data['data'] and data['data']['user']:
73
+ contrib_collection = data['data']['user']['contributionsCollection']
74
+
75
+ # Get the total contributions from the calendar
76
+ total = contrib_collection['contributionCalendar']['totalContributions']
77
+
78
+ # Detailed breakdown (for debugging)
79
+ commits = contrib_collection.get('totalCommitContributions', 0)
80
+ issues = contrib_collection.get('totalIssueContributions', 0)
81
+ prs = contrib_collection.get('totalPullRequestContributions', 0)
82
+ reviews = contrib_collection.get('totalPullRequestReviewContributions', 0)
83
+
84
+ debug_info = f"Total: {total} (Commits: {commits}, Issues: {issues}, PRs: {prs}, Reviews: {reviews})"
85
+
86
+ return total, debug_info
87
+ else:
88
+ return 0, "User not found or no data"
89
+ else:
90
+ return 0, f"HTTP Error {response.status_code}: {response.text[:200]}"
91
+
92
  except Exception as e:
93
+ return 0, f"Exception: {str(e)}"
 
 
94
 
95
  def is_uae_location(location):
96
  """
 
141
 
142
  return False
143
 
144
+ def fetch_github_users(github_token=None, max_users=200, min_followers=0, days=30):
145
  """
146
+ Fetch GitHub users from UAE with ACCURATE contribution counts
147
  """
148
  # Read token from environment variable if not provided
149
  if not github_token:
150
  github_token = os.getenv('GITHUB_TOKEN')
151
 
152
  if not github_token:
153
+ return pd.DataFrame(), "❌ GitHub token is REQUIRED. Please provide a token with 'read:user' scope."
154
 
155
  headers = {
156
  'Accept': 'application/vnd.github.v3+json',
 
160
  all_users = {} # Use dict to avoid duplicates by login
161
  status_updates = []
162
  total_api_calls = 0
163
+ contribution_errors = []
164
 
165
  try:
166
+ # Country-level searches
167
  country_searches = [
168
  'United+Arab+Emirates',
169
  'UAE',
 
170
  '"United Arab Emirates"',
 
 
171
  ]
172
 
173
+ status_updates.append(f"πŸ” Phase 1: Country-level search (UAE)...")
174
+ status_updates.append(f"πŸ“… Counting contributions from last {days} days")
175
 
176
  for search_term in country_searches:
177
+ if total_api_calls >= 150:
178
  break
179
 
180
+ # Multiple sort methods to get diverse users
181
  sort_methods = [
182
+ ('followers', 'desc'),
183
+ ('repositories', 'desc'),
184
+ ('joined', 'desc'),
185
  ]
186
 
187
  for sort_by, order in sort_methods:
188
+ if total_api_calls >= 150:
189
  break
190
+
191
  search_url = f'https://api.github.com/search/users?q=location:{search_term}+followers:>={min_followers}&sort={sort_by}&order={order}&per_page=100'
192
 
193
  response = requests.get(search_url, headers=headers)
 
197
  data = response.json()
198
  users = data.get('items', [])
199
 
200
+ status_updates.append(f" πŸ“ Search '{search_term}' by {sort_by}: {len(users)} users")
201
 
202
  for user in users:
203
  if user['login'] not in all_users:
204
+ if total_api_calls >= 150:
205
  break
206
+
207
  # Fetch detailed user info
208
+ user_response = requests.get(user['url'], headers=headers)
 
209
  total_api_calls += 1
210
 
211
  if user_response.status_code == 200:
212
  user_data = user_response.json()
 
 
213
  user_location = user_data.get('location', '')
214
 
215
  if is_uae_location(user_location):
216
+ # Get ACCURATE contribution count via GraphQL
217
+ contributions, debug_info = get_contribution_count_graphql(
218
+ user['login'],
219
+ github_token,
220
+ days
221
+ )
222
+
223
+ if contributions == 0 and "Error" in debug_info:
224
+ contribution_errors.append(f"{user['login']}: {debug_info}")
225
 
226
  all_users[user['login']] = {
227
  'login': user_data.get('login', ''),
228
+ 'name': user_data.get('name') or user_data.get('login', ''),
229
  'avatar': user_data.get('avatar_url', ''),
230
  'followers': user_data.get('followers', 0),
231
  'public_repos': user_data.get('public_repos', 0),
 
233
  'location': user_data.get('location', ''),
234
  'bio': user_data.get('bio', ''),
235
  'company': user_data.get('company', ''),
236
+ 'debug_info': debug_info
237
  }
238
 
239
+ if len(all_users) % 20 == 0:
240
+ status_updates.append(f" βœ… Processed {len(all_users)} unique UAE users...")
241
 
242
+ time.sleep(0.8) # Rate limiting - be more conservative
243
 
244
+ time.sleep(1.5)
245
 
246
  elif response.status_code == 403:
247
+ status_updates.append(f"⚠️ Rate limit hit after {total_api_calls} calls")
248
  break
249
  elif response.status_code == 422:
 
250
  continue
251
  else:
252
+ status_updates.append(f" ❌ Search error: {response.status_code}")
253
 
254
+ # Phase 2: Major cities
255
+ if len(all_users) < max_users * 1.5 and total_api_calls < 130:
256
+ status_updates.append(f"πŸ” Phase 2: Major city searches...")
257
 
258
+ cities = ['Dubai', 'Abu+Dhabi', 'Sharjah']
 
 
 
 
 
259
 
260
+ for city in cities:
261
+ if total_api_calls >= 150:
262
  break
263
 
264
+ search_url = f'https://api.github.com/search/users?q=location:{city}+followers:>={min_followers}&sort=followers&order=desc&per_page=50'
 
265
  response = requests.get(search_url, headers=headers)
266
  total_api_calls += 1
267
 
268
  if response.status_code == 200:
269
+ users = response.json().get('items', [])
 
270
 
271
+ for user in users[:25]:
272
+ if user['login'] not in all_users and total_api_calls < 150:
273
+ user_response = requests.get(user['url'], headers=headers)
 
274
  total_api_calls += 1
275
 
276
  if user_response.status_code == 200:
277
  user_data = user_response.json()
278
+ if is_uae_location(user_data.get('location', '')):
279
+ contributions, debug_info = get_contribution_count_graphql(
280
+ user['login'], github_token, days
281
+ )
282
 
283
  all_users[user['login']] = {
284
  'login': user_data.get('login', ''),
285
+ 'name': user_data.get('name') or user_data.get('login', ''),
286
  'avatar': user_data.get('avatar_url', ''),
287
  'followers': user_data.get('followers', 0),
288
  'public_repos': user_data.get('public_repos', 0),
 
290
  'location': user_data.get('location', ''),
291
  'bio': user_data.get('bio', ''),
292
  'company': user_data.get('company', ''),
293
+ 'debug_info': debug_info
294
  }
295
 
296
+ time.sleep(0.8)
297
+ time.sleep(1.5)
 
298
 
299
+ # Convert and sort
300
  users_list = list(all_users.values())
301
+ status_updates.append(f"πŸ“Š Found {len(users_list)} unique UAE developers")
302
 
303
+ # Sort by contributions (primary), then followers (secondary)
 
 
304
  users_list.sort(key=lambda x: (x['contributions'], x['followers']), reverse=True)
305
 
306
+ # Take top N
307
  top_users = users_list[:max_users]
308
 
309
+ # Add rank
310
  for i, user in enumerate(top_users, 1):
311
  user['rank'] = i
312
 
313
+ # Create DataFrame
314
  df = pd.DataFrame(top_users)
315
 
316
  if not df.empty:
 
321
  status_message = f"""βœ… Successfully fetched top {len(df)} contributors (last {days} days)
322
  πŸ“Š Total unique users found: {len(all_users)}
323
  πŸ”§ API calls made: {total_api_calls}
324
+ πŸ“… Date range: {datetime.now() - timedelta(days=days)} to {datetime.now()}
325
+
326
+ Recent status:
327
+ """ + "\n".join(status_updates[-6:])
328
+
329
+ if contribution_errors:
330
+ status_message += f"\n\n⚠️ Contribution fetch errors: {len(contribution_errors)}"
331
+ status_message += f"\n(First 3: {', '.join(contribution_errors[:3])})"
332
+
333
  return display_df, status_message
334
  else:
335
  return pd.DataFrame(), "⚠️ No users found\n" + "\n".join(status_updates)
 
341
  """Filter users based on search term"""
342
  if df is None or df.empty:
343
  return df
 
344
  if not search_term:
345
  return df
346
 
 
356
  with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as app:
357
 
358
  gr.Markdown("""
359
+ # πŸ‡¦πŸ‡ͺ GitHub UAE Top Contributors Leaderboard
360
+ ### Accurate Contribution Counting via GitHub GraphQL API
361
+
362
+ **This tool ranks UAE-based developers by their actual contribution count from GitHub's official API.**
363
 
364
+ ⚠️ **IMPORTANT:** You MUST provide a GitHub Personal Access Token with `read:user` scope for accurate contribution counts.
 
365
  """)
366
 
367
  with gr.Row():
368
  token_input = gr.Textbox(
369
+ label="GitHub Personal Access Token (REQUIRED - needs 'read:user' scope)",
370
+ placeholder="ghp_xxxxxxxxxxxx or set GITHUB_TOKEN environment variable",
371
  type="password",
372
  scale=3
373
  )
 
382
 
383
  with gr.Row():
384
  min_followers_input = gr.Slider(
385
+ label="Minimum Followers (0 = everyone)",
386
  minimum=0,
387
  maximum=50,
388
  value=0,
389
  step=1
390
  )
391
  days_input = gr.Slider(
392
+ label="Days to Count (30 = last 30 days)",
393
  minimum=7,
394
  maximum=90,
395
  value=30,
 
397
  )
398
 
399
  with gr.Row():
400
+ fetch_btn = gr.Button("πŸš€ Fetch Top Contributors", variant="primary", size="lg")
401
 
402
+ status_msg = gr.Textbox(label="Status & Debug Info", interactive=False, lines=8)
403
 
404
  with gr.Row():
405
  search_box = gr.Textbox(
406
+ label="πŸ” Search Results",
407
+ placeholder="Search by name, username, or location...",
408
  scale=4
409
  )
410
  clear_btn = gr.Button("Clear", scale=1)
411
 
 
412
  full_data = gr.State(value=pd.DataFrame())
413
 
 
414
  data_display = gr.Dataframe(
415
  headers=["Rank", "Name", "Username", "Contributions", "Followers", "Public Repos", "Location", "GitHub Profile"],
416
  datatype=["number", "str", "str", "number", "number", "number", "str", "str"],
 
420
 
421
  gr.Markdown("""
422
  ---
423
+ ## How It Works:
424
+
425
+ **Data Source:** GitHub GraphQL API (v4) - same data shown on user profiles
426
+
427
+ **Contribution Types Counted:**
428
+ - Commits to any branch
429
+ - Pull requests opened
430
+ - Issues opened
431
+ - Pull request reviews
432
+ - Repository creation and more
433
+
434
+ **Search Strategy:**
435
+ 1. **Country search:** "United Arab Emirates", "UAE" (primary)
436
+ 2. **City search:** Dubai, Abu Dhabi, Sharjah (supplementary)
437
+ 3. **Location verification:** Confirms UAE location in profile
438
+ 4. **Multiple sort orders:** Followers, repos, recently joined
439
+
440
+ **Ranking:** Primary by contributions (last N days), secondary by followers
441
+
442
+ ---
443
+
444
+ ## Setup GitHub Token:
445
+
446
+ 1. Go to: https://github.com/settings/tokens
447
+ 2. Click "Generate new token (classic)"
448
+ 3. Name it: "UAE Contributors Tracker"
449
+ 4. **Check the `read:user` scope** (REQUIRED for GraphQL API)
450
+ 5. Generate and copy the token
451
+ 6. Paste above OR set environment variable: `export GITHUB_TOKEN="ghp_..."`
452
+
453
+ ---
454
+
455
+ **Processing Time:** 2-5 minutes depending on settings
456
+
457
+ **Note:** If you see 0 contributions for highly active users, there may be a GraphQL API error (check status output).
458
  """)
459
 
 
460
  def fetch_and_display(token, max_users, min_followers, days):
461
  df, msg = fetch_github_users(token if token else None, int(max_users), int(min_followers), int(days))
462
  return df, df, msg
 
487
  outputs=[search_box, data_display]
488
  )
489
 
 
490
  if __name__ == "__main__":
491
  app.launch()