Advertisement
Anwar_Rizk

cf-fast-submit-v2.1

Oct 25th, 2023
475
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         cf-fast-submit
  3. // @name:ja      cf-fast-submit
  4. // @namespace    https://github.com/LumaKernel/cf-fast-submit
  5. // @version      2.10
  6. // @description  append the form to submit to codeforces contest problem page.
  7. // @description:ja codeforcesのコンテストの問題ページに提出フォームを置くツール.
  8. // @author       Luma
  9. // @match        http://codeforces.com/contest/*/problem/*
  10. // @match        http://codeforces.com/gym/*/problem/*
  11. // @match        http://codeforces.com/problemset/problem/*
  12. // @match        http://codeforces.com/group/*/contest/*/problem/*
  13. // @match        http://*.contest.codeforces.com/group/*/contest/*/problem/*
  14. // @match        https://codeforces.com/contest/*/problem/*
  15. // @match        https://codeforces.com/gym/*/problem/*
  16. // @match        https://codeforces.com/problemset/problem/*
  17. // @match        https://codeforces.com/group/*/contest/*/problem/*
  18. // @match        https://*.contest.codeforces.com/group/*/contest/*/problem/*
  19. // @grant        none
  20. // ==/UserScript==
  21.  
  22. /* global $ ace Codeforces */
  23.  
  24. ;(function () {
  25.   'use strict'
  26.  
  27.   const openNewWindow = false
  28.  
  29.   const SCRIPT_NAME = 'cf fast submit'
  30.   const origin = location.origin
  31.   const pathname = location.pathname
  32.   const modelist = ace.require('ace/ext/modelist')
  33.   const logged = !!$('a').filter((_, el) => $(el).text() === 'Logout').length
  34.   let $form
  35.   let $programType
  36.   let $toggleEditor
  37.   let $tabSize
  38.   let $selectProblem
  39.   let editor
  40.   // ~/0 というURLは A 問題として扱われる
  41.   const startId = '0'
  42.   const defaultProblemIds = ['A', 'A1']
  43.   const pattern = /(contest|gym)\/(.)\/problem\/([^/])\/?$/
  44.   const problemsetPattern = /problemset\/problem\/([^/])\/([^/])\/?$/
  45.   const groupPattern = /group\/([^/]+)\/contest\/([^/])\/problem\/([^/])\/?$/
  46.   let type // 'contest' | 'gym' | 'problemset' | 'group'
  47.   let submitURL
  48.   let problemId
  49.   let contestId
  50.   let participantId
  51.  
  52.  
  53.   // got from submit page
  54.   /* eslint-disable-next-line object-property-newline */
  55.   const extensionMap = {3: "program.dpr", 4: "program.pas", 6: "program.php", 7: "program.py", 9: "program.cs", 12: "program.hs", 13: "program.pl", 19: "program.ml", 20: "[^{}]object\\s+(\\w+).|$1.scala", 28: "program.d", 31: "a.py", 32: "program.go", 34: "program.js", 36: "[^{}]public\\s+(final)?\\s*class\\s+(\\w+).|$2.java", 40: "a.py", 41: "a.py", 43: "program.c", 48: "program.kt", 49: "program.rs", 50: "program.cpp", 51: "program.pas", 52: "program.cpp", 54: "program.cpp", 55: "program.js", 59: "program.cpp", 60: "[^{}]public\\s+(final)?\\s*class\\s+(\\w+).|$2.java", 61: "program.cpp", 65: "program.cs", 67: "program.rb", 70: "a.py", 72: "program.kt", 73: "program.cpp"}
  56.  
  57.   const regenerateInterval = 30 // minutes
  58.   const retryInterval = 1000 // msec
  59.   const retryTimes = 20
  60.  
  61.   let doRegenerateOnSubmit = false
  62.  
  63.   if (!checkRequirements()) return
  64.   if (!initInfo()) return
  65.   tryToInit(true)
  66.   function checkRequirements () {
  67.     if (!logged) {
  68.       console.error(`[${SCRIPT_NAME}] not logged in.`)
  69.       return false
  70.     }
  71.     if (!$) {
  72.       console.error(`[${SCRIPT_NAME}] not found jQuery.`)
  73.       return false
  74.     }
  75.     if (!ace) {
  76.       console.error(`[${SCRIPT_NAME}] not found ace.`)
  77.       return false
  78.     }
  79.     return true
  80.   }
  81.   function initInfo () {
  82.     if (pathname.match(/^\/problemset\//)) {
  83.       type = 'problemset'
  84.       submitURL = origin + '/problemset/submit'
  85.       const match = pathname.match(problemsetPattern)
  86.       contestId = match[1]
  87.       problemId = match[2]
  88.     } else if (pathname.match(/^\/group\//)) {
  89.       type = 'group'
  90.       const match = pathname.match(groupPattern)
  91.       const groupId = match[1]
  92.       contestId = match[2]
  93.       problemId = match[3]
  94.       submitURL = `${origin}/group/${groupId}/contest/${contestId}/submit`
  95.     } else {
  96.       pathname.match(pattern)
  97.       const match = pathname.match(pattern)
  98.       if (!match) return false
  99.       type = match[1]
  100.       submitURL = origin + '/' + type + '/' + match[2] + '/submit'
  101.       problemId = match[3]
  102.     }
  103.     return true
  104.   }
  105.   async function tryToInit (first) {
  106.     for (let i = 0; i < retryTimes; i++) {
  107.       try {
  108.         if (await initAppendForm(first, false)) return
  109.       } catch (e) {
  110.         removeForm()
  111.         console.error(`[${SCRIPT_NAME}] unexpected error has been occured.`)
  112.         throw e
  113.       }
  114.       removeForm()
  115.       await delay(retryInterval)
  116.     }
  117.     console.error(`[${SCRIPT_NAME}] tried some times but failed.`)
  118.   }
  119.   function delay (ms) {
  120.     return new Promise((resolve, reject) => {
  121.       setTimeout(resolve, ms)
  122.     })
  123.   }
  124.   async function initAppendForm (first = true, doNotRegenerateOnSubmit = false) {
  125.     let code = ''
  126.     let srcFile
  127.     const ajaxData = {}
  128.     const raw = await $.ajax(submitURL, {
  129.       method: 'get',
  130.       ...ajaxData
  131.     })
  132.     const $newForm = $(raw).find('form.submit-form')
  133.     if (!$newForm.length) return false
  134.     if (!first) {
  135.       code = getCode() || ''
  136.       srcFile = $form.find('[name=sourceFile]')
  137.       removeForm()
  138.     }
  139.     $form = $newForm
  140.     $('.problem-statement').append($form)
  141.     editor = ace.edit('editor')
  142.     $form.attr('action', submitURL + $form.attr('action'))
  143.     $programType = $form.find('select[name=programTypeId]')
  144.     $toggleEditor = $form.find('#toggleEditorCheckbox')
  145.     $tabSize = $form.find('#tabSizeInput')
  146.     $selectProblem = $form.find('[name=submittedProblemIndex]')
  147.     // codeforces default settings
  148.     editor.setTheme('ace/theme/chrome')
  149.     editor.setShowPrintMargin(false)
  150.     editor.setOptions({
  151.       enableBasicAutocompletion: true
  152.     })
  153.     if (type === 'contest' || type === 'gym' || type === 'group') {
  154.       const existsProblemID = id => $selectProblem.find('option').filter((_, el) => $(el).val() === id).length
  155.       let exists = existsProblemID(problemId)
  156.       if (!exists && problemId === startId) {
  157.         for (const id of defaultProblemIds) {
  158.           if (existsProblemID(id)) {
  159.             problemId = id
  160.             exists = true
  161.             break
  162.           }
  163.         }
  164.       }
  165.       if (!exists) return false
  166.       $selectProblem.val(problemId)
  167.       // ダミーを作る
  168.       // そのままdisabledにするとformに含まれなくなるので
  169.       const $cloneSelectProblem = $($selectProblem.prop('outerHTML'))
  170.       $cloneSelectProblem.prop('disabled', true)
  171.       $cloneSelectProblem.removeAttr('name')
  172.       $cloneSelectProblem.val(problemId)
  173.       $cloneSelectProblem.attr('id', 'submitted_problem_index_fake_display')
  174.       $selectProblem.after($cloneSelectProblem)
  175.       $selectProblem.prop('hidden', true)
  176.     }
  177.     if (type === 'problemset') {
  178.       if (problemId === startId) {
  179.         $form.find('[name=submittedProblemCode]').val(contestId + 'A')
  180.       }
  181.     }
  182.     if (type === 'contest' || type === 'problemset') {
  183.       contestId = (raw.match(/contestId\s*=\s*(\d+)/) || {1: 0})[1]
  184.       participantId = (raw.match(/participantId\s*:\s*(\d+)/) || {1: 0})[1]
  185.     }
  186.     if (raw.match('updateProblemLockInfo')) updateProblemLockInfo()
  187.     if (raw.match('updateSubmitButtonState')) updateSubmitButtonState()
  188.     applyEditorVisibility()
  189.     setAceMode()
  190.     updateFilesAndLimits()
  191.     $toggleEditor.on('change', () => {
  192.       applyEditorVisibility()
  193.       const editorEnabled = !$toggleEditor.is(':checked')
  194.       $.post(
  195.         '/data/customtest',
  196.         {
  197.           communityCode: '',
  198.           action: 'setEditorEnabled',
  199.           editorEnabled: editorEnabled
  200.         },
  201.         function (response) {}
  202.       )
  203.       return false
  204.     })
  205.     $tabSize.on('change', () => {
  206.       const tabSize = $tabSize.val()
  207.       editor.setOptions({ tabSize })
  208.       $.post(
  209.         '/data/customtest',
  210.         { communityCode: '', action: 'setTabSize', tabSize: tabSize },
  211.         function (response) {}
  212.       )
  213.     })
  214.     $programType.on('change', () => {
  215.       setAceMode()
  216.     })
  217.     editor.getSession().on('change', function () {
  218.       $('#sourceCodeTextarea').val(editor.getValue())
  219.     })
  220.     $('#sourceCodeTextarea').on('change', function () {
  221.       editor.setValue($(this).val(), 1)
  222.     })
  223.     $form.on('submit', preSubmit)
  224.     if (!first) {
  225.       if (code) setCode(code)
  226.       if (srcFile) $form.find('[name=sourceFile]').replaceWith(srcFile)
  227.     }
  228.     doRegenerateOnSubmit = false
  229.     if (!doNotRegenerateOnSubmit) {
  230.       delay(1000 * 60 * regenerateInterval).then(() => { doRegenerateOnSubmit = true })
  231.     }
  232.     return true
  233.   }
  234.   function setAceMode () {
  235.     var filePath = extensionMap[$programType.val()]
  236.     const mode = modelist.getModeForPath(filePath).mode
  237.     if (editor) editor.session.setMode(mode)
  238.   }
  239.   function applyEditorVisibility () {
  240.     if ($('#toggleEditorCheckbox').is(':checked')) {
  241.       $('#editor').hide()
  242.       $('#sourceCodeTextarea').show()
  243.       $('.tabSizeDiv').hide()
  244.     } else {
  245.       $('#editor').show()
  246.       editor.setValue(editor.getValue())
  247.       $('#sourceCodeTextarea').hide()
  248.       $('.tabSizeDiv').show()
  249.     }
  250.   }
  251.   function updateFilesAndLimits () {
  252.     var problemFiles = $('#submittedProblemFiles')
  253.     var problemLimits = $('#submittedProblemLimits')
  254.     var problemIndex = $('select[name=submittedProblemIndex]').val()
  255.     var option = $('select[name=submittedProblemIndex] option:selected')
  256.     var timeLimit = option.attr('data-time-limit')
  257.     var memoryLimit = option.attr('data-memory-limit')
  258.     var inputFile = option.attr('data-input-file')
  259.     var outputFile = option.attr('data-output-file')
  260.     if (problemIndex === '') {
  261.       problemFiles.text('')
  262.       problemLimits.text('')
  263.     } else {
  264.       var filesStyle = 'float: left; font-weight: bold'
  265.       if (inputFile === '') {
  266.         if (outputFile === '') {
  267.           filesStyle = 'float: left;'
  268.           problemFiles.text('standard input/output')
  269.         } else {
  270.           problemFiles.text('standard input / ' + outputFile)
  271.         }
  272.       } else {
  273.         if (outputFile === '') {
  274.           problemFiles.text(inputFile + ' / standard output')
  275.         } else {
  276.           problemFiles.text(inputFile + ' / ' + outputFile)
  277.         }
  278.       }
  279.       problemFiles.attr('style', filesStyle)
  280.       problemLimits.text(timeLimit + ' s, ' + memoryLimit + ' MB')
  281.     }
  282.   }
  283.   function removeForm () {
  284.     $('.submit-form').remove()
  285.   }
  286.   function succeedSubmit() {
  287.     if(openNewWindow) {
  288.       window.open(location.href)
  289.     }
  290.   }
  291.   function preSubmit () {
  292.     if (doRegenerateOnSubmit) {
  293.       initAppendForm(false, true).then(() => {
  294.         $form.trigger('submit')
  295.       })
  296.       return false
  297.     }
  298.     const button = $form.find('input.submit')
  299.     const img = $form.find('img.ajax-loading-gif')
  300.     if ($(this).hasAttr('data-submitting')) {
  301.       succeedSubmit()
  302.       return true
  303.     }
  304.     if (button.prop('disabled')) {
  305.       return false
  306.     }
  307.     var result = callback.call(this)
  308.     let alwaysDisable = false
  309.     if (result || alwaysDisable) {
  310.       img.show()
  311.       button.prop('disabled', true)
  312.       setTimeout(function () {
  313.         img.hide()
  314.         button.prop('disabled', false)
  315.       }, alwaysDisable ? 1000 : 10000)
  316.     }
  317.     if(result) succeedSubmit()
  318.     return result
  319.   }
  320.   function callback () {
  321.     var form = $(this)
  322.     var $ftaa = form.find("input[name='ftaa']")
  323.     var $bfaa = form.find("input[name='bfaa']")
  324.     if (window._ftaa && window._bfaa) {
  325.       $ftaa.val(window._ftaa)
  326.       $bfaa.val(window._bfaa)
  327.     }
  328.     if (form.attr('enctype') === 'multipart/form-data') {
  329.       var sourceFiles = form.find('.table-form input[name=sourceFile]')
  330.       if (
  331.         sourceFiles.length === 1 &&
  332.         sourceFiles[0].files &&
  333.         sourceFiles[0].files.length === 0
  334.       ) {
  335.         form.removeAttr('enctype')
  336.       }
  337.     }
  338.     return true
  339.   }
  340.   function getCode () {
  341.     const $el = $('#sourceCodeTextarea')
  342.     return $el.val()
  343.   }
  344.   function setCode (code) {
  345.     const $el = $('#sourceCodeTextarea')
  346.     $el.val(code)
  347.     $el.trigger('change')
  348.   }
  349.   /* eslint-disable */
  350.   // from contest submit page (/contest/**/submit) {{{
  351.   function updateProblemLockInfo () {
  352.     var problemIndex = $('select[name=submittedProblemIndex]').val()
  353.     updateFilesAndLimits()
  354.     if (problemIndex != '') {
  355.       $.post('/data/problemLock',
  356.         {action: 'checkProblemLock', contestId, participantId, problemIndex: problemIndex},
  357.         function (response) {
  358.           if (response['problemLocked'] == 'true') {
  359.             Codeforces.setAjaxFormErrors('form table',
  360.               {error__submittedProblemIndex: 'Problem was locked for submission, it is impossible to resubmit it'})
  361.             $('.submit-form :submit').attr('disabled', 'disabled')
  362.             $('#submittedProblemFiles').text('')
  363.             $('#submittedProblemLimits').text('')
  364.           } else {
  365.             Codeforces.clearAjaxFormErrors('form table')
  366.             $('.submit-form :submit').removeAttr('disabled')
  367.           }
  368.         },
  369.         'json'
  370.       )
  371.     } else {
  372.       Codeforces.clearAjaxFormErrors('form table')
  373.       $('.submit-form :submit').attr('disabled', 'disabled')
  374.     }
  375.   }
  376.   function updateSubmitButtonState () {
  377.     var problemIndex = $('select[name=submittedProblemIndex]').val()
  378.     updateFilesAndLimits()
  379.     if (problemIndex == '') {
  380.       $('.submit-form :submit').attr('disabled', 'disabled')
  381.     } else {
  382.       $('.submit-form :submit').removeAttr('disabled')
  383.     }
  384.   }
  385.   // }}}
  386.   /* eslint-enable */
  387. })()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement