Pranesh64 commited on
Commit
8641578
Β·
verified Β·
1 Parent(s): 86c086e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +418 -84
app.py CHANGED
@@ -97,6 +97,186 @@ def send_email(to, subject, html):
97
  return False
98
 
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  # ================= VALIDATION =================
101
 
102
  EMAIL_REGEX = re.compile(
@@ -127,19 +307,42 @@ def valid_leetcode(username):
127
  # ================= LEETCODE =================
128
 
129
  def get_daily_problem():
 
 
 
130
 
131
- r = requests.get(f"{LEETCODE_API}/daily", timeout=10)
132
- r.raise_for_status()
133
-
134
- d = r.json()
135
-
136
- if "questionTitle" in d:
137
- return d["questionTitle"], d["titleSlug"]
138
-
139
- if "title" in d:
140
- return d["title"], d["titleSlug"]
141
-
142
- raise Exception("Daily API invalid")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
 
145
  def solved_today(username, slug):
@@ -213,9 +416,8 @@ def subscribe(username, email, timezone):
213
 
214
  row = cur.fetchone()
215
 
216
- # Existing
217
  if row:
218
-
219
  verified, token, unsub = row
220
 
221
  if verified and not unsub:
@@ -224,7 +426,6 @@ def subscribe(username, email, timezone):
224
  return "⚠️ Already subscribed"
225
 
226
  if verified and unsub:
227
-
228
  cur.execute("""
229
  UPDATE users
230
  SET unsubscribed=false,
@@ -238,24 +439,32 @@ def subscribe(username, email, timezone):
238
  conn.commit()
239
  cur.close()
240
  conn.close()
241
-
242
  return "βœ… Re-subscribed"
243
 
244
- # resend verify
245
- link = f"{HF_URL}?verify={token}"
246
-
247
- send_email(
248
- email,
249
- "Verify Subscription",
250
- f"<a href='{link}'>Verify</a>"
 
 
 
 
 
 
 
 
 
251
  )
252
 
 
253
  cur.close()
254
  conn.close()
255
-
256
  return "πŸ“© Verification re-sent"
257
 
258
- # New
259
  token = uuid.uuid4().hex
260
 
261
  cur.execute("""
@@ -270,14 +479,33 @@ def subscribe(username, email, timezone):
270
  cur.close()
271
  conn.close()
272
 
273
- link = f"{HF_URL}?verify={token}"
274
-
275
- send_email(
276
- email,
277
- "Verify Subscription",
278
- f"<a href='{link}'>Verify</a>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  )
280
 
 
281
  return "πŸ“© Verification sent"
282
 
283
 
@@ -352,7 +580,6 @@ def handle_url(request: gr.Request):
352
  return ""
353
 
354
 
355
- # ================= SCHEDULER =================
356
 
357
  def run_scheduler(secret):
358
 
@@ -372,87 +599,194 @@ def run_scheduler(secret):
372
 
373
  users = cur.fetchall()
374
 
375
- title, slug = get_daily_problem()
 
 
 
 
 
376
 
377
  now = datetime.now(pytz.utc)
378
-
379
  sent = 0
380
 
381
  for uid, user, mail, tz, last_d, last_s, token in users:
382
 
383
- local = now.astimezone(pytz.timezone(tz))
384
- h = local.hour
385
-
386
- if 8 <= h <= 9:
387
- slot="morning"; sub="Today's LeetCode"; body=f"<b>{title}</b>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
- elif 14 <= h <= 15:
390
- slot="afternoon"; sub="Reminder"; body=f"Solve <b>{title}</b>"
391
 
392
- elif 19 <= h <= 20:
393
- slot="night"; sub="Final Reminder"; body=f"Last chance <b>{title}</b>"
 
 
394
 
395
- else:
396
- continue
 
 
397
 
398
- today = date.today()
 
 
 
 
 
 
 
 
399
 
400
- if last_d == today and last_s == slot:
401
- continue
402
 
403
- if solved_today(user, slug):
404
- continue
 
405
 
406
- unsub = f"{HF_URL}?unsubscribe={token}"
 
 
 
 
 
 
407
 
408
- ok = send_email(
409
- mail,
410
- sub,
411
- f"{body}<br><a href='{unsub}'>Unsubscribe</a>"
412
- )
413
 
414
- if not ok:
 
415
  continue
416
 
417
- cur.execute("""
418
- UPDATE users
419
- SET last_sent_date=%s,
420
- last_sent_slot=%s
421
- WHERE id=%s
422
- """, (today, slot, uid))
423
-
424
- sent += 1
425
-
426
  conn.commit()
427
  cur.close()
428
  conn.close()
429
 
430
- return f"βœ… Scheduler completed. Sent: {sent}"
431
-
432
-
433
- # ================= UI =================
434
-
435
- with gr.Blocks(title="LeetCode Notifier") as app:
436
 
437
- gr.Markdown("## πŸ“¬ LeetCode Daily Email Notifier")
438
 
439
- u = gr.Textbox(label="Username")
440
- m = gr.Textbox(label="Email")
441
- tz = gr.Dropdown(pytz.all_timezones, value="Asia/Kolkata")
442
 
443
- out = gr.Textbox(label="Status")
444
-
445
- gr.Button("Subscribe").click(subscribe, [u, m, tz], out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
 
447
- gr.Markdown("### πŸ”’ Scheduler")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
 
449
- sec = gr.Textbox(type="password")
 
 
 
 
 
 
450
 
451
- gr.Button("Run").click(run_scheduler, sec, out)
452
 
453
- # βœ… URL handler
454
  app.load(handle_url, outputs=out)
455
 
456
 
457
  if __name__ == "__main__":
458
- app.launch(debug=True)
 
97
  return False
98
 
99
 
100
+ def create_email_template(title, content, unsubscribe_link, email_type="reminder", problem_link=None, difficulty=None):
101
+ """Create a beautiful HTML email template"""
102
+
103
+ # Color scheme based on email type
104
+ colors = {
105
+ "morning": {"primary": "#4CAF50", "secondary": "#81C784", "bg": "#E8F5E8"},
106
+ "afternoon": {"primary": "#FF9800", "secondary": "#FFB74D", "bg": "#FFF3E0"},
107
+ "night": {"primary": "#F44336", "secondary": "#EF5350", "bg": "#FFEBEE"},
108
+ "verification": {"primary": "#2196F3", "secondary": "#64B5F6", "bg": "#E3F2FD"}
109
+ }
110
+
111
+ color = colors.get(email_type, colors["morning"])
112
+
113
+ # Difficulty badge colors
114
+ difficulty_colors = {
115
+ "Easy": "#00B04F",
116
+ "Medium": "#FFA116",
117
+ "Hard": "#FF375F"
118
+ }
119
+
120
+ # Handle special cases for verification emails
121
+ if email_type == "verification":
122
+ problem_button = f"""
123
+ <div style="text-align: center; margin: 30px 0;">
124
+ <a href="{unsubscribe_link}"
125
+ style="display: inline-block; background: linear-gradient(135deg, {color['primary']}, {color['secondary']});
126
+ color: white; padding: 15px 30px; text-decoration: none; border-radius: 25px;
127
+ font-weight: bold; font-size: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);
128
+ transition: transform 0.2s;">
129
+ βœ… Verify Email
130
+ </a>
131
+ </div>
132
+ """
133
+ tips_section = ""
134
+ motivation_section = ""
135
+ difficulty_badge = ""
136
+ else:
137
+ # Use provided problem_link or construct from title
138
+ if problem_link:
139
+ link_url = problem_link
140
+ else:
141
+ link_url = f"https://leetcode.com/problems/{title.lower().replace(' ', '-').replace('.', '')}"
142
+
143
+ # Add difficulty badge
144
+ if difficulty:
145
+ diff_color = difficulty_colors.get(difficulty, "#666")
146
+ difficulty_badge = f"""
147
+ <div style="text-align: center; margin: 10px 0;">
148
+ <span style="background-color: {diff_color}; color: white; padding: 4px 12px;
149
+ border-radius: 12px; font-size: 12px; font-weight: bold;">
150
+ {difficulty}
151
+ </span>
152
+ </div>
153
+ """
154
+ else:
155
+ difficulty_badge = ""
156
+
157
+ problem_button = f"""
158
+ <div style="text-align: center; margin: 30px 0;">
159
+ <a href="{link_url}"
160
+ style="display: inline-block; background: linear-gradient(135deg, {color['primary']}, {color['secondary']});
161
+ color: white; padding: 15px 30px; text-decoration: none; border-radius: 25px;
162
+ font-weight: bold; font-size: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);
163
+ transition: transform 0.2s;">
164
+ πŸš€ Solve Problem
165
+ </a>
166
+ </div>
167
+ """
168
+
169
+ # Dynamic tips based on difficulty
170
+ if difficulty == "Easy":
171
+ tips_content = """
172
+ <li>Focus on understanding the problem clearly</li>
173
+ <li>Think about the simplest approach first</li>
174
+ <li>Test with the given examples</li>
175
+ <li>Consider edge cases like empty inputs</li>
176
+ """
177
+ elif difficulty == "Medium":
178
+ tips_content = """
179
+ <li>Break the problem into smaller subproblems</li>
180
+ <li>Consider multiple approaches (greedy, DP, etc.)</li>
181
+ <li>Think about time and space complexity</li>
182
+ <li>Use appropriate data structures</li>
183
+ """
184
+ else: # Hard
185
+ tips_content = """
186
+ <li>Study similar problems and patterns</li>
187
+ <li>Don't rush - take time to understand</li>
188
+ <li>Consider advanced algorithms and techniques</li>
189
+ <li>Break it down step by step</li>
190
+ """
191
+
192
+ tips_section = f"""
193
+ <!-- Tips Section -->
194
+ <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 25px 0;">
195
+ <h3 style="color: #333; margin: 0 0 15px 0; font-size: 18px;">πŸ’‘ {difficulty} Problem Tips</h3>
196
+ <ul style="color: #666; margin: 0; padding-left: 20px; line-height: 1.6;">
197
+ {tips_content}
198
+ </ul>
199
+ </div>
200
+ """
201
+
202
+ motivation_quotes = [
203
+ "The expert in anything was once a beginner.",
204
+ "Every problem is a step forward in your journey.",
205
+ "Consistency beats perfection every time.",
206
+ "Code today, conquer tomorrow.",
207
+ "Small progress is still progress."
208
+ ]
209
+ import random
210
+ quote = random.choice(motivation_quotes)
211
+
212
+ motivation_section = f"""
213
+ <!-- Stats or Motivation -->
214
+ <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; margin: 25px 0;">
215
+ <p style="color: white; margin: 0; font-size: 16px; font-style: italic;">
216
+ "{quote}" πŸ’ͺ
217
+ </p>
218
+ </div>
219
+ """
220
+
221
+ return f"""
222
+ <!DOCTYPE html>
223
+ <html>
224
+ <head>
225
+ <meta charset="UTF-8">
226
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
227
+ <title>LeetCode Daily Tracker</title>
228
+ </head>
229
+ <body style="margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f5f5;">
230
+ <div style="max-width: 600px; margin: 0 auto; background-color: white; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
231
+
232
+ <!-- Header -->
233
+ <div style="background: linear-gradient(135deg, {color['primary']}, {color['secondary']}); padding: 30px; text-align: center;">
234
+ <h1 style="color: white; margin: 0; font-size: 28px; font-weight: 300;">
235
+ 🧠 LeetCode Daily Tracker
236
+ </h1>
237
+ <p style="color: rgba(255,255,255,0.9); margin: 10px 0 0 0; font-size: 16px;">
238
+ Your coding journey companion
239
+ </p>
240
+ </div>
241
+
242
+ <!-- Content -->
243
+ <div style="padding: 40px 30px;">
244
+ <div style="background-color: {color['bg']}; border-left: 4px solid {color['primary']}; padding: 20px; margin-bottom: 25px; border-radius: 0 8px 8px 0;">
245
+ {content}
246
+ {difficulty_badge}
247
+ </div>
248
+
249
+ {problem_button}
250
+
251
+ {tips_section}
252
+
253
+ {motivation_section}
254
+ </div>
255
+
256
+ <!-- Footer -->
257
+ <div style="background-color: #f8f9fa; padding: 25px 30px; border-top: 1px solid #eee;">
258
+ <div style="text-align: center;">
259
+ <p style="color: #666; margin: 0 0 15px 0; font-size: 14px;">
260
+ Keep coding, keep growing! 🌱
261
+ </p>
262
+ <div style="margin: 15px 0;">
263
+ <a href="https://leetcode.com" style="color: {color['primary']}; text-decoration: none; margin: 0 10px;">πŸ“Š LeetCode</a>
264
+ <span style="color: #ccc;">|</span>
265
+ <a href="https://github.com" style="color: {color['primary']}; text-decoration: none; margin: 0 10px;">πŸ’» GitHub</a>
266
+ <span style="color: #ccc;">|</span>
267
+ <a href="{unsubscribe_link if email_type != 'verification' else '#'}" style="color: #999; text-decoration: none; margin: 0 10px; font-size: 12px;">{'Unsubscribe' if email_type != 'verification' else ''}</a>
268
+ </div>
269
+ <p style="color: #999; font-size: 12px; margin: 15px 0 0 0;">
270
+ © 2024 LeetCode Daily Tracker. Made with ❀️ for coders.
271
+ </p>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ </body>
276
+ </html>
277
+ """
278
+
279
+
280
  # ================= VALIDATION =================
281
 
282
  EMAIL_REGEX = re.compile(
 
307
  # ================= LEETCODE =================
308
 
309
  def get_daily_problem():
310
+ try:
311
+ r = requests.get(f"{LEETCODE_API}/daily", timeout=10)
312
+ r.raise_for_status()
313
 
314
+ d = r.json()
315
+ print("πŸ“‘ Daily API response keys:", list(d.keys()))
316
+
317
+ # Handle the current API format
318
+ if "questionTitle" in d and "titleSlug" in d:
319
+ title = d["questionTitle"]
320
+ slug = d["titleSlug"]
321
+ link = d.get("questionLink", f"https://leetcode.com/problems/{slug}/")
322
+ difficulty = d.get("difficulty", "Unknown")
323
+ print(f"βœ… Found daily problem: {title} ({difficulty}) - {slug}")
324
+ return title, slug, link, difficulty
325
+
326
+ # Fallback for older format
327
+ if "title" in d and "titleSlug" in d:
328
+ title = d["title"]
329
+ slug = d["titleSlug"]
330
+ link = f"https://leetcode.com/problems/{slug}/"
331
+ difficulty = d.get("difficulty", "Unknown")
332
+ print(f"βœ… Found daily problem (fallback): {title} ({difficulty}) - {slug}")
333
+ return title, slug, link, difficulty
334
+
335
+ # If we can't find the expected fields, print the response
336
+ print("❌ Available fields in API response:", list(d.keys()))
337
+ raise Exception("Could not find title and slug in daily API response")
338
+
339
+ except requests.exceptions.RequestException as e:
340
+ print(f"❌ Network error calling daily API: {e}")
341
+ raise Exception(f"Failed to fetch daily problem: {e}")
342
+
343
+ except json.JSONDecodeError as e:
344
+ print(f"❌ JSON decode error: {e}")
345
+ raise Exception("Invalid JSON response from daily API")
346
 
347
 
348
  def solved_today(username, slug):
 
416
 
417
  row = cur.fetchone()
418
 
419
+ # Existing user logic...
420
  if row:
 
421
  verified, token, unsub = row
422
 
423
  if verified and not unsub:
 
426
  return "⚠️ Already subscribed"
427
 
428
  if verified and unsub:
 
429
  cur.execute("""
430
  UPDATE users
431
  SET unsubscribed=false,
 
439
  conn.commit()
440
  cur.close()
441
  conn.close()
 
442
  return "βœ… Re-subscribed"
443
 
444
+ # Resend verification with enhanced template
445
+ verification_content = f"""
446
+ <h2 style="color: #2196F3; margin: 0 0 15px 0;">Welcome back! πŸ‘‹</h2>
447
+ <p style="color: #333; font-size: 16px; line-height: 1.6; margin: 0 0 15px 0;">
448
+ We're excited to have you on your coding journey again!
449
+ </p>
450
+ <p style="color: #666; font-size: 14px; margin: 0;">
451
+ Click the verification button above to activate your daily LeetCode reminders.
452
+ </p>
453
+ """
454
+
455
+ html_email = create_email_template(
456
+ "Email Verification",
457
+ verification_content,
458
+ f"{HF_URL}?verify={token}",
459
+ "verification"
460
  )
461
 
462
+ send_email(email, "πŸ”” Please verify your email", html_email)
463
  cur.close()
464
  conn.close()
 
465
  return "πŸ“© Verification re-sent"
466
 
467
+ # New user - remove duplicate code
468
  token = uuid.uuid4().hex
469
 
470
  cur.execute("""
 
479
  cur.close()
480
  conn.close()
481
 
482
+ # Welcome email with enhanced template
483
+ welcome_content = f"""
484
+ <h2 style="color: #4CAF50; margin: 0 0 15px 0;">Welcome to the club! πŸŽ‰</h2>
485
+ <p style="color: #333; font-size: 16px; line-height: 1.6; margin: 0 0 15px 0;">
486
+ You're about to embark on an amazing coding journey with daily LeetCode challenges!
487
+ </p>
488
+ <div style="background-color: white; border: 2px dashed #4CAF50; padding: 15px; border-radius: 8px; margin: 15px 0;">
489
+ <p style="color: #4CAF50; font-weight: bold; margin: 0 0 5px 0;">πŸ“… Your Schedule:</p>
490
+ <p style="color: #666; font-size: 14px; margin: 0;">
491
+ πŸŒ… <strong>9 AM</strong> - Daily problem<br>
492
+ πŸŒ† <strong>3 PM</strong> - Gentle reminder<br>
493
+ πŸŒ™ <strong>8 PM</strong> - Final reminder
494
+ </p>
495
+ </div>
496
+ <p style="color: #666; font-size: 14px; margin: 0;">
497
+ Click the verification button above to start receiving your personalized reminders!
498
+ </p>
499
+ """
500
+
501
+ html_email = create_email_template(
502
+ "Welcome",
503
+ welcome_content,
504
+ f"{HF_URL}?verify={token}",
505
+ "verification"
506
  )
507
 
508
+ send_email(email, "🎯 Verify your LeetCode journey!", html_email)
509
  return "πŸ“© Verification sent"
510
 
511
 
 
580
  return ""
581
 
582
 
 
583
 
584
  def run_scheduler(secret):
585
 
 
599
 
600
  users = cur.fetchall()
601
 
602
+ try:
603
+ title, slug, problem_link, difficulty = get_daily_problem()
604
+ except Exception as e:
605
+ cur.close()
606
+ conn.close()
607
+ return f"❌ Failed to get daily problem: {e}"
608
 
609
  now = datetime.now(pytz.utc)
 
610
  sent = 0
611
 
612
  for uid, user, mail, tz, last_d, last_s, token in users:
613
 
614
+ try:
615
+ local = now.astimezone(pytz.timezone(tz))
616
+ h = local.hour
617
+
618
+ # Enhanced email content based on time
619
+ if 8 <= h <= 9:
620
+ slot = "morning"
621
+ subject = f"πŸŒ… Today's LeetCode Challenge: {title}"
622
+ content = f"""
623
+ <h2 style="color: #4CAF50; margin: 0 0 15px 0;">Good morning, coder! β˜€οΈ</h2>
624
+ <p style="color: #333; font-size: 18px; font-weight: bold; margin: 0 0 10px 0;">
625
+ Today's Problem: <span style="color: #4CAF50;">{title}</span>
626
+ </p>
627
+ <p style="color: #666; font-size: 16px; line-height: 1.6; margin: 0;">
628
+ Start your day with a fresh challenge! This {difficulty} problem is perfect for warming up
629
+ your coding muscles. Take your time to understand the requirements! πŸš€
630
+ </p>
631
+ """
632
+ email_type = "morning"
633
+
634
+ elif 14 <= h <= 15:
635
+ slot = "afternoon"
636
+ subject = f"⏰ Afternoon Coding Break: {title}"
637
+ content = f"""
638
+ <h2 style="color: #FF9800; margin: 0 0 15px 0;">Time for a coding break! ⚑</h2>
639
+ <p style="color: #333; font-size: 16px; margin: 0 0 15px 0;">
640
+ Haven't tackled <strong style="color: #FF9800;">{title}</strong> yet? No worries!
641
+ </p>
642
+ <p style="color: #666; font-size: 16px; line-height: 1.6; margin: 0;">
643
+ This {difficulty} problem is waiting for you. Sometimes a fresh afternoon perspective
644
+ can lead to breakthrough solutions! πŸ’‘
645
+ </p>
646
+ """
647
+ email_type = "afternoon"
648
+
649
+ elif 19 <= h <= 20:
650
+ slot = "night"
651
+ subject = f"πŸŒ™ Last Call: {title}"
652
+ content = f"""
653
+ <h2 style="color: #F44336; margin: 0 0 15px 0;">Final reminder! πŸ”₯</h2>
654
+ <p style="color: #333; font-size: 16px; margin: 0 0 15px 0;">
655
+ <strong style="color: #F44336;">{title}</strong> ({difficulty}) is still waiting for you!
656
+ </p>
657
+ <p style="color: #666; font-size: 16px; line-height: 1.6; margin: 0 0 15px 0;">
658
+ Don't let the day end without giving it a try. Even reading through the problem
659
+ and thinking about approaches counts as progress!
660
+ </p>
661
+ <div style="background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 8px;">
662
+ <p style="color: #856404; margin: 0; font-size: 14px;">
663
+ πŸ’ͺ <strong>Remember:</strong> Consistency beats perfection. Every attempt makes you stronger!
664
+ </p>
665
+ </div>
666
+ """
667
+ email_type = "night"
668
+
669
+ else:
670
+ continue
671
 
672
+ today = date.today()
 
673
 
674
+ # Check if already sent today
675
+ if last_d == today and last_s == slot:
676
+ print(f"⏭️ Skipping {mail} - already sent {slot} email today")
677
+ continue
678
 
679
+ # Check if user already solved the problem
680
+ if solved_today(user, slug):
681
+ print(f"βœ… {user} already solved {slug} - skipping email")
682
+ continue
683
 
684
+ # Create beautiful HTML email with all enhancements
685
+ html_email = create_email_template(
686
+ title,
687
+ content,
688
+ f"{HF_URL}?unsubscribe={token}",
689
+ email_type,
690
+ problem_link,
691
+ difficulty
692
+ )
693
 
694
+ ok = send_email(mail, subject, html_email)
 
695
 
696
+ if not ok:
697
+ print(f"❌ Failed to send email to {mail}")
698
+ continue
699
 
700
+ # Update database
701
+ cur.execute("""
702
+ UPDATE users
703
+ SET last_sent_date=%s,
704
+ last_sent_slot=%s
705
+ WHERE id=%s
706
+ """, (today, slot, uid))
707
 
708
+ sent += 1
 
 
 
 
709
 
710
+ except Exception as e:
711
+ print(f"❌ Error processing user {user} ({mail}): {e}")
712
  continue
713
 
 
 
 
 
 
 
 
 
 
714
  conn.commit()
715
  cur.close()
716
  conn.close()
717
 
718
+ return f"βœ… Scheduler completed. Sent: {sent} emails"
 
 
 
 
 
719
 
 
720
 
 
 
 
721
 
722
+ # ================= UI =================
723
+ with gr.Blocks(
724
+ title="LeetCode Notifier",
725
+ theme=gr.themes.Soft(),
726
+ css="""
727
+ .gradio-container {
728
+ max-width: 800px !important;
729
+ margin: auto !important;
730
+ }
731
+ """
732
+ ) as app:
733
+
734
+ gr.Markdown("""
735
+ # πŸ“¬ LeetCode Daily Email Notifier
736
+
737
+ Get personalized daily LeetCode problem reminders sent directly to your inbox!
738
+ Never miss a day of coding practice.
739
+ """)
740
 
741
+ with gr.Row():
742
+ with gr.Column():
743
+ u = gr.Textbox(
744
+ label="πŸ§‘β€πŸ’» LeetCode Username",
745
+ placeholder="Enter your LeetCode username",
746
+ info="We'll verify this username exists on LeetCode"
747
+ )
748
+ m = gr.Textbox(
749
+ label="πŸ“§ Email Address",
750
+ placeholder="your.email@gmail.com",
751
+ info="You'll receive a verification email"
752
+ )
753
+ tz = gr.Dropdown(
754
+ choices=sorted(pytz.all_timezones),
755
+ value="Asia/Kolkata",
756
+ label="🌍 Timezone",
757
+ info="Choose your timezone for proper scheduling"
758
+ )
759
+
760
+ with gr.Row():
761
+ subscribe_btn = gr.Button("πŸš€ Subscribe", variant="primary", scale=2)
762
+
763
+ out = gr.Textbox(label="πŸ“ Status", interactive=False, lines=2)
764
+
765
+ subscribe_btn.click(subscribe, [u, m, tz], out)
766
+
767
+ gr.Markdown("""
768
+ ---
769
+ ### ⏰ Email Schedule
770
+ - **πŸŒ… 9:00 AM** - Daily problem notification
771
+ - **πŸŒ† 3:00 PM** - Gentle reminder (if not solved)
772
+ - **πŸŒ™ 8:00 PM** - Final reminder (if not solved)
773
+
774
+ ### πŸ”’ Admin Panel
775
+ """)
776
 
777
+ with gr.Row():
778
+ sec = gr.Textbox(
779
+ label="πŸ”‘ Secret Key",
780
+ type="password",
781
+ placeholder="Enter scheduler secret key"
782
+ )
783
+ run_btn = gr.Button("▢️ Run Scheduler", variant="secondary")
784
 
785
+ run_btn.click(run_scheduler, sec, out)
786
 
787
+ # URL handler for verification/unsubscribe
788
  app.load(handle_url, outputs=out)
789
 
790
 
791
  if __name__ == "__main__":
792
+ app.launch(debug=True)