Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<p align="center">
A Chrome extension that automatically syncs accepted
<strong>LeetCode</strong> and <strong>Programmers</strong> submissions to GitHub.
<strong>LeetCode</strong>, <strong>Programmers</strong>, and <strong>HackerRank</strong> submissions to GitHub.
</p>

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

- LeetCode
- Programmers
- HackerRank

## Highlights

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

## How does AlgorithmHub work?

1. Connect your GitHub account and repository
2. Solve a problem on LeetCode or Programmers
2. Solve a problem on LeetCode, Programmers, or HackerRank
3. Submit an accepted solution
4. Let AlgorithmHub sync the solution files to GitHub automatically

| LeetCode Demo | Programmers Demo |
| --- | --- |
| ![LeetCode demo](./docs/leetcode-demo.gif) | ![Programmers demo](./docs/programmers-demo.gif) |
| LeetCode Demo | Programmers Demo | HackerRank Demo |
| --- | --- | --- |
| ![LeetCode demo](./docs/leetcode-demo.gif) | ![Programmers demo](./docs/programmers-demo.gif) | ![HackerRank demo](./docs/hackerrank-demo.gif) |

## Usage

1. Open the extension popup
2. Authenticate with GitHub
3. Create a repository or connect an existing one
4. Solve a problem on LeetCode or Programmers
4. Solve a problem on LeetCode, Programmers, or HackerRank
5. Submit an accepted solution
6. Let AlgorithmHub sync it to GitHub

Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>

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

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

- LeetCode
- 프로그래머스
- HackerRank

## 주요 특징

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

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

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

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

## 사용 방법

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

Expand Down
Binary file added docs/hackerrank-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "AlgorithmHub",
"description": "Unified Chrome extension that uploads accepted LeetCode and Programmers solutions to GitHub.",
"description": "Unified Chrome extension that uploads accepted LeetCode, Programmers, and HackerRank solutions to GitHub.",
"version": "1.0.0",
"icons": {
"16": "icons/icon-16.png",
Expand All @@ -26,6 +26,7 @@
"host_permissions": [
"https://leetcode.com/*",
"https://school.programmers.co.kr/*",
"https://www.hackerrank.com/*",
"https://github.com/*",
"https://api.github.com/*"
],
Expand All @@ -34,9 +35,16 @@
"matches": [
"https://leetcode.com/*",
"https://school.programmers.co.kr/*",
"https://www.hackerrank.com/*",
"https://github.com/*"
],
"js": ["content.js"]
}
],
"web_accessible_resources": [
{
"resources": ["hackerrank-page-bridge.js"],
"matches": ["https://www.hackerrank.com/*"]
}
]
}
154 changes: 154 additions & 0 deletions public/hackerrank-page-bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
(function () {
if (window.__algorithmHubHackerrankBridgeInstalled) {
return;
}

window.__algorithmHubHackerrankBridgeInstalled = true;

var EVENT_NAME = "algorithmhub:hackerrank-submission";
var SUBMISSION_PATH_PATTERN =
/^\/rest\/contests\/([^/]+)\/challenges\/([^/]+)\/submissions(?:\/(\d+))?\/?$/;

function getRequestUrl(input) {
if (typeof input === "string") {
return input;
}

if (input && typeof input.url === "string") {
return input.url;
}

return "";
}

function getRequestMethod(input, init) {
var initMethod = init && typeof init.method === "string" ? init.method : "";
if (initMethod) {
return initMethod.toUpperCase();
}

var inputMethod = input && typeof input.method === "string" ? input.method : "";
return (inputMethod || "GET").toUpperCase();
}

function parseSubmissionRequest(url, method) {
try {
var parsedUrl = new URL(url, window.location.href);
var match = parsedUrl.pathname.match(SUBMISSION_PATH_PATTERN);
if (!match) {
return null;
}

var submissionId = match[3] || "";
if (method === "POST" && submissionId) {
return null;
}

if (method === "GET" && !submissionId) {
return null;
}

if (method !== "POST" && method !== "GET") {
return null;
}

return {
contestSlug: match[1],
challengeSlug: match[2],
submissionId: submissionId,
};
} catch (_error) {
return null;
}
}

function publishSubmission(body, request) {
var model = body && body.model;
if (!model || !model.id) {
return;
}

window.dispatchEvent(
new CustomEvent(EVENT_NAME, {
detail: JSON.stringify({
submissionId: String(model.id || request.submissionId),
contestSlug: model.contest_slug || request.contestSlug,
challengeSlug:
model.challenge_slug || model.slug || request.challengeSlug,
submission: model,
}),
})
);
}

function readJsonResponseText(text, request) {
if (!text) {
return;
}

try {
publishSubmission(JSON.parse(text), request);
} catch (_error) {
// Ignore non-JSON responses.
}
}

if (typeof window.fetch === "function") {
var originalFetch = window.fetch;
window.fetch = function (input, init) {
var request = parseSubmissionRequest(
getRequestUrl(input),
getRequestMethod(input, init)
);

return originalFetch.apply(this, arguments).then(function (response) {
if (request) {
response
.clone()
.json()
.then(function (body) {
publishSubmission(body, request);
})
.catch(function () {
// The page still receives the original response.
});
}

return response;
});
};
}

var originalOpen = XMLHttpRequest.prototype.open;
var originalSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.open = function (method, url) {
this.__algorithmHubHackerrankRequest = parseSubmissionRequest(
String(url || ""),
String(method || "GET").toUpperCase()
);
return originalOpen.apply(this, arguments);
};

XMLHttpRequest.prototype.send = function () {
if (this.__algorithmHubHackerrankRequest) {
this.addEventListener("load", function () {
var request = this.__algorithmHubHackerrankRequest;
if (!request) {
return;
}

if (this.responseType === "json") {
publishSubmission(this.response, request);
return;
}

if (!this.responseType || this.responseType === "text") {
readJsonResponseText(this.responseText, request);
}
});
}

return originalSend.apply(this, arguments);
};
})();
Loading