Greasy Fork is available in English.
Unified CPH auto-submit helper for Codeforces, AtCoder, and NowCoder.
// ==UserScript==
// @name CPH Multi-Site Submit Helper
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description Unified CPH auto-submit helper for Codeforces, AtCoder, and NowCoder.
// @author Modified by OpenAI
// @match *://codeforces.com/*
// @match *://codeforces.ml/*
// @match https://atcoder.jp/*
// @match https://ac.nowcoder.com/*
// @match https://www.nowcoder.com/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_openInTab
// @grant unsafeWindow
// @connect localhost
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const CPH_SERVER_ENDPOINT = 'http://localhost:27121/getSubmit';
const MAX_HISTORY_ENTRIES = 30;
const SITE_KEYS = {
codeforces: {
submission: 'cph_multi_submission_codeforces',
settings: 'cph_multi_settings_codeforces',
history: 'cph_multi_history_codeforces'
},
atcoder: {
submission: 'cph_multi_submission_atcoder',
settings: 'cph_multi_settings_atcoder',
history: 'cph_multi_history_atcoder'
},
nowcoder: {
submission: 'cph_multi_submission_nowcoder',
settings: 'cph_multi_settings_nowcoder',
history: 'cph_multi_history_nowcoder'
}
};
const DEFAULT_SETTINGS = {
codeforces: {
pollingEnabled: true,
loopTimeout: 3000,
debug: false,
autoSubmit: true,
submitDelay: 0
},
atcoder: {
pollingEnabled: true,
loopTimeout: 3000,
debug: true,
autoSubmit: true,
submitDelay: 5,
aceFillRetries: 3,
languageMappings: JSON.stringify({
cpp: { name: 'C++ (GCC)', cph_ids: [54, 50, 42, 61, 89, 91, 52], new_id: '6017', old_id: '5028' },
python_pypy: { name: 'Python (PyPy)', cph_ids: [40, 41, 70, 31, 7], new_id: '6083', old_id: '5078' }
}, null, 2)
},
nowcoder: {
pollingEnabled: true,
loopTimeout: 3000,
debug: true,
autoSubmit: true,
submitDelay: 3,
languageMappings: JSON.stringify({
cpp: { name: 'C++', cph_ids: [54, 50, 42, 61, 89, 91, 52], nowcoder_text: 'C++(clang++18)', nowcoder_text_new: 'C++' },
c: { name: 'C', cph_ids: [48, 49, 53], nowcoder_text: 'C(gcc 10)', nowcoder_text_new: 'C' },
java: { name: 'Java', cph_ids: [62], nowcoder_text: 'Java', nowcoder_text_new: 'Java' },
python3: { name: 'Python 3', cph_ids: [71, 70], nowcoder_text: 'pypy3', nowcoder_text_new: 'PyPy3' },
python2: { name: 'Python 2', cph_ids: [69], nowcoder_text: 'Python2', nowcoder_text_new: 'Python2' },
go: { name: 'Go', cph_ids: [60, 95], nowcoder_text: 'Go', nowcoder_text_new: 'Go' },
csharp: { name: 'C#', cph_ids: [51], nowcoder_text: 'C#', nowcoder_text_new: 'C#' },
js_node: { name: 'JavaScript', cph_ids: [63], nowcoder_text: 'JavaScript (Node)', nowcoder_text_new: 'JavaScript Node' },
ts: { name: 'TypeScript', cph_ids: [74], nowcoder_text: 'TypeScript', nowcoder_text_new: 'TypeScript' },
php: { name: 'PHP', cph_ids: [68], nowcoder_text: 'PHP', nowcoder_text_new: 'PHP' },
rust: { name: 'Rust', cph_ids: [73], nowcoder_text: 'Rust', nowcoder_text_new: 'Rust' },
kotlin: { name: 'Kotlin', cph_ids: [72], nowcoder_text: 'Kotlin', nowcoder_text_new: 'Kotlin' }
}, null, 2)
}
};
const currentSite = detectSite();
if (!currentSite) {
return;
}
const storageKeys = SITE_KEYS[currentSite];
let settings = { ...DEFAULT_SETTINGS[currentSite] };
let pollingIntervalId = null;
function detectSite() {
const host = window.location.hostname;
if (host.includes('codeforces.com') || host.includes('codeforces.ml')) return 'codeforces';
if (host.includes('atcoder.jp')) return 'atcoder';
if (host.includes('nowcoder.com')) return 'nowcoder';
return null;
}
function debugLog(...args) {
if (!settings.debug) return;
const tag = `[cph-${currentSite}]`;
const message = args.map((arg) => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
console.log(tag, message);
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const immediate = document.querySelector(selector);
if (immediate) {
resolve(immediate);
return;
}
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
clearTimeout(timer);
observer.disconnect();
resolve(element);
}
});
const timer = setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for ${selector}`));
}, timeout);
observer.observe(document.body, { childList: true, subtree: true });
});
}
function normalizeUrl(url) {
try {
const parsed = new URL(url, window.location.origin);
return `${parsed.origin}${parsed.pathname}`;
} catch {
return url;
}
}
async function readJson(key, fallback) {
try {
const raw = await GM_getValue(key, null);
if (!raw) return fallback;
return JSON.parse(raw);
} catch (error) {
console.error(`[cph-${currentSite}] Failed to parse storage key ${key}:`, error);
return fallback;
}
}
async function writeJson(key, value) {
await GM_setValue(key, JSON.stringify(value));
}
async function logSubmission(submissionData) {
const history = await readJson(storageKeys.history, []);
history.unshift({
timestamp: new Date().toISOString(),
problemName: submissionData.problemName || 'Unknown',
url: submissionData.url,
languageId: submissionData.languageId,
sourceCode: submissionData.sourceCode,
rawJson: JSON.stringify(submissionData, null, 2)
});
await writeJson(storageKeys.history, history.slice(0, MAX_HISTORY_ENTRIES));
}
async function loadSettings() {
const saved = await readJson(storageKeys.settings, null);
settings = saved ? { ...DEFAULT_SETTINGS[currentSite], ...saved } : { ...DEFAULT_SETTINGS[currentSite] };
if (currentSite === 'nowcoder') {
settings.languageMappings = DEFAULT_SETTINGS.nowcoder.languageMappings;
}
}
async function saveSettings(newSettings) {
settings = { ...settings, ...newSettings };
await writeJson(storageKeys.settings, settings);
startPolling();
}
function startPolling() {
if (pollingIntervalId) {
clearInterval(pollingIntervalId);
pollingIntervalId = null;
}
if (!settings.pollingEnabled) {
debugLog('Polling disabled.');
return;
}
pollCphServer();
pollingIntervalId = setInterval(pollCphServer, settings.loopTimeout);
debugLog(`Polling every ${settings.loopTimeout}ms.`);
}
function taskBelongsToCurrentSite(data) {
if (!data || !data.url) return false;
if (currentSite === 'codeforces') return data.url.includes('codeforces.com') || data.url.includes('codeforces.ml');
if (currentSite === 'atcoder') return data.url.includes('atcoder.jp');
if (currentSite === 'nowcoder') return data.url.includes('nowcoder.com');
return false;
}
function pollCphServer() {
GM_xmlhttpRequest({
method: 'GET',
url: CPH_SERVER_ENDPOINT,
headers: { 'cph-submit': 'true' },
timeout: Math.max(1000, settings.loopTimeout - 500),
onload: async (response) => {
if (response.status !== 200) return;
try {
const data = JSON.parse(response.responseText);
if (!data || data.empty || !taskBelongsToCurrentSite(data)) {
return;
}
debugLog('Received CPH task:', data.problemName || data.url);
await logSubmission(data);
await GM_setValue(storageKeys.submission, JSON.stringify(data));
const targetUrl = getSubmitUrlForSite(data.url);
const samePage = normalizeUrl(window.location.href) === normalizeUrl(targetUrl);
if (samePage) {
fillAndSubmitCurrentSite();
} else {
GM_openInTab(targetUrl, { active: true });
}
} catch (error) {
debugLog('Failed to parse CPH response:', error);
}
},
onerror: () => debugLog('Could not connect to CPH server.'),
ontimeout: () => debugLog('CPH request timed out.')
});
}
function getSubmitUrlForSite(problemUrl) {
if (currentSite === 'codeforces') {
const contestUrl = problemUrl.includes('/contest/');
if (!contestUrl) return 'https://codeforces.com/problemset/submit';
try {
const parsed = new URL(problemUrl);
const contestId = parsed.pathname.split('/')[2];
return `https://codeforces.com/contest/${contestId}/submit`;
} catch {
return 'https://codeforces.com/problemset/submit';
}
}
if (currentSite === 'atcoder') {
try {
const parsed = new URL(problemUrl);
if (parsed.pathname.includes('/tasks/')) {
return `${parsed.origin}${parsed.pathname.split('/tasks/')[0]}/submit`;
}
} catch {
return problemUrl;
}
}
return problemUrl;
}
async function fillAndSubmitCurrentSite() {
const raw = await GM_getValue(storageKeys.submission, null);
if (!raw) return;
await GM_setValue(storageKeys.submission, null);
let data;
try {
data = JSON.parse(raw);
} catch (error) {
console.error(`[cph-${currentSite}] Invalid pending submission data:`, error);
return;
}
if (currentSite === 'codeforces') {
await fillCodeforces(data);
return;
}
if (currentSite === 'atcoder') {
await fillAtCoder(data);
return;
}
await fillNowCoder(data);
}
async function fillCodeforces(data) {
const langEl = document.querySelector('select[name="programTypeId"]');
const sourceEl = document.querySelector('textarea[name="source"]');
const submitBtn = document.querySelector('input.submit');
if (!langEl || !sourceEl || !submitBtn) {
console.error('[cph-codeforces] Missing submit form elements.');
return;
}
sourceEl.value = data.sourceCode;
sourceEl.dispatchEvent(new Event('input', { bubbles: true }));
langEl.value = String(data.languageId);
langEl.dispatchEvent(new Event('change', { bubbles: true }));
if (data.url.includes('/contest/')) {
const problemIndexEl = document.querySelector('select[name="submittedProblemIndex"]');
if (problemIndexEl) {
problemIndexEl.value = data.url.split('/problem/')[1];
problemIndexEl.dispatchEvent(new Event('change', { bubbles: true }));
}
} else {
const problemEl = document.querySelector('input[name="submittedProblemCode"]');
if (problemEl) {
problemEl.value = data.problemName;
problemEl.dispatchEvent(new Event('input', { bubbles: true }));
}
}
submitBtn.disabled = false;
if (settings.autoSubmit) {
setTimeout(() => submitBtn.click(), Math.max(0, settings.submitDelay) * 1000);
}
}
function getAtCoderTargetLanguageId(cphLangId) {
const visibleSelect = document.querySelector('#select-lang > div[style*="display: block"] select.form-control');
if (!visibleSelect) return String(cphLangId);
const firstOption = visibleSelect.querySelector('option[value]');
if (!firstOption || !firstOption.value) return String(cphLangId);
const isNewContest = parseInt(firstOption.value, 10) >= 6000;
try {
const mappings = JSON.parse(settings.languageMappings);
for (const key of Object.keys(mappings)) {
const mapping = mappings[key];
if (mapping.cph_ids && mapping.cph_ids.includes(cphLangId)) {
return isNewContest ? mapping.new_id : mapping.old_id;
}
}
} catch (error) {
console.error('[cph-atcoder] Failed to parse language mappings:', error);
}
return String(cphLangId);
}
function fillAceEditorViaInjection(sourceCode) {
const script = document.createElement('script');
const scriptId = `cph-atcoder-inject-${Date.now()}`;
script.id = scriptId;
script.textContent = `
(function() {
const codeToFill = ${JSON.stringify(sourceCode)};
let retries = ${Math.max(1, settings.aceFillRetries || 3)};
const selfId = ${JSON.stringify(scriptId)};
function cleanup() {
const self = document.getElementById(selfId);
if (self) self.remove();
}
function attempt() {
try {
if (window.ace) {
const editor = window.ace.edit('editor');
if (editor && editor.session) {
editor.session.setValue(codeToFill, 1);
cleanup();
return;
}
}
} catch (error) {
console.error('[cph-atcoder][inject]', error);
}
if (retries > 0) {
retries -= 1;
setTimeout(attempt, 500);
return;
}
const fallback = document.getElementById('plain-textarea');
if (fallback) {
fallback.value = codeToFill;
fallback.dispatchEvent(new Event('input', { bubbles: true }));
}
cleanup();
}
attempt();
})();
`;
(document.head || document.documentElement).appendChild(script);
}
async function fillAtCoder(data) {
const taskEl = document.querySelector('select[name="data.TaskScreenName"]');
const submitBtn = document.getElementById('submit');
if (!taskEl || !submitBtn) {
console.error('[cph-atcoder] Missing submit form elements.');
return;
}
const problemId = data.url.split('/tasks/')[1];
if (!problemId) {
console.error('[cph-atcoder] Could not parse problem ID from URL:', data.url);
return;
}
taskEl.value = problemId;
taskEl.dispatchEvent(new Event('change', { bubbles: true }));
await wait(250);
const langEl = document.querySelector('#select-lang > div[style*="display: block"] select[name="data.LanguageId"]');
if (!langEl) {
console.error('[cph-atcoder] Visible language selector not found.');
return;
}
langEl.value = getAtCoderTargetLanguageId(data.languageId);
langEl.dispatchEvent(new Event('change', { bubbles: true }));
fillAceEditorViaInjection(data.sourceCode);
submitBtn.disabled = false;
if (settings.autoSubmit) {
setTimeout(() => submitBtn.click(), Math.max(0, settings.submitDelay) * 1000);
}
}
function isNowCoderNewEditor() {
return Boolean(document.querySelector('.monaco-editor'));
}
function getNowCoderTargetLanguageText(cphLangId, isNewEditor) {
try {
const mappings = JSON.parse(settings.languageMappings);
for (const key of Object.keys(mappings)) {
const mapping = mappings[key];
if (mapping.cph_ids && mapping.cph_ids.includes(cphLangId)) {
return isNewEditor ? mapping.nowcoder_text_new : mapping.nowcoder_text;
}
}
} catch (error) {
console.error('[cph-nowcoder] Failed to parse language mappings:', error);
}
return null;
}
function fillNowCoderMonaco(sourceCode) {
const script = document.createElement('script');
script.textContent = `
(function() {
const codeToFill = ${JSON.stringify(sourceCode)};
try {
const editorApi = document.querySelector('.default-monaco-editor').__vue__.$refs.editor.getEditorApi();
if (editorApi && typeof editorApi.setValue === 'function') {
editorApi.setValue(codeToFill);
return;
}
} catch (error) {
console.error('[cph-nowcoder][inject]', error);
}
if (window.monaco && typeof window.monaco.editor.getModels === 'function') {
const models = window.monaco.editor.getModels();
if (models.length > 0) {
models[0].setValue(codeToFill);
}
}
})();
`;
(document.head || document.documentElement).appendChild(script);
script.remove();
}
function fillNowCoderCodeMirror(sourceCode) {
const script = document.createElement('script');
script.textContent = `
(function() {
const codeToFill = ${JSON.stringify(sourceCode)};
const editor = document.querySelector('.CodeMirror');
if (editor && editor.CodeMirror) {
editor.CodeMirror.setValue(codeToFill);
editor.CodeMirror.save();
}
})();
`;
(document.head || document.documentElement).appendChild(script);
script.remove();
}
async function selectNowCoderLanguage(targetText) {
if (!targetText) return;
const newEditor = isNowCoderNewEditor();
const trigger = newEditor
? document.querySelector('.monaco-toolbar-left .el-select')
: document.querySelector('.btn-language');
if (!trigger) {
console.error('[cph-nowcoder] Language selector trigger not found.');
return;
}
trigger.click();
await wait(300);
const options = Array.from(document.querySelectorAll('.el-select-dropdown__item span'));
const exact = options.find((option) => option.textContent.trim() === targetText);
const fuzzy = options.find((option) => option.textContent.trim().startsWith(targetText));
const chosen = exact || fuzzy;
if (!chosen) {
console.error('[cph-nowcoder] Language not found:', targetText);
return;
}
const item = chosen.closest('li.el-select-dropdown__item');
if (item) {
item.click();
await wait(200);
}
}
async function fillNowCoder(data) {
const newEditor = isNowCoderNewEditor();
const editorSelector = newEditor ? '.monaco-editor' : '.CodeMirror';
const submitBtnSelector = newEditor ? '.submit-btn' : '.btn-submit';
try {
await waitForElement(editorSelector);
} catch (error) {
console.error('[cph-nowcoder] Editor did not become available:', error);
return;
}
const submitBtn = document.querySelector(submitBtnSelector);
if (!submitBtn) {
console.error('[cph-nowcoder] Submit button not found.');
return;
}
const targetLangText = getNowCoderTargetLanguageText(data.languageId, newEditor);
await selectNowCoderLanguage(targetLangText);
if (newEditor) {
fillNowCoderMonaco(data.sourceCode);
} else {
fillNowCoderCodeMirror(data.sourceCode);
}
if (settings.autoSubmit) {
setTimeout(() => submitBtn.click(), Math.max(0, settings.submitDelay) * 1000);
}
}
function createBasePanel(title, includeMappings = false) {
GM_addStyle(`
#cph-multi-settings-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 720px;
max-height: 85vh;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
z-index: 10000;
display: none;
flex-direction: column;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
}
#cph-multi-settings-panel .header {
padding: 14px 18px;
background: #f6f7f9;
border-bottom: 1px solid #e5e7eb;
font-weight: 700;
display: flex;
justify-content: space-between;
align-items: center;
}
#cph-multi-settings-panel .close-btn {
cursor: pointer;
font-size: 20px;
color: #666;
}
#cph-multi-settings-panel .content {
padding: 18px;
overflow-y: auto;
flex: 1;
}
#cph-multi-settings-panel .row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
#cph-multi-settings-panel input[type="number"] {
width: 100px;
padding: 5px 8px;
}
#cph-multi-settings-panel textarea {
width: 100%;
min-height: 220px;
font-family: Consolas, monospace;
font-size: 12px;
padding: 8px;
}
#cph-multi-history .entry {
border: 1px solid #eee;
border-radius: 6px;
padding: 10px;
margin-bottom: 10px;
background: #fafafa;
}
#cph-multi-history details {
margin-top: 8px;
}
#cph-multi-history pre {
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-word;
background: #fff;
border: 1px solid #eee;
border-radius: 4px;
padding: 8px;
}
#cph-multi-settings-panel .actions {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 12px;
}
#cph-multi-settings-panel button {
cursor: pointer;
padding: 6px 10px;
}
`);
const panel = document.createElement('div');
panel.id = 'cph-multi-settings-panel';
panel.innerHTML = `
<div class="header">
<span>${escapeHtml(title)}</span>
<span class="close-btn">×</span>
</div>
<div class="content">
<div class="row">
<label>Enable Polling</label>
<input type="checkbox" id="cph-setting-polling">
</div>
<div class="row">
<label>Enable Debug Logs</label>
<input type="checkbox" id="cph-setting-debug">
</div>
<div class="row">
<label>Polling Interval (ms)</label>
<input type="number" id="cph-setting-timeout" min="1000" step="500">
</div>
<div class="row">
<label>Auto Submit</label>
<input type="checkbox" id="cph-setting-auto-submit">
</div>
<div class="row">
<label>Submit Delay (s)</label>
<input type="number" id="cph-setting-submit-delay" min="0" step="1">
</div>
${currentSite === 'atcoder' ? `
<div class="row">
<label>ACE Fill Retries</label>
<input type="number" id="cph-setting-ace-retries" min="1" step="1">
</div>
` : ''}
${includeMappings ? `
<hr>
<div>
<label>Language Mappings (JSON)</label>
<textarea id="cph-setting-mappings"></textarea>
<div class="actions">
<button id="cph-save-mappings">Save Mappings</button>
</div>
</div>
` : ''}
<hr>
<div style="display:flex; justify-content:space-between; align-items:center;">
<strong>Submission History</strong>
<div class="actions" style="margin-top:0;">
<button id="cph-refresh-history">Refresh</button>
<button id="cph-clear-history">Clear</button>
</div>
</div>
<div id="cph-multi-history" style="margin-top:12px;"></div>
</div>
`;
document.body.appendChild(panel);
panel.querySelector('.close-btn').addEventListener('click', () => {
panel.style.display = 'none';
});
document.getElementById('cph-setting-polling').addEventListener('change', (event) => {
saveSettings({ pollingEnabled: event.target.checked });
});
document.getElementById('cph-setting-debug').addEventListener('change', (event) => {
saveSettings({ debug: event.target.checked });
});
document.getElementById('cph-setting-timeout').addEventListener('change', (event) => {
saveSettings({ loopTimeout: parseInt(event.target.value, 10) || DEFAULT_SETTINGS[currentSite].loopTimeout });
});
document.getElementById('cph-setting-auto-submit').addEventListener('change', (event) => {
saveSettings({ autoSubmit: event.target.checked });
});
document.getElementById('cph-setting-submit-delay').addEventListener('change', (event) => {
saveSettings({ submitDelay: parseInt(event.target.value, 10) || 0 });
});
if (currentSite === 'atcoder') {
document.getElementById('cph-setting-ace-retries').addEventListener('change', (event) => {
saveSettings({ aceFillRetries: parseInt(event.target.value, 10) || DEFAULT_SETTINGS.atcoder.aceFillRetries });
});
}
if (includeMappings) {
document.getElementById('cph-save-mappings').addEventListener('click', async () => {
const text = document.getElementById('cph-setting-mappings').value;
try {
JSON.parse(text);
await saveSettings({ languageMappings: text });
alert('Language mappings saved.');
} catch (error) {
alert(`Invalid JSON: ${error.message}`);
}
});
}
document.getElementById('cph-refresh-history').addEventListener('click', renderHistory);
document.getElementById('cph-clear-history').addEventListener('click', async () => {
await writeJson(storageKeys.history, []);
renderHistory();
});
return panel;
}
async function renderHistory() {
const container = document.getElementById('cph-multi-history');
if (!container) return;
const history = await readJson(storageKeys.history, []);
if (!history.length) {
container.innerHTML = '<p>No submissions recorded yet.</p>';
return;
}
container.innerHTML = history.map((entry) => `
<div class="entry">
<div><strong>Time:</strong> ${escapeHtml(new Date(entry.timestamp).toLocaleString())}</div>
<div><strong>Problem:</strong> <a href="${escapeHtml(entry.url)}" target="_blank">${escapeHtml(entry.problemName)}</a></div>
<div><strong>Language ID:</strong> ${escapeHtml(entry.languageId)}</div>
<details>
<summary>View Source</summary>
<pre>${escapeHtml(entry.sourceCode)}</pre>
</details>
</div>
`).join('');
}
function syncPanelValues(includeMappings) {
const polling = document.getElementById('cph-setting-polling');
const debug = document.getElementById('cph-setting-debug');
const timeout = document.getElementById('cph-setting-timeout');
const autoSubmit = document.getElementById('cph-setting-auto-submit');
const submitDelay = document.getElementById('cph-setting-submit-delay');
if (polling) polling.checked = Boolean(settings.pollingEnabled);
if (debug) debug.checked = Boolean(settings.debug);
if (timeout) timeout.value = settings.loopTimeout;
if (autoSubmit) autoSubmit.checked = Boolean(settings.autoSubmit);
if (submitDelay) submitDelay.value = settings.submitDelay || 0;
if (currentSite === 'atcoder') {
const aceRetries = document.getElementById('cph-setting-ace-retries');
if (aceRetries) aceRetries.value = settings.aceFillRetries || DEFAULT_SETTINGS.atcoder.aceFillRetries;
}
if (includeMappings) {
const mappings = document.getElementById('cph-setting-mappings');
if (mappings) {
try {
mappings.value = JSON.stringify(JSON.parse(settings.languageMappings), null, 2);
} catch {
mappings.value = settings.languageMappings || '';
}
}
}
}
function togglePanel(includeMappings) {
const panel = document.getElementById('cph-multi-settings-panel');
if (!panel) return;
const showing = panel.style.display === 'flex';
if (showing) {
panel.style.display = 'none';
return;
}
syncPanelValues(includeMappings);
renderHistory();
panel.style.display = 'flex';
}
function injectEntryPoint() {
const includeMappings = currentSite === 'atcoder' || currentSite === 'nowcoder';
createBasePanel(`CPH Settings (${currentSite})`, includeMappings);
if (currentSite === 'codeforces') {
const ratingLink = document.querySelector('.menu-list a[href="/ratings"]');
if (ratingLink && ratingLink.parentElement) {
const item = document.createElement('li');
item.innerHTML = '<a href="#" id="cph-multi-open-settings" style="color:#00aaff;">CPH Settings</a>';
ratingLink.parentElement.insertAdjacentElement('afterend', item);
}
} else if (currentSite === 'atcoder') {
const navbar = document.querySelector('.nav.navbar-nav');
if (navbar) {
const item = document.createElement('li');
item.innerHTML = '<a href="#" id="cph-multi-open-settings" style="color:#00aaff; font-weight:bold;">CPH Settings</a>';
navbar.appendChild(item);
}
} else if (currentSite === 'nowcoder') {
const button = document.createElement('div');
button.id = 'cph-multi-open-settings';
button.textContent = 'CPH';
button.style.cssText = 'position:fixed; bottom:20px; right:20px; z-index:9999; width:42px; height:42px; border-radius:50%; background:#28a745; color:#fff; text-align:center; line-height:42px; cursor:pointer; box-shadow:0 2px 8px rgba(0,0,0,0.25); font-weight:bold; user-select:none;';
document.body.appendChild(button);
}
const openButton = document.getElementById('cph-multi-open-settings');
if (openButton) {
openButton.addEventListener('click', (event) => {
event.preventDefault();
togglePanel(includeMappings);
});
}
}
async function main() {
await loadSettings();
injectEntryPoint();
startPolling();
if (currentSite === 'codeforces' && window.location.href.includes('/submit')) {
setTimeout(fillAndSubmitCurrentSite, 500);
}
if (currentSite === 'atcoder' && window.location.pathname.endsWith('/submit')) {
setTimeout(fillAndSubmitCurrentSite, 750);
}
if (currentSite === 'nowcoder') {
setTimeout(fillAndSubmitCurrentSite, 1500);
}
debugLog('Unified helper initialized.');
}
main();
})();