Guest User

Untitled

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