Leetcode Reminder

Leetcode reminder and status in your memu bar!

Image preview of Leetcode Reminder plugin.

leetcode_reminder.5m.py

Edit
Open on GitHub
#!/usr/bin/env python3

# <xbar.title>Leetcode Reminder</xbar.title>
# <xbar.version>v1.1.0</xbar.version>
# <xbar.author>zihengjackchen</xbar.author>
# <xbar.author.github>zihengjackchen</xbar.author.github>
# <xbar.desc>Leetcode reminder and status in your memu bar!</xbar.desc>
# <xbar.dependencies>python3</xbar.dependencies>
# <xbar.image>https://raw.githubusercontent.com/zihengjackchen/xbar-scripts/main/leetcode_reminder/demo.png</xbar.image>
# <xbar.abouturl>https://github.com/zihengjackchen/xbar-scripts/blob/main/leetcode_reminder/README.md</xbar.abouturl>
# <xbar.var>string(USERNAME): Your leetcode username</xbar.var>
# <xbar.var>string(LEETCODE_SESSION): Your leetcode session cookie</xbar.var>
# <xbar.var>string(CSRFTOKEN): Your csrf token</xbar.var>

# Potential alternative way to access API
# import cloudscraper
# scraper = requests.create_scraper(
#   interpreter='nodejs',
# 	delay=10,
#   browser={
#         'browser': 'firefox',
#         'platform': 'windows',
#         'mobile': False,
#         'headless': True  # Set to True if you want to run headless
#     }
# )

import requests
from datetime import datetime
import os

# Make sure to change these variables in the xbar plugin browser before using this script
USERNAME = os.environ.get('USERNAME')
LEETCODE_SESSION = os.environ.get('LEETCODE_SESSION')
CSRFTOKEN = os.environ.get('CSRFTOKEN')

# Or change them manually if script is used alone
# USERNAME = ""
# LEETCODE_SESSION = ""
# CSRFTOKEN = ""

if not (USERNAME and LEETCODE_SESSION and CSRFTOKEN):
  print("⚠️ EMPTY VARIABLES")
  print("---")
  print("Please fill in the USERNAME, LEETCODE_SESSION, and CSRFTOKEN in the xbar plugin browser.")
  exit()

# Setting colors if dark mode is on
# All colors are off if dark mode is off for better readability
dark_mode_status = os.environ.get('XBARDarkMode') == "true"

color_premium = "#000000" if not dark_mode_status else "#FFA116"
color_medium = "#000000" if not dark_mode_status else "#FFC01E"
color_easy = "#000000" if not dark_mode_status else "#00B8A3"
color_hard = "#000000" if not dark_mode_status else "#FF375F"

current_utc_time = datetime.utcnow()
target_time = datetime(current_utc_time.year, current_utc_time.month, current_utc_time.day, 0, 0, 0)
time_remaining = target_time - current_utc_time
hours, remainder = divmod(time_remaining.seconds, 3600)
minutes, _ = divmod(remainder, 60)
countdown = "⏳ {} hr {} min".format(hours, minutes)
countdown_alt = "⌛ {} hr {} min".format(hours, minutes)
countdown_full = "⌛ {} hours {} minutes".format(hours, minutes)

countdown_color = ""
if hours == 0:
  countdown_color = f" | color={'darkred' if not dark_mode_status else color_hard}"
elif hours <= 2:
  countdown_color = f" | color={'darkorange' if not dark_mode_status else color_medium}"


# Setting up for API call
endpoint = 'https://leetcode.com/graphql/'

current_date = datetime.now().date()
current_year = current_date.year
current_month = current_date.month

variables = {"username": USERNAME, "limit": 10, "year": current_year, "month": current_month}

# Prepare the request headers and payload
headers = {
    'Content-Type': 'application/json',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
}

# Your GraphQL query with variables
graphql_query = '''
query UserQueries($username: String!, $limit: Int!, $year: Int!, $month: Int!) {
  streakCounter {
    streakCount
  }

  activeDailyCodingChallengeQuestion {
    date
    userStatus
    link
    question {
      acRate
      difficulty
      frontendQuestionId: questionFrontendId
      status
      title
    }
  }

  allQuestionsCount {
    difficulty
    count
  }

  matchedUser(username: $username) {
    submitStats {
      acSubmissionNum {
        difficulty
        count
        submissions
      }
    }
  }

  userContestRanking(username: $username) {
    attendedContestsCount
    rating
    globalRanking
    topPercentage
  }

  userContestRankingHistory(username: $username) {
    trendDirection
  }

  userStatus {
    userId
    isPremium
    username
    checkedInToday
  }

  recentAcSubmissionList(username: $username, limit: $limit) {
    title
    titleSlug
    timestamp
  }

  dailyCodingChallengeV2(year: $year, month: $month) {
    weeklyChallenges {
      date
      userStatus
      link
      question {
        acRate
        difficulty
        frontendQuestionId: questionFrontendId
        status
        title
      }
    }
  }

  isEasterEggCollected

  validTimeTravelTicketCount
  redeemedTimeTravelTicketCount
}
'''

payload = {'query': graphql_query, 'variables': variables}
cookies = {'LEETCODE_SESSION': LEETCODE_SESSION, 'csrftoken': CSRFTOKEN}

response = requests.post(endpoint, headers=headers, json=payload, cookies=cookies)
response_json = response.json()

if response.status_code != 200:
  print("⚠️ SERVER ERROR")
  print("---")
  print(f"Status code: {response.status_code}")
  print("---")
  print("Leetcode Status 🔗| href=https://status.leetcode.com/")
  print("Leetcode Homepage 🔗| href=https://leetcode.com/")
  exit()

if "errors" in response_json:
  print("⚠️ USERNAME ERROR")
  print("---")
  print(f"Check your input username")
  print("---")
  print("Leetcode Status 🔗| href=https://status.leetcode.com/")
  print("Leetcode Homepage 🔗| href=https://leetcode.com/")
  exit()

if not response_json['data']['userStatus']['userId']:
  print("⚠️ TOKEN ERROR")
  print("---")
  print(f"Check your input tokens")
  print("---")
  print("Leetcode Status 🔗| href=https://status.leetcode.com/")
  print("Leetcode Homepage 🔗| href=https://leetcode.com/")
  exit()

try:
  # Another API call to get leet coin count
  leet_coin_url = "https://leetcode.com/points/api/total/"
  leet_coin_response = requests.get(leet_coin_url, headers=headers, json=payload, cookies=cookies)

  leet_coin = "ERROR"
  if leet_coin_response.status_code == 200:
      leet_coin = leet_coin_response.json()['points']

except:
  print("⚠️ ERROR GETTING LEETCOIN")
  print("---")
  print("Check your input variables")
  print("---")
  print("Leetcode Status 🔗| href=https://status.leetcode.com/")
  print("Leetcode Homepage 🔗| href=https://leetcode.com/")
  exit()

try:
  # Parsing
  limit_tickets = 3 - response_json['data']["validTimeTravelTicketCount"] + response_json['data']["redeemedTimeTravelTicketCount"]
  usable_tickets = min(limit_tickets, leet_coin // 70)

  is_premium = response_json["data"]["userStatus"]["isPremium"]
  lc_username = response_json["data"]["userStatus"]["username"]
  checked_in = response_json["data"]["userStatus"]["checkedInToday"]
  easter_egg_collected = response_json["data"]["isEasterEggCollected"]

  title = response_json["data"]["activeDailyCodingChallengeQuestion"]["question"]["title"]
  question_id = response_json["data"]["activeDailyCodingChallengeQuestion"]["question"]["frontendQuestionId"]
  user_status_daily = response_json["data"]["activeDailyCodingChallengeQuestion"]["userStatus"]
  if user_status_daily == "NotStart":
    user_status_daily_str = "Incomplete 😨"
  elif user_status_daily == "Finish":
    user_status_daily_str = "Completed 🥳"
  link = response_json["data"]["activeDailyCodingChallengeQuestion"]["link"]
  difficulty = response_json["data"]["activeDailyCodingChallengeQuestion"]["question"]["difficulty"]
  ac_rate = response_json["data"]["activeDailyCodingChallengeQuestion"]["question"]["acRate"]
  difficulty_color = color_easy
  if difficulty == "Hard":
    difficulty_color = color_hard
  elif difficulty == "Medium":
    difficulty_color = color_medium

  user_status_weekly = response_json["data"]["dailyCodingChallengeV2"]["weeklyChallenges"][-1]["userStatus"]
  link_weekly = response_json["data"]["dailyCodingChallengeV2"]["weeklyChallenges"][-1]["link"]
  title_weekly = response_json["data"]["dailyCodingChallengeV2"]["weeklyChallenges"][-1]["question"]["title"]
  question_id_weekly = response_json["data"]["dailyCodingChallengeV2"]["weeklyChallenges"][-1]["question"]["frontendQuestionId"]
  if user_status_weekly == "NotStart":
    user_status_weekly_str = "Incomplete 😨"
  elif user_status_weekly == "Finish":
    user_status_weekly_str = "Completed 🥳"
  difficulty_weekly = response_json["data"]["dailyCodingChallengeV2"]["weeklyChallenges"][-1]["question"]["difficulty"]
  ac_rate_weekly = response_json["data"]["dailyCodingChallengeV2"]["weeklyChallenges"][-1]["question"]["acRate"]
  difficulty_color_weekly = color_easy
  if difficulty_weekly == "Hard":
    difficulty_color_weekly = color_hard
  elif difficulty_weekly == "Medium":
    difficulty_color_weekly = color_medium

  recent_subs = [(sub["title"], sub["titleSlug"]) for sub in response_json["data"]["recentAcSubmissionList"]]

  streak_days = response_json["data"]["streakCounter"]["streakCount"]

  all_solved_count = sum(item["count"] for item in response_json["data"]["allQuestionsCount"] if item["difficulty"] in ["Easy", "Medium", "Hard"])
  easy_count = next(item["count"] for item in response_json["data"]["allQuestionsCount"] if item["difficulty"] == "Easy")
  medium_count = next(item["count"] for item in response_json["data"]["allQuestionsCount"] if item["difficulty"] == "Medium")
  hard_count = next(item["count"] for item in response_json["data"]["allQuestionsCount"] if item["difficulty"] == "Hard")

  user_all_solved_count = sum(item["count"] for item in response_json["data"]["matchedUser"]["submitStats"]["acSubmissionNum"] if item["difficulty"] in ["Easy", "Medium", "Hard"] )
  user_easy_count = next(item["count"] for item in response_json["data"]["matchedUser"]["submitStats"]["acSubmissionNum"] if item["difficulty"] == "Easy")
  user_median_count = next(item["count"] for item in response_json["data"]["matchedUser"]["submitStats"]["acSubmissionNum"] if item["difficulty"] == "Medium")
  user_hard_count = next(item["count"] for item in response_json["data"]["matchedUser"]["submitStats"]["acSubmissionNum"] if item["difficulty"] == "Hard")

  # Do not parse for contest info if no contest attended
  has_contest = "userContestRanking" in response_json["data"] and response_json["data"]["userContestRanking"] and "attendedContestsCount" in response_json["data"]["userContestRanking"]
  if has_contest:
    contest_count = response_json["data"]["userContestRanking"]["attendedContestsCount"]
    contest_rating = response_json["data"]["userContestRanking"]["rating"]
    contest_ranking = response_json["data"]["userContestRanking"]["globalRanking"]
    contest_percentage = response_json["data"]["userContestRanking"]["topPercentage"]
    contest_trend = response_json["data"]["userContestRankingHistory"][-1]["trendDirection"]

    if contest_trend == "UP":
      contest_trend = '↗️'
    elif contest_trend == "DOWN":
      contest_trend = '↘️'
    else:
      contest_trend = '➡️'

  # Printing the UI
  if user_status_daily == "Finish":
    print(user_status_daily_str)
  else:
    print(f"{countdown + countdown_color}")
    print(f"{countdown_alt + countdown_color}")
  print("---")

  if is_premium:
    print(f"{lc_username} 🔗|href=https://leetcode.com/{USERNAME} | color={color_premium} ")
  else:
    print(f"{lc_username} 🔗|href=https://leetcode.com/{USERNAME}")

  print(f"--LeetCoin: {leet_coin}")
  if checked_in:
    print(f"--Daily Check-In (Completed)")
  else:
    print(f"--Daily Check-In 🔗| href=https://leetcode.com{link}")

  if easter_egg_collected:
    print(f"--Collect Easter Egg (Completed)")
  else:
    print(f"--Collect Easter Egg 🔗| href=https://leetcode.com/contest")


  print("---")
  print(f"Daily Challenge 🔗| href=https://leetcode.com{link}")
  print(f"--{question_id}. {title} | color={difficulty_color} ")
  print(f"--Difficulty: {difficulty} ({ac_rate:.2f}% Accepted)| color={difficulty_color}")
  print("-----")
  print(f"--Status: {user_status_daily_str}")
  print(f"--Countdown: {countdown_full + countdown_color}")
  print(f"--Streak: {streak_days}")
  print(f"--Usable Time Travel Tickets: {usable_tickets}")

  # Do not show weekly problem if not premium
  if is_premium:
    print(f"Weekly Challenge 🔗| href=https://leetcode.com{link_weekly}")
    print(f"--{question_id_weekly}. {title_weekly} | color={difficulty_color_weekly}")
    print(f"--Difficulty: {difficulty_weekly} ({ac_rate_weekly:.2f}% Accepted) | color={difficulty_color_weekly}")
    print("-----")
    print(f"--Status: {user_status_weekly_str}")

  print("---")
  print(f"Progress")
  print(f"--Easy: {user_easy_count}/{easy_count} | color={color_easy}")
  print(f"--Medium: {user_median_count}/{medium_count} | color={color_medium}")
  print(f"--Hard: {user_hard_count}/{hard_count} | color={color_hard}")
  print("-----")
  print(f"--All: {user_all_solved_count}/{all_solved_count}")

  # Do not show contest if no contest attended
  if has_contest:
    print("Contest 🔗| href=https://leetcode.com/contest")
    print(f"--Rating: {int(contest_rating)} {contest_trend}")
    print(f"--Ranking: {contest_ranking}")
    print(f"--Top {contest_percentage}%")
    print(f"--Attended: {contest_count}")

  print("Recent Submissions")
  for sub, sub_slug in recent_subs:
    print(f"--{sub} 🔗| href=https://leetcode.com/problems/{sub_slug}")

  print("---")
  print("Leetcode Homepage 🔗| href=https://leetcode.com/")

except:
  print("⚠️ PARSING ERROR")
  print("---")
  print("Check your input variables")
  print("---")
  print("Leetcode Status 🔗| href=https://status.leetcode.com/")
  print("Leetcode Homepage 🔗| href=https://leetcode.com/")
  exit()