diff --git a/manifest-chrome.partial.json b/manifest-chrome.partial.json index 399004f..85ef12c 100644 --- a/manifest-chrome.partial.json +++ b/manifest-chrome.partial.json @@ -3,6 +3,7 @@ "permissions": ["scripting", "webNavigation", "offscreen"], "host_permissions": [ "http://localhost/*", + "https://cses.fi/*", "https://codeforces.com/*", "https://maang.in/*" ], diff --git a/manifest-firefox.partial.json b/manifest-firefox.partial.json index fd764b9..ca092cf 100644 --- a/manifest-firefox.partial.json +++ b/manifest-firefox.partial.json @@ -6,6 +6,7 @@ "permissions": [ "scripting", "*://localhost/*", + "https://cses.fi/*", "*://codeforces.com/*", "*://maang.in/*", "webNavigation" diff --git a/readme.md b/readme.md index 945889c..d38ce65 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ # ![ICON](icon-48.png) cph-submit -Browser add on that enables Codeforces submit with -[Competitive Programming Helper](https://github.com/agrawal-d/cph) +Browser add-on that enables direct submission on Codeforces,CSES and Maang.in +with[Competitive Programming Helper](https://github.com/agrawal-d/cph). Available for Firefox and Google Chrome. diff --git a/src/csesInjectedScript.ts b/src/csesInjectedScript.ts new file mode 100644 index 0000000..626160d --- /dev/null +++ b/src/csesInjectedScript.ts @@ -0,0 +1,102 @@ +import { ContentScriptData } from './types'; +import log from './log'; + +declare const browser: any; +if (typeof browser !== 'undefined') { + self.chrome = browser; +} + +log('cph-submit cses script injected'); + +const extMap: Record = { + cpp: 'solution.cpp', + c: 'solution.c', + java: 'solution.java', + python: 'solution.py', + rust: 'solution.rs', + javascript: 'solution.js', + ruby: 'solution.rb', + haskell: 'solution.hs', + scala: 'solution.scala', + pascal: 'solution.pas', +}; + +const langMap: Record = { + cpp: 'C++', + c: 'C', + java: 'Java', + python: 'Python3', + rust: 'Rust', + javascript: 'Node.js', + ruby: 'Ruby', + haskell: 'Haskell', + scala: 'Scala', + pascal: 'Pascal', +}; +const idToKey: Record = { + 54: 'cpp', + 50: 'cpp', + 42: 'cpp', + 61: 'cpp', + 89: 'cpp', + 91: 'cpp', + 59: 'cpp', + 2: 'cpp', + 52: 'cpp', + 43: 'c', + 36: 'java', + 60: 'java', + 87: 'java', + 7: 'python', + 31: 'python', + 40: 'python', + 41: 'python', + 70: 'python', + 75: 'rust', + 98: 'rust', + 34: 'javascript', + 55: 'javascript', + 67: 'ruby', + 12: 'haskell', + 20: 'scala', + 3: 'pascal', + 4: 'pascal', + 51: 'pascal', +}; + +chrome.runtime.onMessage.addListener((message) => { + if (message.type !== 'cph-submit-cses') return; + + const { sourceCode, languageId } = message; + + const form = document.querySelector('form'); + if (!form) { + alert('CSES form not found'); + log('CSES form not found'); + return; + } + + const fileInput = form.querySelector( + 'input[type="file"]', + ) as HTMLInputElement; + if (fileInput) { + const langKey = idToKey[languageId] ?? 'cpp'; + + const fileName = extMap[langKey] ?? 'solution.cpp'; + const file = new File([sourceCode], fileName, { type: 'text/plain' }); + const dt = new DataTransfer(); + dt.items.add(file); + fileInput.files = dt.files; + } + + const langSelect = form.querySelector( + 'select[name="lang"]', + ) as HTMLSelectElement; + if (langSelect) { + const langKey = idToKey[languageId] ?? 'cpp'; + langSelect.value = langMap[langKey] ?? 'C++'; + } + + form.submit(); + log('CSES form submitted'); +}); diff --git a/src/handleSubmit.ts b/src/handleSubmit.ts index 8e2aaf4..81685a0 100644 --- a/src/handleSubmit.ts +++ b/src/handleSubmit.ts @@ -91,6 +91,15 @@ export const isAlgoZenithProblem = (problemUrl: string) => { return false; } }; +export const isCSESProblem = (problemUrl: string) => { + console.log(`problemURL-CSES ${problemUrl} `); + try { + const url = new URL(problemUrl); + return url.hostname === 'cses.fi' || url.hostname.endsWith('.cses.fi'); + } catch { + return false; + } +}; export const handleAlgoZenithSubmit = async ( problemName: string, @@ -140,7 +149,59 @@ export const handleAlgoZenithSubmit = async ( }, ); }; +export const handleCSESSubmit = async ( + problemName: string, + languageId: number, + sourceCode: string, + problemUrl: string, +) => { + log('Handling CSES submit for', problemUrl); + log('isCSES check:', isCSESProblem(problemUrl)); + const submitUrl = problemUrl + .replace('/task/', '/submit/') + .replace(/\/?$/, '/'); + + const tab = await chrome.tabs.create({ + active: true, + url: submitUrl, + }); + + 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 () => { + if (typeof chrome !== 'undefined' && chrome.scripting) { + await chrome.scripting.executeScript({ + target: { tabId, allFrames: false }, + files: ['/dist/csesInjectedScript.js'], + }); + } else if (typeof browser !== 'undefined') { + await browser.tabs.executeScript(tabId, { + file: '/dist/csesInjectedScript.js', + }); + } + setTimeout(() => { + chrome.tabs.sendMessage(tabId, { + type: 'cph-submit-cses', + problemName, + languageId, + sourceCode, + url: problemUrl, + }); + + log('Message sent to CSES tab'); + }, 500); + }, 1000); + } + }, + ); +}; export const handleSubmit = async ( problemName: string, languageId: number, @@ -160,6 +221,14 @@ export const handleSubmit = async ( problemUrl, ); } + if (isCSESProblem(problemUrl)) { + return handleCSESSubmit( + problemName, + languageId, + sourceCode, + problemUrl, + ); + } const submitTarget = resolveSubmitTarget(problemUrl); diff --git a/webpack.config.js b/webpack.config.js index 92da5af..d53f0e1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ module.exports = { backgroundScript: './src/backgroundScript.ts', injectedScript: './src/injectedScript.ts', algoZenithInjectedScript: './src/algoZenithInjectedScript.ts', + csesInjectedScript: './src/csesInjectedScript.ts', offscreen: './src/offscreen.ts', }, output: {