Skip to content

Commit 7482423

Browse files
authored
[codex] HackerRank 플랫폼 지원 추가 (#8)
* feat: 플랫폼 설정 구조 일반화 * feat: HackerRank 제출 동기화 추가 * feat: HackerRank 지원 화면과 문서 반영
1 parent aa9c3a0 commit 7482423

18 files changed

Lines changed: 1337 additions & 254 deletions

File tree

README.en.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<p align="center">
1212
A Chrome extension that automatically syncs accepted
13-
<strong>LeetCode</strong> and <strong>Programmers</strong> submissions to GitHub.
13+
<strong>LeetCode</strong>, <strong>Programmers</strong>, and <strong>HackerRank</strong> submissions to GitHub.
1414
</p>
1515

1616
<p align="center">
@@ -30,32 +30,33 @@ from:
3030

3131
- LeetCode
3232
- Programmers
33+
- HackerRank
3334

3435
## Highlights
3536

3637
- Sync accepted submissions directly to GitHub
37-
- Use one extension for both LeetCode and Programmers
38+
- Use one extension for LeetCode, Programmers, and HackerRank
3839
- Create a new repository or connect an existing one
3940
- Customize repository path templates by platform
4041
- Keep a clean root README with platform-based summary
4142

4243
## How does AlgorithmHub work?
4344

4445
1. Connect your GitHub account and repository
45-
2. Solve a problem on LeetCode or Programmers
46+
2. Solve a problem on LeetCode, Programmers, or HackerRank
4647
3. Submit an accepted solution
4748
4. Let AlgorithmHub sync the solution files to GitHub automatically
4849

49-
| LeetCode Demo | Programmers Demo |
50-
| --- | --- |
51-
| ![LeetCode demo](./docs/leetcode-demo.gif) | ![Programmers demo](./docs/programmers-demo.gif) |
50+
| LeetCode Demo | Programmers Demo | HackerRank Demo |
51+
| --- | --- | --- |
52+
| ![LeetCode demo](./docs/leetcode-demo.gif) | ![Programmers demo](./docs/programmers-demo.gif) | ![HackerRank demo](./docs/hackerrank-demo.gif) |
5253

5354
## Usage
5455

5556
1. Open the extension popup
5657
2. Authenticate with GitHub
5758
3. Create a repository or connect an existing one
58-
4. Solve a problem on LeetCode or Programmers
59+
4. Solve a problem on LeetCode, Programmers, or HackerRank
5960
5. Submit an accepted solution
6061
6. Let AlgorithmHub sync it to GitHub
6162

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</p>
1010

1111
<p align="center">
12-
<strong>LeetCode</strong> <strong>프로그래머스</strong>의 정답 제출 코드를
12+
<strong>LeetCode</strong>, <strong>프로그래머스</strong>, <strong>HackerRank</strong>의 정답 제출 코드를
1313
GitHub으로 자동 동기화하는 Chrome 확장 프로그램입니다.
1414
</p>
1515

@@ -28,32 +28,33 @@ GitHub 저장소를 연결해두면 아래 플랫폼의 정답 제출을 자동
2828

2929
- LeetCode
3030
- 프로그래머스
31+
- HackerRank
3132

3233
## 주요 특징
3334

3435
- 정답 제출 코드를 GitHub으로 자동 업로드
35-
- LeetCode와 프로그래머스를 하나의 확장에서 지원
36+
- LeetCode, 프로그래머스, HackerRank를 하나의 확장에서 지원
3637
- 새 저장소 생성 또는 기존 저장소 연결
3738
- 플랫폼별 저장 경로 템플릿 커스터마이징
3839
- 플랫폼 기준 요약이 포함된 깔끔한 루트 README 유지
3940

4041
## AlgorithmHub는 어떻게 동작하나요?
4142

4243
1. GitHub 계정과 저장소를 연결합니다
43-
2. LeetCode 또는 프로그래머스에서 문제를 풉니다
44+
2. LeetCode, 프로그래머스, HackerRank에서 문제를 풉니다
4445
3. 정답 제출을 합니다
4546
4. AlgorithmHub가 풀이 파일을 GitHub으로 자동 동기화합니다
4647

47-
| LeetCode 데모 | 프로그래머스 데모 |
48-
| --- | --- |
49-
| ![LeetCode demo](./docs/leetcode-demo.gif) | ![Programmers demo](./docs/programmers-demo.gif) |
48+
| LeetCode 데모 | 프로그래머스 데모 | HackerRank 데모 |
49+
| --- | --- | --- |
50+
| ![LeetCode demo](./docs/leetcode-demo.gif) | ![Programmers demo](./docs/programmers-demo.gif) | ![HackerRank demo](./docs/hackerrank-demo.gif) |
5051

5152
## 사용 방법
5253

5354
1. 확장 팝업을 연다
5455
2. GitHub 인증을 진행한다
5556
3. 새 저장소를 만들거나 기존 저장소를 연결한다
56-
4. LeetCode 또는 프로그래머스에서 문제를 푼다
57+
4. LeetCode, 프로그래머스, HackerRank에서 문제를 푼다
5758
5. 정답 제출을 한다
5859
6. AlgorithmHub가 결과를 GitHub으로 동기화한다
5960

docs/hackerrank-demo.gif

3.64 MB
Loading

manifest.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "AlgorithmHub",
4-
"description": "Unified Chrome extension that uploads accepted LeetCode and Programmers solutions to GitHub.",
4+
"description": "Unified Chrome extension that uploads accepted LeetCode, Programmers, and HackerRank solutions to GitHub.",
55
"version": "1.0.0",
66
"icons": {
77
"16": "icons/icon-16.png",
@@ -26,6 +26,7 @@
2626
"host_permissions": [
2727
"https://leetcode.com/*",
2828
"https://school.programmers.co.kr/*",
29+
"https://www.hackerrank.com/*",
2930
"https://github.com/*",
3031
"https://api.github.com/*"
3132
],
@@ -34,9 +35,16 @@
3435
"matches": [
3536
"https://leetcode.com/*",
3637
"https://school.programmers.co.kr/*",
38+
"https://www.hackerrank.com/*",
3739
"https://github.com/*"
3840
],
3941
"js": ["content.js"]
4042
}
43+
],
44+
"web_accessible_resources": [
45+
{
46+
"resources": ["hackerrank-page-bridge.js"],
47+
"matches": ["https://www.hackerrank.com/*"]
48+
}
4149
]
4250
}

public/hackerrank-page-bridge.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
(function () {
2+
if (window.__algorithmHubHackerrankBridgeInstalled) {
3+
return;
4+
}
5+
6+
window.__algorithmHubHackerrankBridgeInstalled = true;
7+
8+
var EVENT_NAME = "algorithmhub:hackerrank-submission";
9+
var SUBMISSION_PATH_PATTERN =
10+
/^\/rest\/contests\/([^/]+)\/challenges\/([^/]+)\/submissions(?:\/(\d+))?\/?$/;
11+
12+
function getRequestUrl(input) {
13+
if (typeof input === "string") {
14+
return input;
15+
}
16+
17+
if (input && typeof input.url === "string") {
18+
return input.url;
19+
}
20+
21+
return "";
22+
}
23+
24+
function getRequestMethod(input, init) {
25+
var initMethod = init && typeof init.method === "string" ? init.method : "";
26+
if (initMethod) {
27+
return initMethod.toUpperCase();
28+
}
29+
30+
var inputMethod = input && typeof input.method === "string" ? input.method : "";
31+
return (inputMethod || "GET").toUpperCase();
32+
}
33+
34+
function parseSubmissionRequest(url, method) {
35+
try {
36+
var parsedUrl = new URL(url, window.location.href);
37+
var match = parsedUrl.pathname.match(SUBMISSION_PATH_PATTERN);
38+
if (!match) {
39+
return null;
40+
}
41+
42+
var submissionId = match[3] || "";
43+
if (method === "POST" && submissionId) {
44+
return null;
45+
}
46+
47+
if (method === "GET" && !submissionId) {
48+
return null;
49+
}
50+
51+
if (method !== "POST" && method !== "GET") {
52+
return null;
53+
}
54+
55+
return {
56+
contestSlug: match[1],
57+
challengeSlug: match[2],
58+
submissionId: submissionId,
59+
};
60+
} catch (_error) {
61+
return null;
62+
}
63+
}
64+
65+
function publishSubmission(body, request) {
66+
var model = body && body.model;
67+
if (!model || !model.id) {
68+
return;
69+
}
70+
71+
window.dispatchEvent(
72+
new CustomEvent(EVENT_NAME, {
73+
detail: JSON.stringify({
74+
submissionId: String(model.id || request.submissionId),
75+
contestSlug: model.contest_slug || request.contestSlug,
76+
challengeSlug:
77+
model.challenge_slug || model.slug || request.challengeSlug,
78+
submission: model,
79+
}),
80+
})
81+
);
82+
}
83+
84+
function readJsonResponseText(text, request) {
85+
if (!text) {
86+
return;
87+
}
88+
89+
try {
90+
publishSubmission(JSON.parse(text), request);
91+
} catch (_error) {
92+
// Ignore non-JSON responses.
93+
}
94+
}
95+
96+
if (typeof window.fetch === "function") {
97+
var originalFetch = window.fetch;
98+
window.fetch = function (input, init) {
99+
var request = parseSubmissionRequest(
100+
getRequestUrl(input),
101+
getRequestMethod(input, init)
102+
);
103+
104+
return originalFetch.apply(this, arguments).then(function (response) {
105+
if (request) {
106+
response
107+
.clone()
108+
.json()
109+
.then(function (body) {
110+
publishSubmission(body, request);
111+
})
112+
.catch(function () {
113+
// The page still receives the original response.
114+
});
115+
}
116+
117+
return response;
118+
});
119+
};
120+
}
121+
122+
var originalOpen = XMLHttpRequest.prototype.open;
123+
var originalSend = XMLHttpRequest.prototype.send;
124+
125+
XMLHttpRequest.prototype.open = function (method, url) {
126+
this.__algorithmHubHackerrankRequest = parseSubmissionRequest(
127+
String(url || ""),
128+
String(method || "GET").toUpperCase()
129+
);
130+
return originalOpen.apply(this, arguments);
131+
};
132+
133+
XMLHttpRequest.prototype.send = function () {
134+
if (this.__algorithmHubHackerrankRequest) {
135+
this.addEventListener("load", function () {
136+
var request = this.__algorithmHubHackerrankRequest;
137+
if (!request) {
138+
return;
139+
}
140+
141+
if (this.responseType === "json") {
142+
publishSubmission(this.response, request);
143+
return;
144+
}
145+
146+
if (!this.responseType || this.responseType === "text") {
147+
readJsonResponseText(this.responseText, request);
148+
}
149+
});
150+
}
151+
152+
return originalSend.apply(this, arguments);
153+
};
154+
})();

0 commit comments

Comments
 (0)