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

fixed email verification

Browse files
Files changed (1) hide show
  1. app.py +84 -88
app.py CHANGED
@@ -10,8 +10,8 @@ import pickle
10
  import base64
11
 
12
  from google.auth.transport.requests import Request
13
- from google.oauth2.credentials import Credentials
14
  from googleapiclient.discovery import build
 
15
 
16
  from email.mime.text import MIMEText
17
  from datetime import datetime, date
@@ -39,13 +39,12 @@ def get_db():
39
  return psycopg2.connect(DB_URL, sslmode="require")
40
 
41
 
42
- # ================= GMAIL AUTH =================
43
 
44
  def get_gmail_service():
45
 
46
  creds = None
47
 
48
- # Load token
49
  if os.path.exists(TOKEN_FILE):
50
  try:
51
  with open(TOKEN_FILE, "rb") as f:
@@ -53,7 +52,6 @@ def get_gmail_service():
53
  except:
54
  creds = None
55
 
56
- # Refresh token
57
  if creds and creds.expired and creds.refresh_token:
58
  try:
59
  creds.refresh(Request())
@@ -61,54 +59,48 @@ def get_gmail_service():
61
  with open(TOKEN_FILE, "wb") as f:
62
  pickle.dump(creds, f)
63
 
64
- except Exception as e:
65
- print("❌ Token refresh failed:", e)
66
  creds = None
67
 
68
  if not creds:
69
- raise Exception(
70
- "❌ Missing token.pkl. Generate locally and upload to HF."
71
- )
72
 
73
  return build("gmail", "v1", credentials=creds)
74
 
75
 
76
- # ================= EMAIL =================
77
-
78
  def send_email(to, subject, html):
79
 
80
  try:
81
  service = get_gmail_service()
82
 
83
- message = MIMEText(html, "html")
84
- message["To"] = to
85
- message["From"] = GMAIL_USER
86
- message["Subject"] = subject
87
 
88
  raw = base64.urlsafe_b64encode(
89
- message.as_bytes()
90
  ).decode()
91
 
92
  body = {"raw": raw}
93
 
94
- result = service.users().messages().send(
95
  userId="me",
96
  body=body
97
  ).execute()
98
 
99
- print("βœ… Gmail sent:", to, "| ID:", result.get("id"))
100
-
101
  return True
102
 
103
  except Exception as e:
104
- print("❌ Gmail send failed:", e)
105
  return False
106
 
107
 
108
  # ================= VALIDATION =================
109
 
110
  EMAIL_REGEX = re.compile(
111
- r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
112
  )
113
 
114
 
@@ -126,7 +118,6 @@ def valid_leetcode(username):
126
  f"{LEETCODE_API}/{username}",
127
  timeout=8
128
  )
129
-
130
  return r.status_code == 200
131
 
132
  except:
@@ -140,15 +131,15 @@ def get_daily_problem():
140
  r = requests.get(f"{LEETCODE_API}/daily", timeout=10)
141
  r.raise_for_status()
142
 
143
- data = r.json()
144
 
145
- if "questionTitle" in data:
146
- return data["questionTitle"], data["titleSlug"]
147
 
148
- if "title" in data:
149
- return data["title"], data["titleSlug"]
150
 
151
- raise ValueError("Invalid daily API format")
152
 
153
 
154
  def solved_today(username, slug):
@@ -156,29 +147,26 @@ def solved_today(username, slug):
156
  try:
157
  r = requests.get(
158
  f"{LEETCODE_API}/{username}/acSubmission?limit=20",
159
- timeout=10,
160
  )
161
 
162
  if r.status_code != 200:
163
  return False
164
 
165
- data = r.json()
166
-
167
- if isinstance(data, dict) and "submission" in data:
168
- submissions = data["submission"]
169
-
170
- elif isinstance(data, dict) and "data" in data:
171
- submissions = data["data"]
172
-
173
- elif isinstance(data, list):
174
- submissions = data
175
 
 
 
 
 
 
 
176
  else:
177
  return False
178
 
179
  today = date.today()
180
 
181
- for s in submissions:
182
 
183
  if not isinstance(s, dict):
184
  continue
@@ -191,18 +179,17 @@ def solved_today(username, slug):
191
  if not ts:
192
  continue
193
 
194
- solved_date = datetime.fromtimestamp(
195
  int(ts),
196
  tz=pytz.utc
197
  ).date()
198
 
199
- if solved_date == today:
200
  return True
201
 
202
  return False
203
 
204
- except Exception as e:
205
- print("⚠️ solved_today error:", e)
206
  return False
207
 
208
 
@@ -214,30 +201,28 @@ def subscribe(username, email, timezone):
214
  return "❌ Invalid LeetCode username"
215
 
216
  if not valid_email(email):
217
- return "❌ Invalid email format"
218
 
219
  conn = get_db()
220
  cur = conn.cursor()
221
 
222
  cur.execute("""
223
  SELECT email_verified, verification_token, unsubscribed
224
- FROM users
225
- WHERE email = %s
226
  """, (email,))
227
 
228
  row = cur.fetchone()
229
 
 
230
  if row:
231
 
232
  verified, token, unsub = row
233
 
234
- # Active
235
  if verified and not unsub:
236
  cur.close()
237
  conn.close()
238
  return "⚠️ Already subscribed"
239
 
240
- # Resubscribe
241
  if verified and unsub:
242
 
243
  cur.execute("""
@@ -254,14 +239,14 @@ def subscribe(username, email, timezone):
254
  cur.close()
255
  conn.close()
256
 
257
- return "βœ… Re-subscribed successfully!"
258
 
259
- # Resend verify
260
  link = f"{HF_URL}?verify={token}"
261
 
262
  send_email(
263
  email,
264
- "Verify your subscription",
265
  f"<a href='{link}'>Verify</a>"
266
  )
267
 
@@ -270,19 +255,15 @@ def subscribe(username, email, timezone):
270
 
271
  return "πŸ“© Verification re-sent"
272
 
273
- # New user
274
  token = uuid.uuid4().hex
275
 
276
  cur.execute("""
277
- INSERT INTO users (
278
- leetcode_username,
279
- email,
280
- timezone,
281
- email_verified,
282
- verification_token,
283
- unsubscribed
284
  )
285
- VALUES (%s,%s,%s,false,%s,false)
286
  """, (username, email, timezone, token))
287
 
288
  conn.commit()
@@ -293,7 +274,7 @@ def subscribe(username, email, timezone):
293
 
294
  send_email(
295
  email,
296
- "Verify your subscription",
297
  f"<a href='{link}'>Verify</a>"
298
  )
299
 
@@ -314,13 +295,13 @@ def verify_user(token):
314
  AND email_verified=false
315
  """, (token,))
316
 
317
- updated = cur.rowcount
318
 
319
  conn.commit()
320
  cur.close()
321
  conn.close()
322
 
323
- if updated == 0:
324
  return "❌ Invalid link"
325
 
326
  return "βœ… Email verified"
@@ -339,18 +320,38 @@ def unsubscribe_user(token):
339
  WHERE verification_token=%s
340
  """, (token,))
341
 
342
- updated = cur.rowcount
343
 
344
  conn.commit()
345
  cur.close()
346
  conn.close()
347
 
348
- if updated == 0:
349
  return "❌ Invalid link"
350
 
351
  return "βœ… Unsubscribed"
352
 
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  # ================= SCHEDULER =================
355
 
356
  def run_scheduler(secret):
@@ -362,9 +363,8 @@ def run_scheduler(secret):
362
  cur = conn.cursor()
363
 
364
  cur.execute("""
365
- SELECT id, leetcode_username, email, timezone,
366
- last_sent_date, last_sent_slot,
367
- verification_token
368
  FROM users
369
  WHERE email_verified=true
370
  AND unsubscribed=false
@@ -381,22 +381,16 @@ def run_scheduler(secret):
381
  for uid, user, mail, tz, last_d, last_s, token in users:
382
 
383
  local = now.astimezone(pytz.timezone(tz))
384
- hour = local.hour
385
 
386
- if hour == 9:
387
- slot = "morning"
388
- sub = "Today's LeetCode"
389
- body = f"<b>{title}</b>"
390
 
391
- elif hour == 15:
392
- slot = "afternoon"
393
- sub = "Reminder"
394
- body = f"Solve <b>{title}</b>"
395
 
396
- elif hour == 20:
397
- slot = "night"
398
- sub = "Final Reminder"
399
- body = f"Last chance: <b>{title}</b>"
400
 
401
  else:
402
  continue
@@ -418,7 +412,6 @@ def run_scheduler(secret):
418
  )
419
 
420
  if not ok:
421
- print("❌ Failed:", mail)
422
  continue
423
 
424
  cur.execute("""
@@ -443,13 +436,13 @@ with gr.Blocks(title="LeetCode Notifier") as app:
443
 
444
  gr.Markdown("## πŸ“¬ LeetCode Daily Email Notifier")
445
 
446
- user = gr.Textbox(label="Username")
447
- mail = gr.Textbox(label="Email")
448
  tz = gr.Dropdown(pytz.all_timezones, value="Asia/Kolkata")
449
 
450
- out = gr.Textbox()
451
 
452
- gr.Button("Subscribe").click(subscribe, [user, mail, tz], out)
453
 
454
  gr.Markdown("### πŸ”’ Scheduler")
455
 
@@ -457,6 +450,9 @@ with gr.Blocks(title="LeetCode Notifier") as app:
457
 
458
  gr.Button("Run").click(run_scheduler, sec, out)
459
 
 
 
 
460
 
461
  if __name__ == "__main__":
462
- app.launch(debug=True)
 
10
  import base64
11
 
12
  from google.auth.transport.requests import Request
 
13
  from googleapiclient.discovery import build
14
+ from google.oauth2.credentials import Credentials
15
 
16
  from email.mime.text import MIMEText
17
  from datetime import datetime, date
 
39
  return psycopg2.connect(DB_URL, sslmode="require")
40
 
41
 
42
+ # ================= GMAIL =================
43
 
44
  def get_gmail_service():
45
 
46
  creds = None
47
 
 
48
  if os.path.exists(TOKEN_FILE):
49
  try:
50
  with open(TOKEN_FILE, "rb") as f:
 
52
  except:
53
  creds = None
54
 
 
55
  if creds and creds.expired and creds.refresh_token:
56
  try:
57
  creds.refresh(Request())
 
59
  with open(TOKEN_FILE, "wb") as f:
60
  pickle.dump(creds, f)
61
 
62
+ except:
 
63
  creds = None
64
 
65
  if not creds:
66
+ raise Exception("❌ token.pkl missing. Upload valid token.")
 
 
67
 
68
  return build("gmail", "v1", credentials=creds)
69
 
70
 
 
 
71
  def send_email(to, subject, html):
72
 
73
  try:
74
  service = get_gmail_service()
75
 
76
+ msg = MIMEText(html, "html")
77
+ msg["To"] = to
78
+ msg["From"] = GMAIL_USER
79
+ msg["Subject"] = subject
80
 
81
  raw = base64.urlsafe_b64encode(
82
+ msg.as_bytes()
83
  ).decode()
84
 
85
  body = {"raw": raw}
86
 
87
+ service.users().messages().send(
88
  userId="me",
89
  body=body
90
  ).execute()
91
 
92
+ print("βœ… Sent:", to)
 
93
  return True
94
 
95
  except Exception as e:
96
+ print("❌ Gmail error:", e)
97
  return False
98
 
99
 
100
  # ================= VALIDATION =================
101
 
102
  EMAIL_REGEX = re.compile(
103
+ r"^[\w\.-]+@[\w\.-]+\.\w+$"
104
  )
105
 
106
 
 
118
  f"{LEETCODE_API}/{username}",
119
  timeout=8
120
  )
 
121
  return r.status_code == 200
122
 
123
  except:
 
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):
 
147
  try:
148
  r = requests.get(
149
  f"{LEETCODE_API}/{username}/acSubmission?limit=20",
150
+ timeout=10
151
  )
152
 
153
  if r.status_code != 200:
154
  return False
155
 
156
+ d = r.json()
 
 
 
 
 
 
 
 
 
157
 
158
+ if "submission" in d:
159
+ subs = d["submission"]
160
+ elif "data" in d:
161
+ subs = d["data"]
162
+ elif isinstance(d, list):
163
+ subs = d
164
  else:
165
  return False
166
 
167
  today = date.today()
168
 
169
+ for s in subs:
170
 
171
  if not isinstance(s, dict):
172
  continue
 
179
  if not ts:
180
  continue
181
 
182
+ solved = datetime.fromtimestamp(
183
  int(ts),
184
  tz=pytz.utc
185
  ).date()
186
 
187
+ if solved == today:
188
  return True
189
 
190
  return False
191
 
192
+ except:
 
193
  return False
194
 
195
 
 
201
  return "❌ Invalid LeetCode username"
202
 
203
  if not valid_email(email):
204
+ return "❌ Invalid email"
205
 
206
  conn = get_db()
207
  cur = conn.cursor()
208
 
209
  cur.execute("""
210
  SELECT email_verified, verification_token, unsubscribed
211
+ FROM users WHERE email=%s
 
212
  """, (email,))
213
 
214
  row = cur.fetchone()
215
 
216
+ # Existing
217
  if row:
218
 
219
  verified, token, unsub = row
220
 
 
221
  if verified and not unsub:
222
  cur.close()
223
  conn.close()
224
  return "⚠️ Already subscribed"
225
 
 
226
  if verified and unsub:
227
 
228
  cur.execute("""
 
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
 
 
255
 
256
  return "πŸ“© Verification re-sent"
257
 
258
+ # New
259
  token = uuid.uuid4().hex
260
 
261
  cur.execute("""
262
+ INSERT INTO users(
263
+ leetcode_username,email,timezone,
264
+ email_verified,verification_token,unsubscribed
 
 
 
 
265
  )
266
+ VALUES(%s,%s,%s,false,%s,false)
267
  """, (username, email, timezone, token))
268
 
269
  conn.commit()
 
274
 
275
  send_email(
276
  email,
277
+ "Verify Subscription",
278
  f"<a href='{link}'>Verify</a>"
279
  )
280
 
 
295
  AND email_verified=false
296
  """, (token,))
297
 
298
+ ok = cur.rowcount
299
 
300
  conn.commit()
301
  cur.close()
302
  conn.close()
303
 
304
+ if ok == 0:
305
  return "❌ Invalid link"
306
 
307
  return "βœ… Email verified"
 
320
  WHERE verification_token=%s
321
  """, (token,))
322
 
323
+ ok = cur.rowcount
324
 
325
  conn.commit()
326
  cur.close()
327
  conn.close()
328
 
329
+ if ok == 0:
330
  return "❌ Invalid link"
331
 
332
  return "βœ… Unsubscribed"
333
 
334
 
335
+ # ================= URL HANDLER =================
336
+
337
+ def handle_url(request: gr.Request):
338
+
339
+ try:
340
+ params = request.query_params
341
+
342
+ if "verify" in params:
343
+ return verify_user(params["verify"])
344
+
345
+ if "unsubscribe" in params:
346
+ return unsubscribe_user(params["unsubscribe"])
347
+
348
+ return ""
349
+
350
+ except Exception as e:
351
+ print("URL error:", e)
352
+ return ""
353
+
354
+
355
  # ================= SCHEDULER =================
356
 
357
  def run_scheduler(secret):
 
363
  cur = conn.cursor()
364
 
365
  cur.execute("""
366
+ SELECT id,leetcode_username,email,timezone,
367
+ last_sent_date,last_sent_slot,verification_token
 
368
  FROM users
369
  WHERE email_verified=true
370
  AND unsubscribed=false
 
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
 
412
  )
413
 
414
  if not ok:
 
415
  continue
416
 
417
  cur.execute("""
 
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
 
 
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)