Skip to content
Open
2 changes: 2 additions & 0 deletions manifest-chrome.partial.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"http://localhost/*",
"https://cses.fi/*",
"https://codeforces.com/*",
"*://*.codechef.com/*",
"*://*.atcoder.jp/*",
"https://maang.in/*"
],
"background": {
Expand Down
2 changes: 2 additions & 0 deletions manifest-firefox.partial.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"*://localhost/*",
"https://cses.fi/*",
"*://codeforces.com/*",
"*://*.codechef.com/*",
"*://*.atcoder.jp/*",
"*://maang.in/*",
"webNavigation"
],
Expand Down
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# ![ICON](icon-48.png) cph-submit

Browser add-on that enables direct submission on Codeforces,CSES and Maang.in
with[Competitive Programming Helper](https://github.com/agrawal-d/cph).
Browser add-on that enables direct submission on Codeforces,CodeChef,AtCoder
CSES and Maang.in with
[Competitive Programming Helper](https://github.com/agrawal-d/cph).

Available for Firefox and Google Chrome.

Expand Down
86 changes: 86 additions & 0 deletions src/atcoderInjectedScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ContentScriptData } from './types';
import log from './log';

declare const ace: any;

log('cph-submit atcoder script injected');

const idToAtcoderLanguage: Record<number, string> = {
91: 'C++23 (GCC 15.2.0)',
89: 'C++ IOI-Style(GNU++20) (GCC 14.2.0)',
52: 'C++23 (Clang 21.1.0)',
43: 'C23 (GCC 14.2.0)',
79: 'C# 13.0 (.NET 9.0.8)',
28: 'D (DMD 2.111.0)',
87: 'Java24 (OpenJDK 24.0.2)',
88: 'Kotlin (Kotlin/JVM 2.2.10)',
19: 'OCaml (ocamlopt 5.3.0)',
4: 'Pascal (fpc 3.2.2)',
13: 'Perl (perl 5.38.2)',
6: 'PHP (PHP 8.4.12)',
55: 'JavaScript (Node.js 22.19.0)',
31: 'Python (CPython 3.13.7)',
70: 'Python (PyPy 3.11-v7.3.20)',
67: 'Ruby 3.4 (ruby 3.4.5)',
32: 'Go (go 1.25.1)',
98: 'Rust (rustc 1.89.0)',
20: 'Scala (Dotty 3.7.2)',
12: 'Haskell (GHC 9.8.4)',
};

chrome.runtime.onMessage.addListener(async (message) => {
if (message.type !== 'cph-submit-Atcoder') return;

const { sourceCode, languageId } = message;
const languageText = idToAtcoderLanguage[languageId];

if (!languageText) {
alert('The following language is not supported');
return;
}

const selectBox = document.querySelector(
'select[name="data.LanguageId"]',
) as HTMLSelectElement;

if (selectBox) {
const options = Array.from(selectBox.options);
const targetOption = options.find(
(opt) => opt.text.trim() === languageText,
);

if (targetOption) {
const atcoderValue = targetOption.value;
selectBox.value = atcoderValue;
selectBox.dispatchEvent(new Event('change', { bubbles: true }));
log(
`Successfully set language to ${languageText} (Value: ${atcoderValue})`,
);
} else {
log(
`Error: Could not find an option matching the text "${languageText}" in the dropdown.`,
);
}
} else {
log(
'Error: Could not find the AtCoder language select box on this page.',
);
}
await new Promise((r) => setTimeout(r, 1500));
/* there is no cloudfare verification during live contests*/
const tokenInput = document.querySelector(
'input[name="cf-turnstile-response"]',
) as HTMLInputElement | null;

if (!tokenInput) {
document.getElementById('submit')?.click();
return;
}

const cloudFareVerificationChecker = setInterval(() => {
if (tokenInput && tokenInput.value) {
clearInterval(cloudFareVerificationChecker);
document.getElementById('submit')?.click();
}
}, 250);
});
109 changes: 109 additions & 0 deletions src/codeChefInjectedScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { ContentScriptData } from './types';
import log from './log';

declare const ace: any;

log(`cph-submit Codechef script injected`);

const idToCodechefLanguage: Record<number, string> = {
42: 'C++',
50: 'C++',
54: 'C++',
61: 'C++',
89: 'C++',
91: 'C++',
52: 'C++',
43: 'C',
36: 'Java',
60: 'Java',
87: 'Java',
31: 'Python3',
7: 'Python3',
40: 'PyPy 3',
41: 'PyPy 3',
70: 'PyPy 3',
9: 'C#',
65: 'C#',
79: 'C#',
34: 'JavaScript',
55: 'JavaScript',
32: 'Go',
6: 'PHP',
83: 'Kotlin',
88: 'Kotlin',
75: 'Rust',
98: 'Rust',
};

const languageMap: Record<string, string> = {
'C++': 'C++',
Python3: 'PYTH 3',
C: 'C',
Java: 'JAVA',
'PyPy 3': 'PYPY3',
'C#': 'C#',
JavaScript: 'NODEJS',
Go: 'GO',
PHP: 'PHP',
Kotlin: 'KTLN',
Rust: 'rust',
};

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const simulateHumanClick = (element: HTMLElement) => {
['mouseover', 'mousedown', 'mouseup', 'click'].forEach((eventType) => {
element.dispatchEvent(
new MouseEvent(eventType, {
bubbles: true,
cancelable: true,
view: window,
buttons: 1,
}),
);
});
};

chrome.runtime.onMessage.addListener((message) => {
if (message.type !== 'cph-submit-codechef') return false;

const execute = async () => {
try {
const languageName = idToCodechefLanguage[message.languageId];
if (!languageName) {
alert('Current Language is not supported');
return;
}

const languageSelectBtn = document.querySelector(
'#language-select',
) as HTMLElement | null;
if (languageSelectBtn) {
simulateHumanClick(languageSelectBtn);
await sleep(300);

const options = Array.from(
document.querySelectorAll('[role="option"]'),
) as HTMLElement[];
const targetOption = options.find((opt) => {
const val = opt.getAttribute('data-value');
return (
val === languageMap[languageName] ||
opt.textContent?.trim() === languageName
);
});

if (targetOption) {
simulateHumanClick(targetOption);
await sleep(400);
}
}
} catch (error) {
log(error);
}
await new Promise((r) => setTimeout(r, 1500));
document.getElementById('submit_btn')?.click();
};

execute();
});
133 changes: 133 additions & 0 deletions src/handleSubmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import config from './config';
import log from './log';

declare const browser: any;
declare const ace: any; //Ace Editor

if (typeof browser !== 'undefined') {
self.chrome = browser;
Expand Down Expand Up @@ -100,6 +101,22 @@ export const isCSESProblem = (problemUrl: string) => {
return false;
}
};
export const isCodeChefProblem = (problemUrl: string) => {
try {
const url = new URL(problemUrl);
return url.hostname.endsWith('codechef.com');
} catch {
return false;
}
};
export const isAtcoderProblem = (problemUrl: string) => {
try {
const url = new URL(problemUrl);
return url.hostname.endsWith('atcoder.jp');
} catch {
return false;
}
};

export const handleAlgoZenithSubmit = async (
problemName: string,
Expand Down Expand Up @@ -202,6 +219,106 @@ export const handleCSESSubmit = async (
},
);
};
export const handleCodeChefSubmit = async (
problemName: string,
languageId: number,
sourceCode: string,
problemUrl: string,
) => {
const tab = await chrome.tabs.create({ active: true, url: problemUrl });
const tabId = tab.id as number;
chrome.windows.update(tab.windowId, { focused: true });

chrome.tabs.onUpdated.addListener(
function listener(updatedTabId, changeInfo) {
if (updatedTabId === tabId && changeInfo.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);

setTimeout(async () => {
await chrome.scripting.executeScript({
target: { tabId, allFrames: false },
files: ['/dist/codeChefInjectedScript.js'],
});

chrome.tabs.sendMessage(tabId, {
type: 'cph-submit-codechef',
languageId,
});

/**ChromeV3 don't Allow us to inject script due to CSP policy**/
setTimeout(async () => {
await chrome.scripting.executeScript({
target: { tabId, allFrames: false },
world: 'MAIN',
func: (code) => {
const w = window as any;
if (w.ace)
w.ace
.edit(
document.querySelector(
'.ace_editor',
),
)
.setValue(code, -1);
},
args: [sourceCode],
});
}, 1000);
}, 2000);
}
},
);
};
export const handleAtcoderSubmit = async (
problemName: string,
languageId: number,
sourceCode: string,
problemUrl: string,
) => {
const tab = await chrome.tabs.create({ active: true, url: problemUrl });
const tabId = tab.id as number;
chrome.windows.update(tab.windowId, { focused: true });

chrome.tabs.onUpdated.addListener(
function listener(updatedTabId, changeInfo) {
if (updatedTabId === tabId && changeInfo.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);

setTimeout(async () => {
await chrome.scripting.executeScript({
target: { tabId, allFrames: false },
files: ['/dist/atcoderInjectedScript.js'],
});

chrome.tabs.sendMessage(tabId, {
type: 'cph-submit-Atcoder',
languageId,
});

/**ChromeV3 don't Allow us to inject script due to CSP policy**/
setTimeout(async () => {
await chrome.scripting.executeScript({
target: { tabId, allFrames: false },
world: 'MAIN',
func: (code) => {
const w = window as any;
if (w.ace)
w.ace
.edit(
document.querySelector(
'.ace_editor',
),
)
.setValue(code, -1);
},
args: [sourceCode],
});
}, 1000);
}, 2000);
}
},
);
};
export const handleSubmit = async (
problemName: string,
languageId: number,
Expand Down Expand Up @@ -229,6 +346,22 @@ export const handleSubmit = async (
problemUrl,
);
}
if (isCodeChefProblem(problemUrl)) {
return handleCodeChefSubmit(
problemName,
languageId,
sourceCode,
problemUrl,
);
}
if (isAtcoderProblem(problemUrl)) {
return handleAtcoderSubmit(
problemName,
languageId,
sourceCode,
problemUrl,
);
}

const submitTarget = resolveSubmitTarget(problemUrl);

Expand Down
2 changes: 2 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module.exports = {
injectedScript: './src/injectedScript.ts',
algoZenithInjectedScript: './src/algoZenithInjectedScript.ts',
csesInjectedScript: './src/csesInjectedScript.ts',
codeChefInjectedScript: './src/codeChefInjectedScript.ts',
atcoderInjectedScript: './src/atcoderInjectedScript.ts',
offscreen: './src/offscreen.ts',
},
output: {
Expand Down
Loading