Skip to content

Commit c632427

Browse files
authored
Added multiplatform support and improved docs (#2)
* updated gitignore * added codechef ratings * updated requirements file * Updated README * updated leetcode script * updated endpoints file * fixed variable issue in endpoints.md * tmp implementation of codechef api
1 parent 0487ec9 commit c632427

File tree

10 files changed

+200
-57
lines changed

10 files changed

+200
-57
lines changed

.github/scripts/update_status.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
base_url = "https://about-coderme.vercel.app/"
77
websites = {
88
"leetcode": ["jayampatel", "hasan_15_07_03"],
9-
"codeforces": ["jayampatel"]
9+
"codeforces": ["jayampatel"],
10+
"codechef": ["jayampatel", "jympatel"]
1011
}
1112
status_data = {}
1213

@@ -24,17 +25,17 @@
2425
"status_code": response.status_code,
2526
})
2627

27-
# Update the status badge in the README.md file
28-
with open("README.md", "r") as readme_file:
29-
readme_content = readme_file.read()
30-
31-
avg_success = sum(1 for status in status_data[website] if status["status"] == "success") / len(status_data[website])
32-
status = "UP" if avg_success >= 0.5 else "DOWN"
33-
color = "brightgreen" if avg_success > 0.7 else "red"
34-
readme_content = re.sub(r"!\[" + website + r"\]\(.*\)", f"![{website}](https://img.shields.io/badge/{website}-success%20rate:%20{int(avg_success * 100)}-{color})", readme_content)
35-
36-
with open("README.md", "w") as readme_file:
37-
readme_file.write(readme_content)
28+
# # Update the status badge in the README.md file
29+
# with open("README.md", "r") as readme_file:
30+
# readme_content = readme_file.read()
31+
#
32+
# avg_success = sum(1 for status in status_data[website] if status["status"] == "success") / len(status_data[website])
33+
# status = "UP" if avg_success >= 0.5 else "DOWN"
34+
# color = "brightgreen" if avg_success > 0.7 else "red"
35+
# readme_content = re.sub(r"!\[" + website + r"\]\(.*\)", f"![{website}](https://img.shields.io/badge/{website}-success%20rate:%20{int(avg_success * 100)}-{color})", readme_content)
36+
#
37+
# with open("README.md", "w") as readme_file:
38+
# readme_file.write(readme_content)
3839

3940

4041
# Create the status data

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ __pycache__
22
*/__pycache__
33

44
.idea
5-
.venv
5+
venv

README.md

+27-23
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
1-
# about-coderme
1+
# About Coder Me 💻
22

3-
API to access your leetcode rankings.
3+
## Overview 📘
44

5-
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fabout-coderme.vercel.app%2Fleetcode%2Fjayampatel&query=%24.rating&style=flat-square&label=leetcode)
6-
![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fabout-coderme.vercel.app%2Fcodeforces%2Fjayampatel&query=%24.rating&style=flat-square&label=codeforces)
5+
API to access your competitive programming profiles and rankings across platforms like LeetCode and Codeforces.
76

8-
## Usage
7+
![LeetCode Rating](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fabout-coderme.vercel.app%2Fleetcode%2Fjayampatel&query=%24.rating&style=flat-square&label=LeetCode)
8+
![Codeforces Rating](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fabout-coderme.vercel.app%2Fcodeforces%2Fjayampatel&query=%24.rating&style=flat-square&label=Codeforces)
99

10-
Make `GET` request at `https://about-coderme.vercel.app/{website}/{username}/` for all data for that website
11-
For more see [URL Formatting](docs/endpoints.md)
10+
## Usage 📱
1211

13-
## Claim Your Username!!
12+
Make a `GET` request to `https://about-coderme.vercel.app/{website}/{username}/` to retrieve data for a particular website.
1413

15-
It's not required but you can use it if you need multiple-platform requests (ex. leetcode + codeforces).
14+
See [API Endpoints Doc](docs/endpoints.md) 📃 for more details on API endpoints.
1615

17-
### Steps
16+
## Claim Your Username!! 🎉
17+
18+
You can claim a consistent username across platforms:
19+
20+
1. Fork this repo 🍴
21+
2. Add your details to `users.json`:
1822

19-
1. Create `fork`.
20-
2. Add your username in `users.json` in format below:
2123
```json
2224
{
23-
"USERNAME_YOU_NEED*": {
24-
"github": "YOUR_GITHUB_USERNAME*",
25-
"leetcode": "YOUR_LEETCODE_USERNAME",
26-
"codeforces": "YOUR_CODEFORCES_USERNAME"
27-
}
28-
}
29-
```
30-
constraints:
31-
1. USERNAME_YOU_NEED (REQUIRED): username you need for your acm profile, must not be taken.
32-
2. YOUR_GITHUB_USERNAME (REQUIRED): must match your current GitHub username for PR to be merged.
33-
3. others: optional
25+
"YOUR_CHOSEN_USERNAME": {
26+
"github": "YOUR_GH_USERNAME",
27+
"leetcode": "YOUR_LEETCODE_USERNAME",
28+
"codeforces": "YOUR_CF_USERNAME"
29+
}
30+
}
31+
```
32+
33+
Constraints: 🚧
34+
35+
- `YOUR_CHOSEN_USERNAME`: Username you want to claim
36+
- `YOUR_GH_USERNAME`: Must match your GitHub username
37+
- Others are optional, which are `codechef`, `codeforces` and `leetcode`

docs/endpoints.md

+40-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
1-
# URL Format
1+
# Endpoints
22

3-
## Intro
3+
## 1. Using Endpoints
4+
There are two main ways to access the data:
45

5-
Current to access API, base url will be `https://about-coderme.vercel.app/{username}/` where username will be your username for specific site.
6-
Above URL will have all data from `/{username}/*`, where * is used for platform like leetcode (only one currently supported).
7-
There is issue of users having different usernames across different platforms and we will try to find good solution to it.
6+
### I. Using Claimed Username
7+
`coderme.vercel.app/{claimed_username}`
88

9-
## Leetcode
9+
This uses the claimed username added to `users.json`.
10+
Can be used to get data for all platforms:
11+
```
12+
GET /{claimed_username}/
13+
```
1014

11-
For leetcode you can use `/{username}/leetcode` to get all profile data of leetcode.
15+
Or get platform specific data:
16+
```
17+
GET /{claimed_username}/{platform}
18+
```
19+
20+
Works like a REST API - can append various paths to filter data.
21+
22+
### II. Using Platform Specific Username
23+
`coderme.vercel.app/{platform}/{actual_username}`
24+
25+
This uses the actual username for a specific platform.
26+
27+
Can be used to get all data for that platform:
28+
```
29+
GET /{platform}/{actual_username}
30+
```
31+
32+
Can also apply queries to get partial data:
33+
```
34+
GET /{platform}/{actual_username}?query=query1+query2
35+
```
36+
37+
38+
## 2. Supported Platforms And Their Queries
39+
40+
| | **leetcode** | **codeforces** | **codechef** |
41+
|-------------:|:------------:|:--------------:|:------------:|
42+
| **website** | leetcode.com | codeforces.com | codechef.com |
43+
| **ratings** ||||
44+
| **contests** ||||
1245

13-
Currently additional URLs are `{uname}/leetcode/contest` giving data only about content (with contest history).
14-
and other `{uname}/leetcode/contest/basic` for contest data (without contest history).

main.py

+40-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from fastapi import FastAPI
44
from fastapi.middleware.cors import CORSMiddleware
55

6-
import platform_scripts.codeforces as codeforces
6+
import platform_scripts.codeforces as cf
7+
import platform_scripts.codechef as chef
8+
import platform_scripts.leetcode as lt
79
from leetcode import contest
810

911
app = FastAPI()
@@ -17,46 +19,57 @@
1719
allow_headers=["*"], # Set this to the HTTP headers you want to allow
1820
)
1921

22+
2023
@DeprecationWarning
2124
@app.get("/{username}/leetcode/contest")
2225
def get_leetcode_info(username: str):
2326
if username[0] != '@':
2427
return contest.get_contest_data(username)
2528

29+
2630
@DeprecationWarning
2731
@app.get("/{username}/leetcode/contest/basic")
2832
def get_leetcode_info(username: str):
29-
return contest.get_contest_basic(username)
33+
data = contest.get_contest_basic(username)
34+
data['warning'] = {"endpoint to be depreciated by end of 2023"}
35+
return data
36+
3037

3138
@DeprecationWarning
3239
@app.get("/{username}/leetcode/contest/basic/rank")
3340
def get_leetcode_rank(username: str):
3441
pass
3542
return contest.get_contest_rank(username)
3643

44+
3745
@DeprecationWarning
3846
@app.get("/{username}/leetcode/{query}")
3947
def get_leetcode_info_based_on_query(username: str, query: str):
4048
query = query.split("+")
4149
pass
4250
return contest.get_leetcode_info(username, query)
4351

52+
4453
@app.get("/codeforces/{username}")
4554
def get_codeforces_info(username: str):
46-
return {
47-
"rating": codeforces.get_current_rating(username)
48-
}
55+
return cf.get_contest_data(username)
56+
4957

5058
@app.get("/leetcode/{username}")
5159
def get_leetcode_info(username: str):
52-
return {
53-
"rating": contest.get_contest_basic(username)["data"]["userContestRanking"]["rating"]
54-
}
60+
return lt.get_contest_data(username)
61+
62+
63+
@app.get("/codechef/{username}")
64+
def get_codechef_info(username: str):
65+
return chef.get_contest_data(username)
66+
5567

5668
@app.get("/status")
5769
def get_status():
5870
return json.load(open("status.json", "r"))
5971

72+
6073
@app.get("/{username}")
6174
def get_all_ratings_from_username(username: str):
6275
with open('users.json') as file:
@@ -65,12 +78,29 @@ def get_all_ratings_from_username(username: str):
6578
userdata = users[username]
6679
else:
6780
return {"error": "username not found!",
68-
"code": 1001}
81+
"code": 1100}
6982

7083
body = {}
7184
if "leetcode" in userdata.keys():
7285
body['leetcode'] = get_leetcode_info(userdata['leetcode']) # TODO: use better functions
7386
if "codeforces" in userdata.keys():
7487
body['codeforces'] = get_codeforces_info(userdata['codeforces']) # TODO: use better functions
88+
if "codechef" in userdata.keys():
89+
body['codechef'] = chef.get_contest_rating(userdata['codeforces']) # TODO: function is ok but not complete
90+
91+
return body
92+
7593

76-
return body
94+
@app.get("/{username}/leetcode")
95+
def get_leetcode_rating(username: str):
96+
if username[0] == "@":
97+
with open('users.json') as file:
98+
users: dict = json.load(file)
99+
if username not in users.keys():
100+
return {"error": "username not found!",
101+
"code": 1100}
102+
if "leetcode" not in users[username].keys():
103+
return {"error": f"leetcode id for {username} not found!",
104+
"code": 1101}
105+
username = users[username]['leetcode']
106+
return lt.get_contest_data(username)

platform_scripts/codechef.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import requests
2+
3+
from bs4 import BeautifulSoup
4+
5+
6+
def get_contest_rating(username: str) -> int:
7+
response = requests.get(f"https://www.codechef.com/users/{username}")
8+
if response.ok:
9+
soup = BeautifulSoup(response.text, features="html.parser")
10+
rating = soup.find("div", class_="rating-number").text.split("?")[0]
11+
return int(rating)
12+
13+
14+
def get_contest_data(username: str, queries: list = None):
15+
# TODO: if queries is none
16+
# TODO: tmp implementation it's not good as each function will scrape web all different times
17+
return {
18+
"rating": get_contest_rating(username)
19+
}
20+
pass

platform_scripts/codeforces.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import requests
22

33

4-
def get_current_rating(username):
4+
def get_current_rating(username: str):
55
response = requests.get(f"https://codeforces.com/api/user.rating?handle={username}")
66
data = response.json()
77
return data["result"][-1]["newRating"]
8+
9+
10+
def get_contest_data(username: str, queries: list = None):
11+
data = {}
12+
# TODO: if query is empty make all queries run
13+
data['rating'] = get_current_rating(username)
14+
return data

platform_scripts/leetcode.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import requests
2+
3+
data = {
4+
"query": """
5+
query userContestRankingInfo($username: String!) {
6+
userContestRanking(username: $username) {
7+
attendedContestsCount
8+
rating
9+
globalRanking
10+
totalParticipants
11+
topPercentage
12+
badge {
13+
name
14+
}
15+
}
16+
userContestRankingHistory(username: $username) {
17+
attended
18+
trendDirection
19+
problemsSolved
20+
totalProblems
21+
finishTimeInSeconds
22+
rating
23+
ranking
24+
contest {
25+
title
26+
startTime
27+
}
28+
}
29+
}
30+
""",
31+
"variables": {"username": "jayampatel"},
32+
"operationName": "userContestRankingInfo"
33+
}
34+
35+
36+
def generate_query(pars_required: list):
37+
pass
38+
39+
40+
def get_contest_data(username: str, queries: list = None):
41+
# TODO: tmp code to remove leetcode/contest.py
42+
response = requests.post('https://leetcode.com/graphql/', json=data)
43+
if response.ok:
44+
response = response.json()
45+
return {
46+
"rating": round(response['data']['userContestRanking']['rating'])
47+
}
48+
pass

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
fastapi==0.68.0
22
uvicorn==0.15.0
33
requests==2.31.0
4+
beautifulsoup4==4.12.2

users.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"@jayampatel": {
33
"github": "jayam04",
44
"leetcode": "jayampatel",
5-
"codeforces": "jayampatel"
5+
"codeforces": "jayampatel",
6+
"codechef": "jayampatel"
67
}
78
}

0 commit comments

Comments
 (0)