// ==UserScript==
// @name XMOJ
// @version 3.8.0
// @description XMOJ增强脚本
// @author @XMOJ-Script-dev, @langningchen and the community
// @namespace https://github/langningchen
// @match *://*.xmoj.tech/*
// @match *://116.62.212.172/*
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/clike/clike.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.js
// @require https://gitee.com/mirrors_google/diff-match-patch/raw/master/javascript/diff_match_patch_uncompressed.js
// @require https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.2/purify.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.js
// @resource BootstrapCSS https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_cookie
// @grant GM_getResourceText
// @homepage https://www.xmoj-script.uk/
// @supportURL https://github.com/XMOJ-Script-dev/XMOJ-Script/issues
// @connect api.xmoj-bbs.tech
// @connect api.xmoj-bbs.me
// @connect api.xmoj-script.uk
// @connect challenges.cloudflare.com
// @connect cppinsights.io
// @connect cdnjs.cloudflare.com
// @connect 127.0.0.1
// @license GPL
// @run-at document-start
// @icon data:image/png;base64,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Uk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAJcEhZcwAACxMAAAsTAQCanBgAAAPSaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA3LjItYzAwMCA3OS4xYjY1YTc5LCAyMDIyLzA2LzEzLTE3OjQ2OjE0ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZTIyMGE0MzYtMWFhYi01MjRjLTg1ZjQtNDUyYjdkYTE4ZjdhIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjlEQTA5MUE5OTM0NEYxNEM5Q0RFMEVFREY2MzA4QThEIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjk1RkQ1QzI3QzBFN0I2NDdCMTBGMzU5NjU0RUI1NjQ2IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCAyMy41IChXaW5kb3dzKSIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZTIyMGE0MzYtMWFhYi01MjRjLTg1ZjQtNDUyYjdkYTE4ZjdhIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOmUyMjBhNDM2LTFhYWItNTI0Yy04NWY0LTQ1MmI3ZGExOGY3YSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PiotHO0AAHUaSURBVHic1L1lmB1V1v7927uqjrVb3CACcQWSAAGCu8vgEtzdCRoIFggOwWVwGNwJIUKIESXu2t1p72NVtdf7oao7HWxmnv888u5cdXXOOXVOVe21Zcm97qU2bFrPf7UpBb5nqKmuJ5VOU1RUzOOPP84bb7xBt25daduuA337DWDEiN257dZb2LlnL/r368cjj4zjgvNH0b59J6ZNnU5RUT61tbVkMlm69+jB0qVLOfSQg1i5ai1PPf0iN1x7Ed98+y0LFy7mzjvv5N133ue77yYyYMAAjj/xWJ557jkuvvgC9th9OIuXLGHF8uWcfuqp5ObmOr7JDtfaFoWe4/m+6/ueD/jRSNQ88dQz3HP3PezYdQfatGlN506d2KlnTzp17MSpp59JWWkp3br3YOQ+eyOuz+jRd3DU0Udw2mmn8PXXXzNz5s9ccsnFHH30UXz15Xdcc+117L3PXvzjHx9y6KGH8f13E9lnn31ZsmQZw3ffFddNMWz3PRgz5n5OP/VUSstK+fDD97nt1lvQWrFTj53QWjf3r4hgxAcMgqCU+rdlZP+Xpft/qIkIlmURiURwHCfqOE5ufUPDDq7rHe777km+Lz188Td6rjfd871FwKpELL6ivr5+nW3baa21q7XOWpaVsW074ziO+d9+pv9U+/+tgJuEGo1GdSwWKyovLy9atGjRjsuXL9+rvKL80Ouvv77/urXr2FJeTm1tHZ5n2rmed7Tve0eDwrIsEokEOYk4DfX1q6ujkZXxWGxBPCcxN5vJLBSRrVrrRsuyGyORSJ0oHxH5337sf7v9/0rASikSiQSOE8mNRCJtKisr2s6fP3eA72X3G3PP2H22bFqb98fftADV4gAQqrduBQTwuwBdgJFN34gkChobchoXVZRvmrf4119/Ft8sikajWx0nUpmTk1PhRJz/Xwj8/xcCzsnJIRaLlWQymR0mTvy+28qVy/b0fX+fqdN+7Dl12sTwrAiQgxWJogDf+CilUGjEKAyC+p1AdCBubVBIcI5SWFrjZr2ctavW7LJ21bJdpv74wzkArVt1Wr9q1fIZ33zz9bRVK1f9mpubuz43N3ej1na5ZWn+L7b/kwI2YsjLyyM3Ny9h207fr776ZsDixYv3Wbd2w1733HNXm+CsKFrnIlqhlQYENBjfhIK1EBNspVosQILXykcApTRKSzALjR8oMZZGxOD7gtY22HlYysE3HiiPLeVbOmwpX9th4sTvjrasOAMH9lr78cefzs6k3TkRJ/aebdsL/7f67M/a/ykBG2PIzc2jVWmr3T/88MNhq9es6VtZsWWv51/4qXNwRi7YRSilEDEYAGXwlQ9oEAUIzRNVAWIQPDAGDYgKP1TB91U48ST4JqhQ6KLQxkKMD1rwLQE7BiqBMoLvu8ycOavTzJmzOj3xxISjDj/sgCNd1703Nzf3YyDzP9Vn/6z9nxCwMYZ4PE5BYdGgjz/+x0k11VXHTpkyecfg0yjKKUahghnqBbNtWwtnrwn/ig63WQn+KINvZ4PzRKNEI9jgWyhlo5VCfBMIVfmgTPjXx2gPpQRQYJzwZgMFT9sOSpegRPDcFB9/8tGgnJz8Cd999+1nIG/n5OR8+H9hj/5fFbCIEI1GadeuXdvXXnvtItfNHD1z5szeAFrnYNsRPCOoJt1IBKXV9sqNAOESjDQJWQIjXRQiHnjJ7U5v/r9x8MnFJgFojKUAPzzLD1cHEwwuiTR/TykruER4XctKYFtxGhuTBTN+nv63nNyiA7/4/NPj4vH4Szk5Od/8bwr6f0XAIoLjOLRv157nJ7w06ttvvxj1y9xfd0OyaJ2H1g4K8DwfsTQYg9IaE3ZUsESHhr/SiGigyXQVLG3jZ1MIjQB06NSJ9u06UNqqhIK8PLxsmi0b11NZsYU1K9fT4FUDNpoEIjaiNBibQPv2QWkUgphg4IjSaKWChcME64nnGSwrDipGKukXL1q05JSCguIRH3747pf9+g15LCcnZ15VVeP/eF//rwg4Fo2xZs2aAS+/9MrtixYu3SfrpvLRuSgrByMt9lACQQoEs1IErQPN14Svg6XYBB4gJShsvHQloDnqqOM55pij6dunLwVFucTjcSIRB5OtJb11PY31W1m3djVfT/2FN9/5mLUbqkBFsXQcwUFEo7WF8bMIabSyg5VeDCIK07zXK4J5rhBRoBSWU0BtbapjbW3tqPr65F5Lly169+xzTh8Tj8cbzP/gjP4fFbBSitzcXOrqam966cUXz6+pqe+EygE7HyBcetW2ZVQFSlP4YfNfw7ZZjDFobYF4KAOeW02P7jtx+513sf9+IyktLdn+JiQTKGN5BqJt2Wm3vuy57+6c+rfDGf/E60x45QN818d28tGWxvc8tDIoS1DK4HkGsJCmewtdiyIt7huFL4KOOIiJUFFR272iYsu14x56+ID9DjxkbGlJyTv/U8v2/5iAlVK4rrvz9dde9UR1dXI4WsfQeaBsEAMq0GC33yV/30w4i8X3ET8DZPB9hbJiiJ9i0OAhPPPscwweNAAF1NRU8967/2DFquWMGDGCgw7Yj5Urt3DXnTdTVVnFkUceyLEH70bfPt0ZN+4eunTpxh1jxmFMPVnX23Y/PoBCOzEgEChabxt4f+AmVmiMGKxoAiRmb9hUM/jvr732XH5+wfF/O+mkG7TWK/8zvfvn7X9MwCIy6q2/v3Z3Nmu11jqCUhZKGYRQuNvO/M031fafeRkMhoKiNvTsuTMlpYWk043MmT2baLSYBx9+iCGDBgDw1tuvc9edY1i5chW+MdQ01nPQgQewtaaRiZPnsnrVCr6e+BMTHm3LPbdfzj4HHsJ1115JfX0jz774MgMH7ULHjh2IJSI01MOSJUuYP38+mXQDWIlghWlSAM3v79k3BqXB+D4IOHYM1zcFlZU1x112yYUjzzrn/DHxeOxJpVT6P93fTe2/VcBKKSKRSPTccy4cv2b1qtNFR2IC+EbAzzYvb7/5VovvB4dtaVwvjSUuA3cdyNlnnsVBBx1CYWExidwYru+yfu0aMpk0O+20MyLC+PFPc+utt1BfnwUFljbErCgAWmmaPE8pN860X6s5YdSdPPZAI8cfvQ83XnQYZxy/Ox06dySeZ4OtEeOQrNrM/PnLePa5z/jg09k0uBYqmkbiSVRjPDTVdIsh6iCmaQT4uCaLUhZYuaq+3i158vFxY4cOH36U7/vXOo49Synl/adl8N/mX9Na49h2uwfH3v/RyhUrzjOiY8Y3gUaqQFsW/LN9SMCyLIybJhHV3HDTDUyaNJELLryALjt0Ip5wcLMZtED37jvRr+8AHMdma3UNz014nvr6emwnCtiBqdPih5t2TGVZWJEIlfXVvPb3N9m6dTMFJYV07zWYrJdgwS9r+HnyQpYsWo8VzWP3/fbkldfG88z40XRqF0MySbQXD6w1JYgyoS0toNwWh0+gNAIolB3FJ9eePOnHPQ879NApy5cvv9f3/Tb8h2Xy3zKDg5imGvTpJx++VN+Y7ascG0v5+G6gEImEC7FlgfnzyJzSCt9NE3Xgjrvv5erLLwGguqqaGTNm8NnnX7B61RqKSovZY4/h7L/vfnTq1JHS4iLGPXw/Z5xxOps3VYH+i8c0gu9uZfiwgdxz//W06tiFqhqfF197mzfffJ8Vy5ZQk0yRE0kwdHgfzjnjKI44Yk9OvuAAosUul15+P5s2Z1GOQlSLPbtpzVYmHMgKlBN+HJwTsSIYXcC6dZusE084+ZqxY8eeOGrU2bfEYtG3+E95wzZsWv9fPjZuXs+69WuZP38h03+eyeo167nu+psirVu3OzgWi6/VVlwsJyE5ecVyxVXXymFHHCtgibLyxXKKRdmFwl8cyi4QQK648hppar/8Mk8OO/xwAURrq6VmJgMGDpFvvvlOjDEiInLv2LFiWbag88W2InLlVdeKiMjMmbOkW7duwfd0kQDyzDMPi0iFbF7/s5x03CHhbyYEHCkpbSNdu/aVRCJfALnmimOlpvJTEflVHr7rRsl1Wgm6QHCKg8MuFuwiwSkQnFzBiQtOTvCeVSxYxWI5ZWI5JaJ0nkSjxQI5ArbcNvouqa9vuE5EHGMMnu/i+RlcP41nMv/2YV19zVX/5cGhFIgRUqkMkUiE2traIZ988vG5M37+aazn261FFOKnOPyII3hhwjP069eHl196ATfrYik7cB3/2W9rjXgpunbryksvvkBOToLFS5bxt7+dxOQff2TEXntzzTXXsfvuI2hszLBx4zo2b9rAlKlT2W/f/WjVqowuO3bizTffpr62Hq2F3YYO5cAD92fz5k289967VFVVoewcMFBb00BVRRVj7xvPx59/D1YJSjn0HzSQceMe5q67bqd37wEsWLSSjz/5hnjUZujQgQwZMIApEyeyYt1qIIpWUbSyCJwkOnB2iwIslIDWTRpZ4KHTWlBKUGJhRPHDD1+Scc0+w4cPy0SjkUUiJtU04/8riI7/5xm8dt0aVixfyfQZs4YefMhhawBROiGWE5e2bbvIVVdfI19/861k3YxUVFbIU089K+eMukCUyhNtF/3x7LUKREdLBJB77rlbREQymayceda5AkjHTl3kl3kLmmf1ihVr5OCDjxClLAHk1ttul2w2IyIie4/cR8AS247IVeEMnjVrtvTo3j3YNp1i0XZ7gWjzSqB0VHBKJK+wtXw36UdxvZRMmjJRkqmkfPPNDMnLK5PWrQrl24+eFJF18uz9l0pxfjz8vi2OXSa21U6U1VawWgt2qeCUioqUiB0rEdspFKVjAhG59LIb5OGHn5CS4o4CMdF2rgBy1dU3SG1d/Ssi0sX9f5jB/08buogQTyRIZzJtR48e/dDnn33SSVv5aDuB76bp0KE9t916G/vtOxKlfQqL8rnggnM5++yzEMkG5sOf2L3Gc4lEcthrxO4ALFm8lLfeehtQ+L5h2fLlVNfVkspk2HHHTlxz7TW0adMOgMW/LiGVCraweDz+Tx4CkAyOZfPQvXdw6YVnE41YIIaCwhJG7LEbs2ZO46wzz2DKlKn06duDbj36saW8hh+nLQevkaNP2JsHxlzDxWeeQP9eXXG9CoypQykf8FFaBTNXAgybZ1zEpOm0Q3tuufUarrzyInYbOhitDcbzQcV5+KH7uPPOe09Lp7Mv2trp+c/8A3/WtG1b/PuHTSIRp6SkhEwmHbv9jjvu/uKzT4ZrK4HSVhBtsQpYumQZZ59zFm+98wa2dqipqeTaa6/iumuvRdG0XLXwVkHwWlvgu5SVtaddu0Bos+fMIZWsx7ZzKS+v4vrrrmPUqFH84+N/ANC5c2datSoLf6IlcO2vlzWFwvjV5Ocp/va3Izn55GOJRDQINNZWMXv6DAb0340H7xvH7sOGsXzVXNavWwTATzOWsHldHaWdd+bsi0/n8Rfu4NUJY7l41GlYdhbjVaMcFyGJ4IPRiJ+hoCCHG26+maeeeJx4IoJvPG66+Xqefu4Z2nfqiNIKVJRx4x7g/gce3luECbaOdPuvCNneuLHi3/6SGMOq1aupqa1hzuzZZ73z9ptnaTsXVBTfBFEYrTW1dQ28/957bN5czl57HcC8eQt58MFxADhOIb6ETvuwGfHBSwNZwMdIlkg0B4CKykrA4HmBT2Dl8qWsXL6UnXfemZOOOyEM6zXtVS0FbML3txd0U1cF8WEHy4pQ21BFMlkDgBZNbW2Kq666ntvuvoP9DjyMydOmMua+0VSUb0LrHL765mtOPrOKXj1a02Pn1hx56In0HbY/D/boRvfupdw4ehwpz8NyHMR3UdrC97MkYnFOPOEkBvTrg2c8jBh2Hz6U4cOH8tTTz7Fh7UqcaD5upo77xtxDaVnx8IsuOO8+rewLjEjlv+PltCsrqv71s8PmeT6zZ8/jl3m/DPvy049GKytXoXIxxg+VWh8jBjsaQ0yE2bN/5cS/nUlVZTXaKUAr3Yyq0KIRJBCc1AMQiSWIRiPYEUUyFQi0qKgA23aIRhNBBMkY9hwxgpNOOBGAmupq0ukUAMbfdq9K/XVvKG0hWFRsrSHjetQ11pJOp7FUDqJzmDxtBmecfhqdO+3ImjVr2bRxM0oXYEwdZWXtSOS3YcqsDTwx4UNefu1nbjj3Io4/f28uv/hYapIpbr/jOZSOYSkfxEPbOVRV1nDeuecwfNge3HLLTZSWljD2gbF8+92PLF+2DKVjGAFt55FKN3DLLbdQ1qr1Uccfc+RsrRjjif+Xz9Sy2ZZl/euSDZvWmlgsUvbztB+f8STS2rYS+E37qShAB8pjOHPS2SyTvvsK0NiR3MAZYALnPYDn1gDCYUccy3HHH8+OO3TEsS201pSV5eOZLIcdcRDfdf8a246EAoZOnTrSoUOwhH/11XesWbPl334WcTOAx8knn0jnzjuRk1tEz547M2/uBiwrhorlsmnDJjZt2Ag4KDsP8WrZZZfdue++Meyx53AqKyt55PGHeGDMQ1xyy/Uk3VGcccmpXHjOWUyZtJavv/+SSCwXzxMs28Y3PjN+/pnZsxZyxplnUFRcwmuvvsGC+fPQTg5aRxBj0JaFUnlUb63guuuuszp16nT1bkMG/oRS3/1TJ1HYbP2H7sI/b9FohHQ6o5955tlHK8rr+sYSRRgTCNLzfJqi8yIaY4IgWhDKS4ShPhM4OpQC4+F5DRQVFnPrHXdw8kkn0LpV6R9et3VpW1rv2fYPP/vHPz7nySeeIp0OZnvTwPnLFq7YYuo5/bRzeeDBeygsilJYVMqTTz7JMUedQ3lVfbDKxAsxLkHo0G0kEolz0UUXM3Lknjz9zNMMHjKIO267nSmT5jB18jfccs+z9BnUm8HD9+Hi807iux8+w82m0VZBCAtS2JESvGyac0ZdTCInztJla9BOboAysXTQp2Go1IoUsXrFUm699Zbil154/u52bducJErW/ivysjds+NczG5RSTJ36E/Pmzh+1fu26E5RTiJvNoi0LERXMWiGAwYiAIgS6KZQKQmiWVogJcE/GT5HIiXHr7bdxyUXn4dg2dfV1/DR9OuvWriWbzQbKWPjASulmW9CyNMlkihkz5vLF519TUVGBbcfxvBQtlZE/tx3D961cTjz5BFq1KuWgQ/Zl77324NprbmG//fbh9b+/jmUl8A2Il8VYEUQM0Wg+nTq2I5vN8tZbb6O1sMvgXSkoLgGVYENFDY89+hIv7TqUgYN2YGC/nsz85VfQGiN+M1hBO1HmzJoDeCgdDZxdOvAtGDFIiGZRykLZeXz39ReMe+SxoXfdOfr4WDTyCGGM6y8FnM1m/y0Bb926tduPP343xraLLM8IPmB8L1BmxA+WXssOFIpgowWaYr3g+WHnGx/E5YgjjuPiC8/HsW2mTpvGtddey8IFC8hms4E2rgNMs+Bvp0QqpTDGkE43efQieF6AmPBlG27LGMPvNXW2+cF9l/rGBgD23HMvBvYfhGU5pNMNgIvnNqC1Ib+4lNrqAEfd2FjB5198xR4jdufdd94hkYgzb8Fi5s+fB9pBTIzpPy1iy6pllLVrw+DBfZn5y6/4LRS+oL8AKwI4odNHB33WtMeqEDxkfMDGNxEee/Rh1X9A3xtP/dtJXwPz/pnM/k1ftFKfffrxI7adX2Ik9MiEShV+lrYd2hKP5bJh/QY83/+jbm36HTAupaUlHH300UQiEVauXMmNN9zI1ClTiEZjnH766Shl8/rrb9HYmKa4uIhWrVsFipxs05ibzSAVrBgNdbV0bN8OS1vhHf8zHSPLI+Meom/vnbjmquvRWvPmG2/y5dcfAx7de/TgzrtuZ8DAAXz9xVfcf/+DrF+3kscfH09NbRWnnvo31qxezSMPP8mGtavAyQXjUFWfYtXKpQzt3pq2bZu2HcNv4opsF28UHfiugydjm/YfHJadRyZTwe23314ycED/c3r37HkT8Jc4IHvypIn/pAOCyziOzTffTDy/rq5xv4iTH0BmfBdsEK+eotISJjz3FD179ubII49i/txZYOVBiGP6fWjQpX279gwbOhyAzz/7mkmTfgDgphtu4fobryUScci6WT766B989dXn9OvbCy/EPQcdITSD15sFHrr+tIXn+zTU/zUOSjmF/DR1MkcdcQwHHnQ4VeUVvP/h26QzjeTk5vDo+AfZd5+RVNVu5YLzR5HIz+f8c84jnU7z/ITnefGFCUFH+hF0ROPjgyRAgee7YPkoK5xHltcCmfJHW4cOZdr0bE3PZUFobVh2ISuWLuHhcQ9dOu6hh97Nzyv48a+eT2ddj392uJ5HQ2OybMOGtTdaVjQqYtDKUFKSh6WygJCTk6CkrJiSkmJycwPvkTIuxUW5xKM2LeeyCkdmTm4xrVoFkJolvy5p/rxtm3bYtoUxhkgsQk5OnB27dgxjwwqtg5iuDmGvwaGDQ1to7eBlPd57/13mzv0FiP9laFI7OSxbsYLHH7uff3zyEYMGDmD4sF049NB92f+AkUyaMondhw9n1txZ7L7brnTq0g5wEfEZMGAgxx19HPkFeRgvG05GTTySQ5cO7Uk11LFh46ZQVi22TC2gTYsJKiF6s+W6F1gkTX9FwkidjvHi8y+rHyZNuhkobIKn/dFhL1uz7k8fXADbsokn4kz+/rs701naOxZ4fgNHHXUcN918NeMfe4TPPv2Mvn16UlZShMLQv99AVixbRt/efbjz7nv49IuveeiB+3F9wVIKY1x8fDLZGjw/RZQYXrO+oBk77lEyxkfwee+DT9m6eS2nnX4WewwbRiaTRiwLbUURLCzlYmmzDVljhEgkwqpVq3j11VdpbMyAjgWK3h9ZDCKgIjiOhZvJctbpRzPm3tFE8PHEINkMu/TrxcP3jmWXAf347OvvqKzcRBPi8rLzDuf0sw6j9+CfqVhQDcZBSTWD+g+kQ6fu1FXUkKcVhYV51NSk0Lo9OuLh6/JgH3ZLQGyUrgWTDu7H2Gg7HsKZgoCZWKB9FzebDISNx5h77j9wl8FD92vdpuw9+RMkn925U6c/FbBlWZSXl1OztWowmBPQliUi2Db07bszgwcN5LHHxnPC8ccyYu8R5CUKAXjqyfEcd8zR9Oy5E+3at6N8azWRSIRsYxqjwPfSdOnchUMOPgQVxmp79twZrW1E4qxYuoxLLz63+T6cSJxpUyYzbfKP+L5PbW3tn97zdk0l0E4i8O/+SRMdAuJDt/ykyVO5/75HsW1FfUMDJcVlHHXsiew58kCmzlzAw+Meor4mBRQDlfho3EwG8RyQOGQV+flRLr/8DCBFQnvcfce1DBy+G2MfeI75CzahXBsdEXzTNGMDk7G0tJBhw/ZgydKVLFm8GDuSF4AIEJTJ4nu19O87gNZtO/Dzz9P5adpk3n733XsuvPDcL7Wl6/9I4bFPOu6YP334vNw8Xn71Neebzz+5FSe3uAm9mM0Kzz73FL377sTxxx7PoYccAcCKFctwPZ9uXbux7377ADBz5hxG33oL9Q31WFYU321kjz324qH7x7LrsF2br5Wbm49lxXDdwAPmuwoxKfoMGMDxxx7LHnvsTn5eDqlkitmz5/DKq68xe9aMYJ9XOTQpL0qFu3Jooolv+CtrIkiCUGTdBiDGvPmLmDd/0XbnPPv8++zQfQfmzp9P1ZY1aLsU0wyuiZDJRBGTD9SACK7nkfHqwcpgYhrl2Jxy+un06t6Vs869lbkL56NcOxgQYoHyED/LhRddyB23j2b6jNmcd94lzPvlJ+xIAYLBuHUMHzaUZ5+dQO8+vTnj7PN47eXneeSRh3scdfTh3SKOM8eY30vY/kM4YNi0pamu2nqgZTn7GMBk0xgCZ8KmjRuZNWsWxx97PMlUiucmPMubb/yddDrL4UcexZWXX05RUQGTJk1i4YJ5oBzEZOnZpw8vvDiB7t26hgPgF5YsXcrzz7+A66axnFx8z0VMI4ccehQPPTiWnXfusd197bnnnhx66KHcfMvNvP3WW0Aq/CSBsiP4XgaoDx+iiL96RoyPeA0M2WU4hx6yH9VVW/nwo/dYu2YjkXghIhar16xg9ZoF4RcsjFcNBBLOpuuJWk1QHBeopWO7Nrh+IxSWsGj6Ip599hUuP/9kBg7blTF3XM4Z511DZVUt2kpgsIPvKch6LgC77TKIp58ez5lnnMXSJcF1dxk6lOcmTKBXr95k3cBXr9CsWrGcv7/x1okDBvSf80ePZ3/66We/e7NFSom9YN68Ub5v8pVW5OblcsThJ1NUVIhlG84/73wAPv/iS6679gaymUD4v/wyh9ZlrbjggnM5+uijeOiR8WxcvxY7anPtddfTvVtXslmXZ56ewGOPP86yZdtmjO/WANCtWy8eeeQRunfrDMBXX33FunUbaNu2HXvvPYJu3bry2KOP4WV9fpk3G8eOsWLFajw/SyTqsNeI/amtrefnGfN+B9mR8B+AeHWM2HtvnnvmWXr0CAbd/gfvz9lnnU1lTTLYBzXkRmMM6N8b3w/6x/czKOPTrUsrHMcHlQRq2WVQTx599HaG7TGYLRtWc9MtT/D5N9NprK5i/JO3cPDhe3P8Ufvz9AvvgSiUGER7KB3h8fGP0a5dRy696HyG7TaEx54Yx/nnjiI3N5fnnn2GXr1643kuY+67j3ffeRsfhbJsnnvu+VP+/vprt6fT6fTvxvKtt97+u+Puu+/l5FPOpH27LnvadnwLVp6ALf0HDpPftmzWlcuuuEoAcSJ5Eo0FMJtjjjlOamrqRESkT/9BAkivPgOkMZUS3xj5+KOPJBHPEUC69egpd937gFx48SWy54gRsscee8rb73wgIiLGGLnnnvulVatWApYUF5XIVVdfK42NKRER2bhxk0yeMlWmT58hV119tdiOlssuv0w2bymXJUuWyV4j9xVUVGw7Ktdcc/3vITs48uFHH4mIyHnnj5Lxjz0iIiLnnHeeoLRgxaSsdYk888h1UrFpvtRuWinVG5dJ+ZpZUrl2qoisk8/fe0TKyooFkPPOPlFEMrJ65XQ55tgDw2vkS34kR/7+8JUisl7eff0+KcovFCgRbbUSrDxRdr5AVHLy8uWxJ59p7t/vJ34tU6ZNEhER34jcfc+9Eok4Alp0NF90LE8AeezJpw+aM2ces2b/st1hn3POmb+bwfl5eXzwwSeln33y8dWe57XSkQRGGTZs2MiTTz5Hq7Iysl6Kww87lEQihx137AaAm62nCRTYq1dPYrEonuc1z+yu3bqSiMVobGjgHx9+SDLVSFlZR+64/W5O/tsxpN0sVRVViAjt2wd+52VLl3PvvXfR0FAPxKmq3soTTzzBiL1GcuThB9G2bRvatg1Shi1H8fQzT3HwwQfRulUZrVuVscsuQ/jhu+8B549WMEA1O0O0snCsIMksFokFGqzv06d3N8676ESmTFvC1B/mkp+fA5aLchRr123grTfep6KiCqUdvv7se8497VR+Xb2eKZN/ArsQLXnUZTczadoCTrqiiq47tadV62Kq68pBO2AM4ts4kXwa62u58Yab2Fq5hauuuIy999oPgOqaKh4Z9xj3P/AA2azGiuVjfAOWgHJ4/oUXLz72yKO/8Pztkbd2Nvt78J6RHJavWHFxY6r+EKWCB9ZWjMrKrVx99VUk4lF88Vm1+ipuvvFWjj/uWGbNmMXrr7+GMT5HHnU0Z511FtFohDfeeIONGwJTLBoLsgIy2SxbtgSRn6zr4cSCJTTmRIglwPhCefl6wOKR8U/Q0JjCipXgo8HEyaST3Hf/gyQb6olFA/+w5xvefe8dMqk0Tz3xLK3L2lG1dSuffPQpqBh/rmhleeqpx+nfry+PPPI40ajNzJ9/4fNPvkARR/DJzS1AdJTXXn2Xpye8iaUclPZAKzzXAHGcgh1xa9eyanM5E157B1DgFIJto2iElFBZa+Gn68gptInGbQLgZACzVeRgjCISK6GhbhOvvfYG559/Dnl5BYGAKyt44/U3SKcasZ12aKVRlovBAzvG0l8X7jF58o8dq2tq1rXUOewvv/p+u8cVI7Ru07r9nDkzDnCzWUfZCUSCqJBl26TTKdLpNEpv67B2bVsx/tGHOO20k/F9j0GDBtEqjAo5jtPsNqypCmLPubk5DBjYl48//ZTammpuv+0ufpoyi42b1rNg/pwwYGGhFCxfvgylI0EidphkZqwoP037maWLFmCFgQ4RQ1V1DagYH3/yBQsXzSOTNqzfUAGWDeJul53YFIRQdgFffTmRs88+izffeIytFQ2cM+oCVq7eiLZtxBMso8FXiATPIcTxJQ1uloGDduOKa26hZ+/ufPvFlzxwz+3U1DUikQSioyAWYjzAJ2LbaKcNmWQNgT7lgMlBqUaw6hHjkE3X03XH7kx49lnKSlpv6+P2nbj/obFccunlbFy3AYviMDMg8HKlUqncl1577fizTj/5Ydd1twm4bduy7QScm0jw4+SfBkyZMnNniDVn9jWna1pxLOVx9jlncclFl+L7PlU1VZSVlLH//s0cJmzYuIHS0jIOO+ww7r1vLNdecy3zf5nNsuUr6N6tK2edczY//TyHr7/6kkULZrJowcw/mWHhwPODHF9DFDuah+f5VFUFSpBWCiNZlGWHeGvFihUbAQ06DpIFtX1ecfMYVw7GizJt6jS2Vi5h+dLNLFmyGMvJQ6w0eBkaGhrA83HDrcZx4mTdBkpblzL2vrvYf//9qampYcCVF5GfA5defhWCF/qfNL7rE3cchgzpjLLbsGbVFLZWVhJkPsRBZ1F2A8ZL0b17D9544y2GDBmA57o89uTjeL7LlVdcw9FHHoUo4fxRo6isqMBySkPnmEaUY8+ZNfPkO2678eFkcls+tF1SXLRdRxYVFVJRsaVffX1FMVbBtg5pGvFhgnUskUNBQSHPTZjAHbffziGHHsro0aNp364dYx94gPGPPMKJJ57Mgw+OpaS4FUpZbCmvYNzDj/Hkk4+wQ5euvPLKy3z3ww8sXbIERLAdazu4TXC9wAerlUU6neb99z9i4fy5KDsXbActCuOlQdxghokBkwGiONEYnhH+GgAR+HrjiRiWrdFaEYlGcFMG4/tEnCgHHXwoKt6JXYfuxZvvf08q1YiIoWev3uy62668/fb7XHvdlXz15eccfezx3D1mHJvKq7C1T4B6rWPXgTtz4sl7AxuZMu17KiqrgUKCIIODcaFbt5145dXXGDJkACLw/Isvc8MNN2E7DkVFrTjzjNM56rAjcZ80XHrxlVRU1GA58TCXWqivrd7xnffe2y3ZmJze/Hg333zrtuOmW7l3zNiCvffa600gAGo3gdCdItFOkWAViLKLpKCwjQwesqsUFZcKIJ07d5FpP/0stXX1sseeIwKt2smVXXcdIR077CTRaLFYVp7k5hbK+Me3aYlGjGSz2eBwXcm62RaHu+3IupJOZ+SJZ54TQOxYQQAmR0nPPgNl3GNPyrQZs+WnmXPkjnvulU47dBOwxIoWCipvO+D79rDZEoFSKS4plKVL3pfP/vGk5OUVSCTWSpxoVG6+7TZJp9MikpF0Oi2PPPy0oAPNdefe/WXx0mWyYeMWeeftV6SxsVZmzZkvBQXtxLaLJBYNLIqeO3WVSd+8ICKLZPas16V37y4CiGUXi9JtBFUqYMtDDwcavOf58syzEyQej4tSEQFbSkvbyatvvBlo076Rc867TJTKFVSBWJGiQBO3Iu5+++4/buLESXz66ed8+unn2JMnTd1uPGutuy5fsaZfQEv0+7HelBFW35Bk1sw5zVEiI4LreqTTafxwxvhG8fPPswGFbUdRyqGxMcv1117NL3PmMOq8s2jTtjU5iVwEaY7dKhXgvpQSlHFRyqK4uIRoNEY8FiSQGeOivBQHHHwwTz/1FJ07dWxeZXYZ2I/jjz2G0087jZkzZoHO+aspDGH2vjF+GKcVstkUhUW5XHjRhaxds4aLLrqQ666/kWNOOJKXX3+NObMms2TRfB586GHuG3Mnxx53MuvWr+WWW0dTV1eHpTTaTnPiEXtzw21XMmDwQLaWb+aBsS+yaNEatE7giwLlBXlLvmLq1Gnsu+9IfvxxCldecSWe52JZOWjLprKygssuu5RkqpEhQ4aycOGvwbZpaXzPDxIFfGWvWLliv5ycHJ3NZg2AfXwIWoOAj+qrr77o9v0PX++smuAlTV0gLf9vgniQHQ1fW2zYWMmdd91LTk6C+QsWo6wcBIWyAoS/b4J4rWVHyLoeLzz/DK+8/DJt23ckJycR5PtKwMpoawtfBOO7QJrcnFzeeucdduq+U4ilBpNN0q3HTowefTtdOneitraWX+bOw/iGIUMG03OnHtx33xiOOuoYGhpMGK0Jm6IZiNAkYKXVNuVLASqgjGiorycnnkPXHr1p1boNmWyarJcCFNrKYcIzTzGgz46cdeqxnHLSCUyeOgd0gp12bMO4Mdcxcp/++LkRFv+6jNtuHs87H3yLY5ciOAhZBBfwUXaU9957n08//4p0YyNYEZQVxaBCcyif6sp6Lr7oUkQi+K6P5UTwRQKMGgKiqa2tbfPRPz4+0LKsz0UEO5VKNT+m1lql0+nugFLKCnJ3/7RpmrvMsjBG+OarL4NPrBhKO81aaxC2VTjawogXfM+O4XmGdWuWbverthPBcz2CFcQHXGyt8bKBYHXzHq0YOnR3hu22C5lMmptuvoUnn3gcgNvvuJvrr7+WESNGMGjQICb98GOgbf5hC/bgINToBP/HQilFY30jY8fcywMPPMTTT46nrq6Ohx59lIVzZwHRZvimm65DUg3UVlWBFUWZRk4/7Rj2P2ZvZMtqJn23gPMvHcOyleU4uhjIwTd+ANnVQexXxA4C+mkfZcVRSiOqaWIpMIIViWP8AK3iRCN4v00cUBY1NXVF69atP/iRRx7+vK6uDvvrr79pfs54IlGwatWqPs3URP+0bVOIlDJBng8BJgulELxtM198/HAZxDSQk5tD+/YdyC8swXEiNDY20K5dO3YZsgszZszghx8mkmpM0q5tWzp36kJuTk4o1qZrRmnXoQsAK1as4qtwcAG88/a7nH/+ebRpXUabtttMjT9/BotUuhHj+bgZD9fzUTqOj8OLL77M2tWVDN9jKAsWzeGD995t/qZvAq06onzi0SiO7YCforR1Cf17t4Oa1aiookeHLpxyyjG8+NqHrFlVia1iYYjXBTyQKMpEwQo4RtAGrVQoQAK0ihaMaBSB+ej53jYwRVOzNMbDWrly9ZBUKqkbGhqMXVAY8GNYlkVNTU2HjRvX94VoE4S8ib2oxdIFSgRp+sxIOJOtkIREMKJQdggoQgJDXgHigkkxePBgzjjnbI4+8gjat2sbdhbYoeyqa2oZPnx3KirKeeGFF9lzjz2J5yQAQkc7QJbNG9YjEmQ1HLDfQZRv2YztOJx88kkU5OcCEOC+m2A9fxB0EAEaKC4uJhItJq8gQyxmk6xuREU0ViSXb7//gm+//xiAffYdTo8ePUjVuGB8fLea3YfvSn1DmsbGNLm5ccY8cBcHHjICU7eBeYuW0q33Hoy+8172OXRXbrj8fqZNX4Kyi8J4L0EgRnS49QlKdICLVgoRvwVwJwQeEhDABKCQplVWoUVjUGzasqZ4zpwFHWrratcx9aefmfrTz8xfuIi77h6zL5DUdrEQKRZlF4utWonSZaKc1qKjrQTyBZzAx2pFxI4WiOUUirKKRdklouwwLTRSKMouEmUXinaKRNtB6uXBBx8mq1at3s6fbYwnnuc2v04mk9KzVx/Zc8SI7c7bvKlcjj7qBAmBYNK7Vx+ZPXueiIjU1dXJhBeekzf+/rpk3ayIiEyZMk2KS1oHWrQdl6uvubFZi+4eatEQkY4dO8qbb77RfJ177rlH4ok80Va+KF0odqRUICo7dussP0x8T0RqRVIrxU2uFDHLpK5hsdxx+/USj8WlR/dOsnnTYinfvFgeGnuDtC3Nk1FnnyqrlkwUkZWyZPbbMqzfjoEGr3cQnK5CtESIOoJTJFiBxYJVKMrKE9vJFytSIBAJ71eJRYlE7fZiOa2EaIEQzResYrFpJZCQ9u07bLx19D0HXXXtTba9ZvVqAHJyc9iyZXN7IK60CpYONIJBK/BNBnFrSCRK6NypC1kvxarVq/EytTiRIlCEG3443IwhcHMaLEvhphvo3ac/zz77FB06dMD3Xb788mumTZtOQ0MjrueSn19At67dmDJlGmtWr2fL5nKef/F5enTfiTmz5vLzzF/48MNPsKwcfD/DwkULmPjDRAYO7EteXh7nnDWqeWLOnTefK6+6hqqt1WDlQOh4CCataaE0elx91VWceOLfeOXll+nUuTM33nQT036awScff4Zl54ehQIs1q9Yx5p5H+fTDL8lk0rhGiMajrF2/ks8/+5mMK2zaVMW5oy4nGsvlvfffR0SY8MJrrF+7kifH3UCPgXty201XcNr5t1JZWw92QeCt1KrZSpEQxREky3v42Qbi8Xw6dd4RI8LqpZvIeCksKxZAfwjHvBIUFvX1ydxJk37o4rqusqdMmwZANBqzly1b1iNYVAXCpcEojcIDv5Y999ydc889n149+5Jxs0yZOonHxz/O2rVrsJzSwMnQtJ5AEGtV4PuC5ViMHj2aDh064Hk+940dy7iHx1FV9eepM8kkXHzRxZSVtWb9uiacdy7ajuH7KYqLS+nYoSMAa9etZcGCBaSSaVauWsNrr77JvHm/oK3cwF9LSyBbyxahV+8BADz7zDPsOmwYe++9N3379ueTjz8KHkaE4sI8cvOK+fLrSXz59aQ/+J18nEgujSmPjz8NlU2dSyQSI+v5fPHNVO6462kee7IL+x9xOEd8/A0vvvE54qtAgzfRZnqHpia+h/EbGDRwVy697FJ69d4Zg2LmlBncN/ZuNmzeDFY0VCANKBelNJmMl1i2bFmb+vp6zT77Hcg++x3IAQcfWtK7T9+3QIl2ikTZiSBD3SoRlC09e+4gC+f/8Ltw4bvvvC+tWrUVpaOiwux17ELBKRR0kWi7UFAR2W3oCEmm0uL7vrz9zrviRBwBJZFIWWCwg+TnF8vIkfvJCSf+TU455VQ54cQTxY7GgiGqE4KdHyz/TpCVv8uuu8n6jZtFROTBhx6W1q1bS15egQQhFkfisbZi2UE4zrYdufbaIFw4a7twIXLJJVeKiMjSpUulvKJcqmpqZZfdRggqIUoXSSSSK5dccIF8+eUX0qtXdwEtSrUSVAdRVhvBLhYVKRM72lqsaJmoSJnoSJlYkTai7Daio50E8iURj8nLT90lIlXy0fuPSVlpgaASoq0Ssa1t2f8BE0CegJJBg4bIggWLftfvb7/6opSVFAraEiL5glMglg62FK2j0rvfoGdHnXeBY1dWBtmFtmXl19fXdwogmttsRtEKfI/zzvob3ToW4mVWQ6Q9WdeQiDgce9zRzJo9l3vvvSOI2ljhN004MhUgWfbYczjxWJTaunomPPc8btYlGs0nm61FxOO0007lrLNH0b1bN/Ly8tFa4XkePfoM5O5bb0A5kVCtU0iYYdh/l11o17Y1nuczffrMMELloFUutm2RSieBBsDHAxqTTSiPMF8XUFYuL7/0IhHH4bjjj2H9hs088eTTzPh5OspKIL7gWMKQQd0Zud9Q2ndoy6JFy1COjcZCcJFsA0I23AQi6GgcY1RgAtkarbLoaB7J1AY+/ep7jj/5UIbu1p8dOrehonI5qDjGV6ADRKUSEJMlNy+Xc849j969ewa0jsqgVZpM/VaOP24/3n5nF97/+LvgWoDRgeprvCz5ufEOBx98UFu7vq4uXE50biqVbhd0kAZDEJ4Lk9O6dm6FSVbgexms4rZoW+N7KSw7hxEj9uCBB+N4bgYl8W1LoQgBaa+mW48gZrylvJw5c+YBEVw3hdIeV195JTfeeDPFxcW/W/iuuORCPvngfX6Z/TOCTRN4vPvOvbjw/PNRwNKly/j11wDaEosVkcnUkXXTKJVDrz4DyC1I4GczdOnSOfzVJkUBRMepb0jy6KPjefudv2OMsHHjJtDRIH9KBbaEn62lvn4LTZEapV3EuJhsBZGYoiC/EK0VybRHfW3tNpiuiqHjMXwVBWKsXLGaDatW0K1vX8qKiwEfxCBKoUTRzBkmHh077sQBBwTxYGNcLCcgP62vXk+iVQk9unbCtmyyxgq+Z/mhUq1pqK9ts3bt2kF2bU1NeMM6J5PJtkFpjO9hi8JYNhIGyjevW4Mj3chkNb5xsXSs+Wbatm3LDp13YNnyX9EkttGCCqFAFMVhUKO2toaqqmrAwpgUhxx2CLeNvp283Dzq6+t59rnn+Pjjjzn66GO4+OILKCnM5+WXX+Keu+5i0o8/YmnFHnvuzjXXXM2gvn0A+OCDf7Bk8WIsK490uoJEIp/zzruUgw8+kE47dCYaj4HvkZOINt3ZdoNQ2VF8L8369RsAC5ycgFE2hMYqFLYKEtubSMaVMvjZag49dB+uufJsOnXuCkSoqU1xxRU3cMwxJ+KLcP8DD1BRvhEdKQaExmQjDfVJ0DnE7AigMEoCOo9mszeAFJWWlrJjOCi1Mmhl4WazAWORn2TTxnX4viYAM/hBWDIcWFnXjW/dWtnKrq6qDB9ax7UdiaCc8CIWSuxmPNO6NSvxvT1AR9BKYxAsAsRiq7IyunfryrLlTdiqYIZorTASnFNTW4UgAS90QRFVVRtp3aYN5517IXm5eZSXb+WKK67kH/94j2QyzcwZs+jYYQeOOeYw+vXpydNPP8nmTQGWuF37NhQWFgLw4YfvM/7R8biuAI106dKNJ598kr32Gk4ikfjdivBb+Tb9R+kISkcxxgO3Lnw3MMzTGcgk/YB9MfSI+ekMOYlcrrjkPIbv2p2vv5mN+DbLlq3l5ONP4NyLLsII+K7P9TdchRIPcCkuLqGoqATSaTLpMG6r1XY3JSbwrvXo3iOgbRQvILxB8D0PWwcAyLUr1wRJfyqKkKEpy0OwyGTSkYqKigL7qaefJRqLMWf2LOex8eNRVhyUhaddtGRQWY2Hzbw1lWQUOCaFnU0jkXi4ZCpy8+K0aR8E+AONNUgY0yrwsxpg8cLFiBE6tGtH//478/33G+jRY0f22WdPAN54433eeuttjLGwrXwaGxs499yz2bDhTk47LQD6FRUVNsuotraeF154hbFj76O8IkgKKyjK55lnn+KA/fcFYO36dbz99nssXLyC4oJcjj36cIYPH95iEAYCFuWhHQPiUZAb48JzL6ZVcR6Ii5tJYlswYt89qK5KUl0bpMKIpIjF82jXsTU/Tp7ChZfcSU1tCs/3uf6W0Vi2Bb6P7weOGd+NAkKfHt3p2L07a1evZ8uWGiAWeCysLIJGYzBkSCQcevfeKfiuH+QtWb6Dm86gIi6VdY2U1/gBKlNnUGRQ0oTu1GQz2ejWyspCu6SkhFg8TkF+XoLQUyIojFZYnosyNhBh3eZGsq4hYnso10PswDEvGHJzc+neo0f44Fm0ToTRmSZXmmbalBk0NDRSUFDAGaefwg8/fE+XLp3JzyugqqqWiRO/xZgMtlWM0h6W0lRVb+Waa67h5VdeZcSeu9O2bVuUhjWr1/LDpMks/nUurhfFdnLw3CrOPf9s9to7GDCff/kFl112OWtWrcb3NZYteK7L8OHDt3nlYBvZuAaTStOqc1suuvB8ylrlgZsk8Aco6tIWTzzxAvPmLUBF8pFsstlt6vk2DY1Z6hsV6AiPPPYkycZa0g2NvPTCiyjlIKaKzu3KOPqofdGRYqZP/4hVmzYAMbSKhOzyhJQTLolEIV12aNIZwLJsMApxXSIxmyXL11DTmAomk86CZFEmErqIBd/3VTKZtOxkKolgyGSzZcG4bnryIEIRvLLZWllDfUOa3PwcfD9LyNNNkKdk061r10Dgoe0b3hZKGbSVw/Tp0/jii6854YRjOO6441m2bCn1oVZbXV1DZeXm8LqCbQfhxWTSxxiPWTOnMW/ujBAMIGHeTbAXOraLSIb2HTty9FHHEXUiLF+1nPMvuJB1q9ejnVyMNig/Rag4bxclA0ICcQ9tOaxYsZZ99z+awvwcbC3ocAXaUl7B+g2VaJ0HxPFI4jgRtFUEKhdtBQqSdqLU1SR54L77KczP5ezTjiXlGj58732uveREDjxmJI11S3n17feprE6iVAliCGhJpKnHNXl5BXTt2q35HgNEDfh+hng8wfp1m0im66FZ8QxGalPI1PONU1/fkGunUklEjJ1Op1tgd3TgtACM8kAibK6oYPPGCjoUFmK8JCg/yODXwWxo07otbdq0ZdOmjcGNGgVWEwlYwJJ+6623MGjQQLp124G77xlDZdWWcAnyg0w8IDc3zq233sTIkXsx/rFHeP2119ht6FBGjhxJIpHAdYNcZDFBlqEdiVFf30Dffj3p0ztQut56813KN21G6ViAmGzKy21GpbRoYjCZFH369mf47sNw0y4fffw+y1asbj5Ta0VRUR7RnDzSaRs320jHTj149JH72XnnAezUvTvnjPqF8Y9NIGMUiEYrh32HD2TcY3dSU7mZi886mL69dyKbtRn32Kt88eV0UAWIssHLoOxgOVECgker1m3o0b07ICGFsoV4GYxJgmWzef3WcA+3QYXhW1Ehj7XG+IZUOo3tewbPM1FjpF3T+FYqzM8Ov4gdIVPfyOYNW7H69cE3DWEgXje7/Nq1a0/XbjuyadNGlAalAuY2ELSlsCTO0qWLOfnkk3nmmafp06cXpcUB3FVbNLO/7rvvXpx//lnk5CQYPfo2vv/+Wx55dBxDd92Nf7XNmj2XTCaIPYsPASW/bl6dWs5f8WrZfY89eOmlF+nSpQuWZfHxJ0dyxpmjqKmuRYyw00478vJLD/La6x8wfvyTaMviuuuv4uijD+XD999mhx07MHbsw0z9eS6TJ80BywGjqKuupX7TKuKxDH0G9GJzbYwXH32Se++bgOtFwUmAJ2jboJSHbyy0FbDGt+/QgVgshudnQsyZgJ9C+Y3g57Fx/VayWSsEYAQexAAU6IMOMhGNMcq2HRvHsWOWbbUOloJgdqCtbdEgS4Gr2LS5FqM04jYiuFhWrDlm3KpVKzp17AxMDi8WTIBgPwhQEpadx4wZP3HIIQdz4UUXcuSRR9K/Xz9al5Wxww47MnXKZJYu+5WZM2fTu3dPJk6cRG1tLd9/+z0F+YX4JkygDuslNHFYSEh7EHOizFuwkJ+mTQdlYXwJNFTjtzR9g/trFrPNRZecT9euXbn8ykvp07c/5549ioMPO4A3Xn4JgLK2Zeyy61C+nTgZgEgkRrdugYv03rEPcvgRB9B/wHA6dtwBJbODqBqwavVGUskU0fwor//9Q5556ismz54O5GJHCvCMAe0iZFHioVQU43lEI1H69+sX3Gr4nFppjJ/BwsVNZlm5egNZT4GtQvsqCCX6xg8JXkVAPFspjWVZ8Ugk0gFAlGpW04PXTeu7w9KlAbG2+GnET6PsJqeGIScnQbv2HYGgTI2yIChC1bQ8gtIWViSfzZs3M/q22/jgg/f56puvKCsu45RTT+azTz9m/rxfOPucc+jfvx/ff/89NdXV3HTTjTw3YUIIxFBoy0aMj+/7IS+XYNsRbCfC6lXr8V2DZdnh0ibNEKI/brq5IlqysR7xAq03FtGAT9v2HRh1ztlALkOH7U7Xrt1YsWI5X37xOXvvNYJ/fPQBbVqXsWH9Bub/Mjco1KUNPh6xRIREfjGV9Y08/syLTJ+9lqhuj7EsfN9D4SLKRZTgt6A8jsUi9GsWsDS/b9wGIrYhm0yxfn15IBelmgMNEPCO+Qhaay/iOEl7QP9+KK0S8+bN7QHhuZZGwkiSqCbUgMWKpasw2SC31xcXm6Z6BQalbLr36IHSNmI8FA4iXjhrgunj+4JSFo5TguumWLZkMe+8/RYXXXAJBx94IPc/+ADjHx3P/HnzWbliKSiN48QwxrBu7Xp830NaQCQVNtF4FCOGbDrV/G4wOAOEv+8ntrELbGf7Nv1OkCPVv98gnns2mLGTJ0/j808+JRqNcdutt3PayX/jp+kz2XXwCB588CHOPOMMnnn6GSKRXA477DDmzl3KY4+OY8HChehoPr4fmIeDhwwkt6wL639dSE1NcD+e46L8DLa4CC4GwZcoqCiIB0oRT+TRabu03ibFKU08ZrF5Sy3VdUmC/TecwSoYrE3P5zi2n1+Q79q77jIEESl8+823Aj9hM3GY36zRBUqKZl1Fkqyridgu4tZDtDX4AXOMZdm0b9eOgoIiaqprQ9dZgMAQEbSoMKqlMKLQdg6NyWqeevIZ9t/3ILp378aos89hlyG7MHvWHFzXQ1tB7i4I8XiCZCrJ8xNeZPr0yey+x56ce865xHPjGDHMmDGb1155mcqKLRQWFjJ02B6IwLfffEc2q1F2FsvetiwjOYCNsnOY+MMPXHj+Jbz95ks0NDRyySUXsWnTRoqKSjj5pOP59dcFnHX2Gdw7Zgwj9t6bnXr35edpP3L/2Pt45pnnyWQM6VQdOHnBoM7W0q5VEeedezpYDnNnL2DVms1YkSjodLBiiIfGhNCmMOAfEtO0aduBrt12pCUrLSjwXHQiwuoNFdQlU9tWH2MhRiPKawZq2LaVKSgoqLIB5Xle17yCoHCn+EEqo2iFKAfLBKXhDLChLsWm6iw9WilMugLJ7RJUBFNZwNC6VWvatelGTfUcRLvgugEuGDA4aCsnmOGig8wFq5AF8xdw3nnn88AD9zNkyGD69+vXvP/8UfN9j+nTJ3HSycdxxpmnNL9/3DFHcdABe1O1dSsFhUXsussuCHDddbfwwvMfYKQO19QGM8GzyGYCJngdc/AbEvw4eSKV635l3drV/LpwHsouwHVh4eJ59O/bl+uuuZiB/ftSWVVD+dbA+6cjmtqakJ6BVuA2Im4j7VsXcc/tlzFsxGCqN67h2edeJusaYrlFQWqsElzjgWTAzwANKKsAtEJ8j1ZlpRTk5YWQIAmqv6BQ2SzkRVi1rpb6ZCoYAMYJ4D4ofCtgvwchEo2kS0tKq2yQqG1b/UrLAk+UiB8UZlRNbs2m/1hsrdrK2vWb2altJ4yXxQJEdGh+uHTu3InOnTuz6NfpSDaN0g79Bg6hqLCQRb8upXzzOpQqDiBAITzWsguYOPE7TjvtdE457RT2HbkPnTp3IZ3K0Lp1a2JxGzdr2LB+E1VV1XzxeYAh+3HidAYPGEr7Dm1o3bqMaCTK/vsd8LsB0b//AOAlfN+QiBYAkIhbRKJpIA1eFDAUlxThxBTx3BjxRBw3FSWZUtxw/RiefPxBzjrrItavL+e+O+5j9bI1aDuBySbpseOO5CcSrFq9htz8GEMHD+Xs047igCMPBKN5+IkX+W7KbLSVgx+W5PGyDSApevToRcfOHVm3Zi1Ll/4KOh/L0nTrGlT1M8YPyOMUiOdjxAMrxsb1FSQbU6CiwSrbVM5PmXCZ9ok4kUxJSUmV7YubsJTTt337DihlB3tcM3LRbPOQWhbG99iwcRNi7YBvBI0J5a+ALGVlRbRv3xrwyc9PcPc997Lf/geTiMfZsGEDt952N9998wmWUxJcRxTKimA5hSxe/Cu33XY7L734PEXFrXCzGU4//Swuvvh8QHH3PWOZOmUiy5dvwHEK+fjjr5k1azp777Mn48Y9TDQS5cGHxvHdtz+Qm5fHiSccj+8bnn7qSSDNiL1Gct65FwOGbt1bce3Vp3D9jfdQXV0PCL5yyGvVjkZPo5TG0jEMmkk/TOG444+jY/tOVNX4zJ23BIscjKmhfZtiHnrgZnrsuAMbF88hv6SArt27UtClB25dLXePfYJHxk9A2QVgRzG+i+81grhcde2NnHHaKRQVFVFdXc3TTz3OU089jxNzGDCgf7DQKgeFjxIDvh/MI89j0+YtZLN+YOnQJFjY5mESnIiTKSoursb1U+1FZMO06dMlngjygLVdHGCC7EJRVqFglYiKBBkMd1x7vvgbvpb6tV+K59eLlxFxsxkRyUhFRZUcdujxAshll50tntewXZB61ao1MmLvAwQQxykU2y4RbZeJtoslGi8TpXPDYH1A7L3r0BFS39AgvjHyyKOPNwfoLauoGZd1xZVXieu5UldfL8OG7RmeY0lZWQcpLe0kgAzcrY8sW7ZSwrQByaZXSapupoy57SKJ6uB8KxqR2++6Wc4590yJRHIECkRZrURbAZYs0CC0KFpLxCoTQEadcpikNk8VkYUiskJEVoqpXyCfvf+0HHHISIlFHUHFRcU6ipXoKGALILfdfpc0NDaGveKLiJHaqg2y+/A9xbJsueLKIAPD833x/aSIaZBUY6Wk1/8k2c2T5KjD9hZwBKs0kI1VIMoqEOxC0VahgJLdhu3+/dvvvtsHz0/3FBEzb/4Cad+pc9CBTcA5q6BZwNilAlpOO+5A8dZ9I9UrPpZsarOYrIgbgtx+/HG6dOiwozgRW95/5xmR9DoxXoUYv1ZcN0jYXrx0uey661ABxLaLxHZaiR0pE8suFh0iGpRdJOgccSIJ+frbL0VEpKGhTsbcN0bKWrcSQBKJPLniiquloqJcREQmTvxROnToJKDFtooFAkREx46d5Jd5U4K+NCIm64mXXil+40ypXTdFTj58/xAxkiOJnFxxIlEBR7p06hYOREfuu+96mTH9G9ltyCBpAkHtsVs/mfXDyyL+fJn1/eNy2SWnyvF/O1wGDekVIC3QYtkJwS4WHe8gSgcD5fobbpFkMugL10uLn60Wt36dSMMauX30aAFk5L4HihERT4z4fqOIXyP1lSsku36q1K74Qobt2kcgIkTbCFbZNgE7BYIKEC1HHn3cl7N/+aWjTVDaXBUVFtGje3c2rF3TvCxv59KzLfAclixfTibroxB8vw7H3ubhTCZTNDQ0kohHyUnYmIYKVKIAFclDKY3nZ9mpe1een/Acp59+GnN++YVYblvcTBatLAwKmmgIdQQ3W83tt99O3z79ad2qNVdfdSVHH3M4mzZuoqiojB7depBIJKiqqeKJJx5n48b1RJxCBA1+iqKiEl588UX69x1OfV0DM6fPYfjwwWzdWkflltX06z+QW2+4jF+XrWPO4sUk03HwM5w36lxuuPoSvv70bRauWMgF5x2JcaPs0LGYLZtK+dvxh3HqqYfTa8BO1FTXMHrMK3zx/Sw8ccBYIBZYBWjLBt/Dz1SDSXHZpVcy+rabicdjZD0PxzIoS1G/ZRP5iQgFeUF40xch6xkitg63PwNeEitmUbGlIYgnowNcdEu/XJNjCkVZWZnp1Lmzq4FOYCguLqLnzkF4KvAvSwsJawg5JsvLa2lMpdG2wvcbQW/ju3BsJwCxNyTB9dB+klT15iDChMGIj2d8+vTtw6uvvEL/fn1JN24BOyztajUZ7oDyUXYeUyfP4Oyzz2XFylVEnBg7d+/DPnvtz4B+A0gkEhh87rj9dj744D2MEbJuEs+rolPnzrzy6ivsu+9IPM/j7bf/zqtvvkgkEWfV6hrGP/o2C+YtYedhfXl2/J3sNqh/wFMFuI01tM61OefkIxhz66XodCOmoZLRN5zPj1+/xuhbL6bX4L5sKq/noovH8Pm38/BMDIiBToCVh5IYxliIyYI0csEF5zP2/nuJx2Mksx5aB/4uP1mLNNagtEt1bUjOrhQRWzfxBoG44CbRtmL95nLq6kMmhd/BvBWIIRqL0aN7j2xeItFgA6WCkEjE6dBph2AEeW7g4pPffN/S1DcmWbdhMzv3bo8XCripDGerNq1p36ETFRXr+OTjTxm264442NRXlBMvjaOtSCBkz9C7b1+enfASfzv1VFYu/RUdaRUmn7W8qEarYj7/7Fv2mXcAxx9/LAcfsj+JRIK8vEJ699oZrS3Ov+ACchKF/PjjFEBzwAH7cfqZp9C5YwdE4MfvJ3PFlZdz6unHoZQiGi3gs6+ms7WmnOeevpUhe+3Eh+88yrNvfsQnH09k+rSfGX/3vVx+6ak4uRa+DfHCPHLLyhARtm7dylfv/YOHx7/KgoWVKN2emKpC/CweYS1jZWHcFEqnOP2Ms3ho3EPEYlGSSRdfCWIrXC9N1Ya15OfaVJSv55OPPgKgtKgQBfg+OAH9LL7JgpXD2nUbqK2tYxvN6G+lbCguKlgzYED/RyKRSArPT9/tm2AP/cdHn0ssViDghDWNioIaQE6Z4JSIjuRI1LHk/RcfEVMxRZIbvhTJ1Eg6Uy+uaZCM1MtFl50nSmmBiNx13QWS3DhNGtZ9K8lN00UyteKnRdysJ0k/UMAmTf5Rdtyhi4AS22otlt1alF0qyikQIvmi7RLRdomgcsL9L1BUWrVuL6/+/V1JplK/Qxw2tVQqJR988JGUlrQXy9Jy9bWXi4jInNm/SI/uPQSQw488SBbO+kSy9fNF6hdIpnKmVCz6QmoXfibZtT+KWz5TNqz5We4Yfb0cst+ecuDIPaVtm1bh2hgVpUtEWe3FdvLEiRaIbRWLrVuJpYpFa0dOOukEaaivFiNZafAaJJsx4mVEvMYqqVg+Seo3/iDr10ySQ/YZIlAsOYk8efm158SIJ2m/UXw/K6ZxkzSu+ErE/VXuvOty0RpRVkKsSFmgaIV1mpRTKBDx27RpN+7Nt95l/fr14PnpR1wv6KRf5syX7t12DhStpgJPTqkQKRXsQrEjCdFayy3XXCFSPVdSqz+UdPli8bINknbrRIzI0uULZa+RB0lT9sO4e2+Q7JZJklr3maQ2zRBJ14nxfWk0GclKoEl+/+130qnjjgKORKOtxbKLxYoUiYqEioNTINgFQqRYrFhpCJuNSSxeJJdcfo3MmjVbNm/ZIrW1dVJXVy+bN2+RmbPmyFVXXS1OJBZoyZYtl10eCHjmrJnSrdsOoZBs2aFDG7n71ktl7pT3ZfPKb2Xruh+lYtUkWTb/G3njlcdk7913lW0O32igwFlFgi7ZBnWN5Iuy88WKFItWBQJROe64E6W2tkZEPEn7dZKWrIjxxdRtlcplEyWz+RvZummiHHbwnuHvFssZp50vGbdektkaSWXqJJOqk7qN8ySz4TNprJ4pJ5x6bKD8RXKCjAurNFCA7WLBzpNIJF7VsWPXY0uK21JQ0Ao8Pz2+ScCVFVvlmGNODASsCwSrMPxymahIqdjRYonYMSkrzJfP33pEpHGSVK2aKOmKVSKuL146qDj2y/y5MmjooOZAzzPjbhS36gepXvOupKqmivjVYnyRrOtJJqxv9NnnX0r7jjsIaNFOodiRElFWSbiKtDh0oehIqdiRMlFWgI+OxfJk+O4j5PQzzpKzR50vw4aPkHhYpQwigl0olhWVq666TkREZs+ZIzvvtFNwf05x86qQl4jJroN2lkMP2F323XtX6dCurNk0U5EisaMl4kSLmlNLAuGWCHaZaKtQnGiJWE6Q9H3gwYdLeeXWQFv2s+JljYgn4mfKpXrNV9K4+ROprZgop56wX3gNRw4+6lCpb2gUMSJeWsSks1K98VepWPWt+Nmf5dFHr5OcvFxRypJItEi0LhWsslBGRYKVK3l5eSuPOfrEtocecjSHHnJ0MIM9PyUinoiIjL3vQQEllsoRyyoIRobVSpTTSrBKJBIpFohI5/ZFMuXrZ0SSs6V2xbfilq8WaTASjhWZPPNH6TVgcCAAG3ntudvFa/hKtq77u6SqZolk0+JlRJJZI9mwFN27H30grdq2DkyoaGtx7NaCLg6OcKZoKxC8E2sj0UQbsaMl0rKo1bYjJtrOC7LfdZHYVkxuuOE2ERH5ddFi6dmzVyA4u0yUXSZEiwXiv/mNaOAPiJSJijTlDBWIihQLTolgbzu0VSqRSDAg9ho5UtZsWCciIo3ZjGQ8EZMVMclGqV79vdRtfkcaqr+Uy887pvla+x50lGypWR/Yv2kRSRqp37RcKlZ+I+LOk1dfuVuKi6ICltiRYtF2iSirVSDgpmQD4qastPSrcQ8/yl13juGuO8dg3Tb6lj2AEYFbzKGhMcXnX3xBY7IWW4ckLDoomxpo4BonkqCquo4pU+czdEgPduhRSLJ8PcqKYSfy8bRPl3Zd6NV3ZyZOmsLWyiq+nTiTnXt0p/+AnamvrsARhRUrAmUFvm4t9NmpF63bt+eHiT+QrK/H0oEbUSnTHJTSCowYlAQZiwHVv4Oy4jhOUCZeVBwVQCQwBJ4p8X0KCwsZPGgA03+ewQcfvE8644GOIegwbdMCOwEqirISKB1FW9Eg9bEJNREy0AcsvyZgIVBCxHLIZsvZZdjuvPzKC+zQsQspSeIoO6j7YDLUb16A5dQRLyrh7tEv8ODjbwEwYp+DefXV52hT2paU5xJVkKpejZ9eRlGHHN5+/2suu+wBtlZ5aKcQIwGVchgND+4BQYmfyclNPBmP50zfuHETW7aUg+enr3K9lKSz9SIisn79Btln5MhwHy4SbReJsoNlKBgtgffE0p0ECmVgn04y76fHRRq/kJrln0u6aq14ni8ZPy0iRt779GMpa72zgCXtW7WVrz9+VkzddKlc/g/JVq8Q8T3x/EZJmXrxwlXk2edfkvy8wCNjOQViOwUSFG+MhXt7IuCk0LmirELRVuAksSOloq0W+2L4fx0pa9YJevfqJ8XFwUzTdqFY8bairVLRqihwGNj5ouwgY9KygsO2S0VHSgIlRucG1ycmWDmiYwWio8F20Lt3b5kzd37gmPHqJWsaxZhGET8ltRvnSd3aT0XcufLg3VdI1Am2haG7HiG/Ll0pImlJ+a54ni915aukYvlnItnv5YvP75aO7fMDz5rdNXymUBa6RGynTCyrSLRVINFoomLffQ/qNGTIMHbZdTi77Doc67bRt7RRSk5UAYSf/Lx8li5dyuQfpyDioOyQHkC1QFMoUMpGqTibtqxl9tzFDN9jNzru2JHq8nJsHceORMnqevp270/Hzl347psplG9dz4wZixg8eBA9enahpnwz0WgEK+qgxcJDoZXFkIEDKC0p5vvvvyWTbsCYDAHRZxOkPkARIlnAQtsRUE31GWhhagXUT+I24jgRjj/+KAYOHsjgIYOxrQirV60ATGB+42/LZNCEKAqFwuD59YjfGPI5Z0PwnwFJI14G8TP06tWD5ye8yJAhg0i6WSylcZRBS5qG8tUYr5r8jm158ol3uHX0YyQzWfoPGMmECY/Rp3dXsqaOiGfjNlZQX7Ocsta5TJ76CxdcdB+r1tSi7XZhfN2lCWShdYAXEwwiRhxHTzvssIMfb9u2DR3at6ND+3Yoz0/dDebmpkRux07w5VffcMqpp7O1ogKiucHDGAlVpjBGbPvYlsZPa8TUsMfwfjz37G3stENbtm6qpbC0F+S0Q5TBsWyef+UFLj7/CjLpegb368eEp0czYGBnGjZuJd56J6ycDni+hev7OLZga5unnhjPZ19+Q5s27ejcuQvFJaVghIqtFaxbs5JZs2bxyy9zw/vJD1Iw5Te2tG+IxyzuuvtOLrnofKLRgFxm9eo13HLr7bz+2hso5YClEaW3ISS0Ddks+PU4Ti4j9tiN3n360aFjZ4qLi3C9LFu2bGTN6lXU19dx+WUXsMee+5NxXYzWaDFELUOycgWmYQ25HVvx/Cufc81VY6mpbaBnn4FMmPAcw3cbjCceyrMxNStoqF1JYZtc5vyyglPPuZlfl1ZiRfIxVirAl0mYSYgf+qICtJkIXmFh0age3Xd82bSsyez56eWenxTPaxQv1JCqamrloEOCGr3KyRMdDWv5WoWBuWIXiormiI7niI50EKXbBz7UvQbIyoUfiV8zRWqWfi9uTbl4XlJ8Uycinjz82MOCFQQJhg4eIotmfCJS+6NUr/he3OQmMb5IJmUknfHEl6QYqZPNmzdLKpX5nY2bTKZk0cJF8syEF6X/oCHBZmQlAgXILhDlFIpyCkTrqBx7wilijEgqlZRXX31JvvjiMxERWb5ytfQbMERAB0pUpEhUtEiIFQkqIYAcceQx8uFHX8iaNevEc83v7qOhoUEqt1aKiCdZaRDP8ySbEvFdkcbKpVK16jORhqny+ktjpaSkUADp1K2LfPnd94FC5TWIeGnxa7dK1cJvxJRPkwUz3pV+vQIzTuvOou12oqJxUZF4s9nYFAiyrCKx7QJxnMTm8869JP/UU86h5YHnp5/2/LQ0Hb4JOvPhh8eLbTuCjkvEKRFtl25LbbQLglROq1CwW4my2ojSwc0fecCesmXhd+JvnCy1a74Vt36N+Jm0+L6RrFsvo8fc2Kzl7r/77rJx3j/Er5omFau+FDe5UcQXcbNGsn69uFItQbQlK+KnRbykiJ9t1vib2tJly+TkU08ONY6EaKckCFhEiiTiROWFF14SEZE33nhdEom4DBzYXxYuCsrTnnbaGeHgyBc7WixWtERQcbGdhIy59z7ZvHlLiyu5YkxajJ8UMent7qNRjKT8jJiML5L0JVuxQapXfSpe/bfywdsPSauytoGDpl0refvjd0VEJG1EfNeXTPVqqV32hbibZsvaX6fKboP6hiZcUWACWaViq2LRVq7gJIKJpstEW63FsVqJUjmSm5s3/sEHxnH/2Ae3O6zbRt+yATh/25wWtLIpLi7h22+/paJ8I5adwIQIxkCdDRzaTft2sG8FZdwXL1/JhvWb2H/kHiRyDA0NDUTiRUFKSiTGLoN3I+O6TJs6iZXr1rF+1Vb23m8EhQUxGmo2EE1YaNtBkcDzg2KOYsJSeM1bq+DjBzgT5VJa3Ip99t6HTRVbmTfnZxAL5TgBLZJk2HPPvRg2bDfq6mv5buK39OrVi5NOOol4PMG7773H/PnzsOygkIjxPTA+4x59kCsuu5T8/Bw8P4VvXExYSNKY4NlFBBET9o2FZSwsy+BnllJXO4/cwhJ+mLqeUeffwpbyLRSXlvLIo49wwjHH4aEDp2a2htotv5LIMVQko5xxzhVMnj4bZSeCmkphSECLCuj9Q3AdElgHjm3h+3XeyH33PT2ZSlZVVFRSUbntsG4bfUsS6AnsHPRe4NssKS5h+YpV/PTTtABvGxaU3CbgJjxdCHITQWuNpWHBkmVs2VLJyJF7ErENDfXVAeWeHyeWiLHrroOpqS1n1sxfWLxyHZs3VrP33sNJRJOkkxuIJhIoCkEcfC2I0oiyAvRCU6EsFRBMZN0sWTdJQV4xe+45gp9nzGT1qlWIClCKxvVIJhs45dRT6dypI8cddwJHHXkUxcWl/DxjFuMffYzq6hqsaDwoAeDVcc2113Pt1ZejbY3rpkEMllIExogFyka0Fd5T0DdWFmyl8LObqKn9kVih8Mv8Ws488z7WbVxFfkkOD4y/j9NOOB3LWGgDJltJ9eZFxHMVKU9x6llX8+2k6VhOAuXEw7IBARheKxMGpdU2ASP4xkVr/6PBgwc9nc1mcT13u8O6bfQtWaAGOLV5DouPZTmUlpTx3XffUFVVAUS2RXpC8aqmi6nQLhRBKRtfYP6ixdRtTTNy5B4oq5p0aiuxeD5K5RCPJ9h1t93YtGUjc3+Zy8LFS6mpbGSfkcOIOBlSqQYi0VKMiQWplSFiMMjsa9LiA63e94LcIaUgNyeX1q3b89VXn5NsTGI5EYzApg3rWbV6DcOHDaNt2zbE4wl++mkmV1x5JQvmz0U7+SCCcevp3ac/TzzxKMVFRfi+BxLUfbCsJgGHypjWAcRYCUoMtij8zFYqt8wgpwBWrkly2qm3s2zVMvILW3Hn/Xdw3qnnobEDGHN2M7WbZ5HI8fCdAk474wa+/PZndDQXpWL4voTqfOAH0CKBgHVTmR0Jsjf9BkbsNWIUqLWu6/Hbw7pt9C0A9caYviLSTZoodzG0a9eeZctWMGPGTJqKJgb8HU3n6JaTHlRA74PSGBNl1rwlGDfNXvv0xvhVeG6KSKQATIzc/Dx22W0XFq9aytJFS5kzfwUmbbPH8KGATyaTIpaIoi27OXKplQ6yJSQouYMIlhXUShJAa4uuO+7IpCnTWLZkSbBsKhtjhHnzfuHv77zLDz9M4qWXXuWB+x9kxfLVKJ2gCTkqJs2tt45mn71HhBXWDJbWAVMBwTIcHMGgtjVoP4ttKSRTS9WWn4nmeVRuTfC3E+9h/q9LyM3P5Za7buOK8y7GWDbKN0hmCw2Vs8jJSeLHEpw76k7+8ekMxIoFv290i06VADmpwn5XQYqoZQVppVqZr7r36P6I1nobh3CLZt1y602ISCOQBI4l5DHzjYdtRWnbrgPffPMlVVVbUVaEbRkBAYFYsEkEdlkT+DqYbTG0yWPS9B+IWsJe++wK2RR+JosTzQViFBQWMHy3YSz6dQ3Lly9kyoxfyIkXsvvuwzGZSnyvnmi8GIum0GUwi0K0Dqpl0hVBjT/bcqisrOKHH77FdRWWtoNBpzV1NdUsWTyfFatWkvYEraMYo7C0wvcaKCpqxe2jb6Vt29YY3w3zE0JTRBRGhTNY68AY8LLYlkGyjcFSm5cmJRFOOXE00+esIZGTx9U3XMAt19yIry1sH7zMVuoq5hGPZzERmwsueYC33v0eQ/uQ9a5l+kWojyrC/g2Er5RgWQrfrTEjRux1RTQSXdRU8PO3h3XrbTc3CbsS2AHojQKtBSMe7dt2YMuWaqZOnRJyQYQpLcoK/t/kAGHbfYEdUBLYWTQWP/w4gwK7kN2HDcPNNpD1G4gkoohnUVxSxtBhuzJ34QLWrF7Bd5OmUZAoZvjgnkiqBt+1cBLxYHmWbcBu1SzkJuqD4H1LR3CzLh98+CnpdLYZ9SkiKMtCWxEsywk70g4cNoAxDQwcOJBzzz2HRMIJM/klsDLD6wrBvgugjItj+0i2kbrNK1FWLVlyGHXOXXw1cRqxWJzLr7iMu++4MyA5zWaRbB11W5cQjfmQKObKK8bx0mtfY6yisCt9FIamzWDbQahcBVXdbFvj+Rny8vJ/7NJlh/ujkUhaa41lWb87WpIzVwAPApuax1DIvHbxxRewc88+YLJsX4KtifLQBOg/QoZY0Sh8/GgtOuZgq/bcfM8EnnvqA+K5OfjZLdRWLUT5ScgYunfbkacmPMKwvfcAhOtuv4fvv55GjhPDa6gitXUTXroRrfwWZHUtk41U830A7NxzJ/Lz8zGeH/JlblvyQoAiEFQRb8qiBOjWtRvRaCScuUHHBpp707oV/I5WYCFIupFk+Xr8VA2FZTncfuvjfPT5ZCwnh/MvPJl7x9wSMNX5GuU3Ul8+l5xYBhWNcdP1D/P8yz8A7dCWA1YtGoM2oEXQYtBiUJgwfQhQGm2FSqbX4B980MEP7NitW03rtm1p067dHx6/rfU2Qyl1Jag6rWwsZeObDO3atebqqy8lGlVB2kWowQY8i4DYiDTtTyrMt/FQrsbN+vha4ZLHVXc9yrPPf0pRQSciqXoaaxbh2RVkpJZeO/Rh/PjH2X2vvencsQ2uVY9LPZbZSrZ6JY0bF5CpWov2kygl+NrGteIYbQc4AK3CKmpCbkE+VqSp+miQwY+WZtpFIaQCRILUnDBxOL+wEMu2A609TKoQS/AtRcaOYSyFjWC7WdzqLTSsW4bKVBK3UqiGBgpzNJ06tuWMiy7grocfIo3gm1q02UR2y0LyIgaxC7n11qcY//Rb+OggTUgMyrMwSjBagr8q4CgTbJDg0AjauLiZKnr06DWppKRoaiIWJScn8afH78rLKqXeAjqKyF1ATCGIeJx15um8/977fPLJR9h2HIwOFJHmWdSCwFSFtX7d4Od9lQHLIutHuf62h4jHEpx92kE01NXQuLWcRGEXXAuG9OnPqy+9zJaVs9ipcw6ZdAMx5RGLRkhn6khVZUHSRMs6IxJFCCI1qJZ7V5Aj20QAIxL6z2FbjlKLRUCQkEcEVBNZJkGxy5aJ4lobbPGw/Azpqs00Vq4h7rhE7CxGpfFrLa6+4Hz2PXoUu+1zADbg4aGNT3LDRqKWhZ+Tx713P80jT7wGxFB2BCNJ8AMSUlGy/c01r5TBayWCqCy5ObGaE0887oFOnTpVZTLpFs/++/Zn9YMfBQqAG0DZRly0shgz5h7mzJnFls1VYLWgSwL43c0JqKYSL4EyZkWiNKaTXHHd7UQdxSmnnEDd5kqy3hYSxSAe7NClAzt0SJBa9Su4HlhJjMniaI3np0nVueiYjZPXFsuzER3B6OY0DMRqybYnf/Xs21oLB0rwUpppfAXBEsHyk2jl4iUrSNevwrbqsbVGwjI2WRSJWA777LMLGBfXyxCXNKmKCmyJoHJKefChRxk77llAY0WCyuIoQYlPkAz3W3HItt0nHG6uW59s3677XdqyvspkUrjuXxcB/zMBu6DuBEkDtyilYr6foW/f3tx802guuvj8IJlMN9Uiau7R39xcWDdQgnxj34C286hP13LpdXcRj0U45sT9aNy6gWRDJbZjIykfyWbQ0UxQOt7korQLyiMSFVzjk0nV4BS0QTsRgsqdYXQnVIpiTlOytwl5RP6iCTTlONu2EyomNspqYQZK00zKks3Wgp0mFrPQRqN1DDEWxvEx1JLeNANtRxA8UpLBdjSRgiIeefQZ7rr/CVzjEInl4YlqoaA21Vf8I3E0WSkK49XRu1f/CUOHDn08J5HrK2Vj239dAPuvKoC7wD3AOhHu0Up1MMZj1Lln8vPPM3jp5WdB5wU0P8FQDBwAsE3bbV5dtoUaxdhop5Dq+hquuOEOCvOFkcccgrj1uCYLIjiRCFZeDrgW4iVQVgZIo8ni6BiezgFioKLNsfemfOamy1ohgZvSTfnOf9a2fRaJRIhGouFvtOgaRXAtfKx4PolIwH6HaPBjaB1FR2vx/Sza12AyiLjYto2Lw5MvvcrosQ+TyWosJw/PWMHer4LE+GZy5pZEaEGPAmHyWaaB1q3Kvj/uuGPH9O7dO1tdXYMxBuuvJAiorJvc7g293T7VvHYNB24EfZilo6xbt4HDDz+SuXNnYUXzgvKtyg7cmSHTWjDymridnVAATe+DMoL4tXTt1J79DxkB2sP1syCgjcbBQowi4yvQLpogmVxZFr6xETsHRRxlggCVhLPAjkTIuoYPP/iY2pqGQFH5SwEHufFi0gwcPJhdBvVD8ENHR6A1i9h4ykJJEkwttgqsCRGFESdgildplCg0UcTNopSPFY1QVZvhyy9/oqamAaxEYOs2OS6azMsmTV6cbf2DwdIqUCiNh639X4cP2/P4ww49fGF+fi7+X2e1/7sCNoBqawwn2ZZzm1J24Y+Tp3L0scewtbwcK5aH8QJqtMArFE7d5lLlvzVpgmYpB9/1CKqHmhbXyvKfaQpIgB2BP1moldZItpGgCtl/ojUVM2mK21pAMXYkgufXtRBsU9/obSte800F5zi2RiuD66bKi0tKzxnYb9AnI/fZm9LSEjxv+xJ2f9b+yQTfrm0SkXFGzCTxvev33GP48Q+Pe4SzTz8Nk23EjuYFbHOm6QY1SFPHBjZycxZcEzOAMthxC9+LB7PMCEiK0qIyWpXlB2aKnUAZG9WUEmIELA9UBtF+kPwsVvNgCkwfhaUdopE46zdWUFle/nsNGoLl0fPp2LkbRUW5ZLLJZudCc+Hm0IsWuGhBfEKeLQ3aEFT+83GMi23FWLxsE8mUG2RpaINtAq+X5zU0+89ppjfW4aSwg2dTmd/cniGTTTZg3NGZTPazdDr4XCvdTJf0nxRweFGZ5fv+BZalvjz5pBPuWL9mdfubb7oRL5NEW7EAGRHe3PYjs2UdiOCv8TVKaxzJBGE4LRg/zb77j2T01WcQjVqogn7YOhb8ltHB8maFCpzywmtsu46RQMBKBabOddffzJtvvE5TaZ1mrk+lwA/q8F595WUce+xh+H5Afi7KNLspm5jCFCYgIjM6jKJJ6JXVNCarMOmFRJ0ijjrhKhYsWIOyHMBFORpx6wOFrUUkbpujqMniCCvLiQmURvHxMnVu5y5dHjrtjDNfSKVSJhaNgSVU1VW1IDT/6/ZvCzhsVUbMi+Lz2aGHHdq6trr63vsfuP8gpSNoRzAotNgYz28mXd3mCWrRVByTcXDERyuXbMiLmVuQzw7d2xCzBfK68a/ZOn/cCvMTNPF1QKAZy3YDzaNjhzZ06NDuv3wNTCFeehN2vE1YPCQCJoIocH0vsDiUDvWEwEUatKb+CFyUlqVDhVlwsym/TevWj59+5llji4uLs67rIhLUlnJ9F/Uv9sl/VcAAxjdmUzab3XTU0Ued2ZhKjX3yicfP8ADtRJGm8jlNTf12xElAIaSzZH0XUBgVAWJUVimmzqwmV6XxciZjWZFgCaPZVG3xe7L9dQjoGI3vY9sWm8sraZnHYwKVClp09pKly5g3bwHJZDpwbYZxV2lyy9Ic5iBwOGz7NWWB56ZRpgHjrqW6yoCOBFmBuskVan5zj2ybzaG+IqFJhxLcTNIrKSl47vi/nXqjViqTzWZpibP6V4UL/56She8HjGu+B7ajcV3D3HnzaGyox/Ml8cbf37zz9VdeusyTiIO2W+xjf9IsA9oLTEBjg44HYDfPIyL1aDJsi4E5bL/MN+1nTdcIIy/N1ws0e18p8OyAG0RZgd2MhFQVBmOyKCXogIW7xW/9Vilseq/FstpiP7XRKGIYlUOWgG8SKxOsvn5AGyy/G+S/6Q4l+F7WKynJnXDSyaddAsYvLiokNy9vOwH/O+3/ZQY3NxGhoaE+edJJJ12TiCc2vDDhmTsyGTdPOQGofJsC+xthiwmLd2jQYNGILV6Y4B/cnrNdlmNL52EY2SIY0dt/EghCAEcpRAueB4amfNvA/BDjEbEFywroApscG7+bbdvfdLOsg7MCH3zAtRWwvsaUwjWCjwUmikgsVLAybD9gCGO9CsHH97PZsrLC508/a9RF9fUN2Na/PlP/rP1HBNzUqqqrOeW0U8Yl4rENz094dkxNbWNXOxrHhKFig9q2xKKwPButLDytEC/FAfuPYL8Ru4GXQvseom1MYXt8UQH3pSi2mbSh50prtNZBJbGQvzKeiIMIqXSSqG2zevkaHnvy6YDkW4U+Xy9FIuFw+qlnslPv3qRdj5x4HM9zcbNN20vT8imh0hi81mF4UiEoSwIHR0MF2sriWWDFErz6+gfMn7sMdC5IFMFr4RcgBMcIlq0xxsP33GS7NqWPn3bm2den0v8pk+0/LGClFBXlFVxy0YVv19fWLn/j768/0JBMjdROBIUVOhxU89IdOAZ0SIyZYeTu/bjy5nMgUw3pJNh5kPPnlEp/1so3b8b1XNqHFVmmTf6R8Y+PC0i/lUJpjSFNIh7h5FNOYs8RAQXx2rVrKC0tIZHI/Tev6EHyVzCNELEgUsLP02Yx/5fFaCyMMqHXqmnam8AYsHRQIkFTjcV9111//f1r12/8t5/3r9ofGIf/b00pRTKVor6+bvZOvfoc1XOnHk+aTGPSy6Z/d65RgqtNyJAK9Y3VsHE5rF9JesMGqtZvIJtei/ErELcS8WoxXl141GC8rRivHONXYrytgMfGdcs5+KCDGDxoCDOmTgOB2uogc15CqE/Avg6+myHZUAvACxMmMGDAIEadfRaNDXWIX494VYhXiXjlGFOJ8asxfh3Gq8VkqzFuJcZspX7rUurXrqZx4zrq1q4mtWUDJhkQoysDWCnQ6RZmY1CwxMs0EovodaVlpdcore9vWdj5P9X+ozO4ZVNK0djQUL/P/vtf3KZdu4U//zT98sbGxh7ayWsuuWeQgEwz9MpYEWfbcmjZKK1Ibt0YlHNTgUvQ90IOj1DJMXgBTZSxiecWUblhOco0EtFZqresxq3vRiYbkLFpDX4TOpSwNGu2Efwk5WuWYJk0VRtXk9y6GuMYjHiokBDc6FDBkqCEnzI+onyUpfDSjUSNgAc6aqNUgqgdBXQQrNeZ4HomGrpzXSRbR+vWred12aHbNZs2b/5a5L+mRP2z9t8mYAiEXFNdzTmjznlyzeo1s+tqqq6p3Fp5rOMUopTC9VSgZOFigESiANW2GypVQcxPExMFnqKZMVt5QTiwuQmiDEb5KDyUv5l+/Vvzygt343kefXt2Q+XU4oaWgiaC70eDUkE4ZNJZRFJg1XL5xUezx547sVOPrpTlZxHfowke0OKJQFxQTTUdJQA+OjEoCFYho+Pogg74DoCLsrMoiWApwXIM2UwaJEXPnXf6at/9D7xq0a9LFv6rbsf/SvtvFTAEQq6rq8P3/Z+6ddv5zG7d/RmzZv5yUdb1OllWHsZoLO1gyDBl2lyGTl5EYa4im9oaapi/Layxvd2rLUE18VqHkZmcoiJitsOilWuoSa7g7bffD6JehLq1CKIcGlP1fPHVd7RpX0BO3KF91x2pbEyzcXMlLc0kQRBv+xmmdEtzSeGbVADGs/NZv2kZc+b9GvBQGhUQk+OTSW9GKy3Hn3DCU4cdetjty1asqEinf791/Sfbf7uAm1rgWpOGffbZd+y8efOnlbUqvmDjxg1/U7oUZcexHc2nn//IvAWLycuN42bToYn7+3hnSy9dU75Yy88UAQgfpUgmU6xetRnLjuHhNUcVA10nwgsvv8N3E6fgODbGN0Husba2u4ggv4lIKX7rClZiMBLUPy4vr6G8vB7LyUeI4GdTGKro3mPnZPdu3UafdOLfHmvVqiwzZ+7cf9mn/F9t/2MChkDIjY2NWJaetOuuu8xXeuhXH7z/2dXZdKqPFSnG9S1WrGqpRf6RA+Nfvlr410KRg4UG7SHiBv5epdA6h2TKY9HiNS3OD4AC21+vycHR8rd/+3nTYQAb20rge1lEtgJ5nDPqgl+POPzQWz7+6JP3y8vLKSjI/28XLvwPC7ip+b5PTk5u9Y5de7y0eOmyqa1bFR038bsfrgJKVLQMrR2MCYpv4Luh0aj+XM5/4SBSBDa077vBacoLXZE2Rmy0jmF0rEWcVv3x7/0upLfNg6a0QnlB4ESMB8rH8wJG2v0P2F8uveTyh/bae6/nV61ctbi2tvZ/RLBN7X9FwBAwqSaTjRQUFS4tKSu9f+iwoe9HY/HTf/j+++t8tKXjRf9fe1cTGtUVhb/782ZG4+QlTiKdX1cpohsxkzFVaCImFktaTZGqBETQje3OhT9dKOi2dFNw78qFYiFVF4JRaLEkCKW0AauUZlEngiajmRjnvXfvPS7uezGJEFskyQj9dg/uuzzux733vHO+cw44cUArEKxqhEVerTfc2ostGAOxWHhHz3E/hj2GbENmwlvKwts44cKZw0qwMAQjOYzvA2SLlHV1d+Hrr47dKhY7zjuOM9KYXPOyOlVdVnKBFSQ4gtEGnuepprVr72ttzvX3f3G18uz5vju3hw4BlAZWg3MZFsELM/oWWLa0yKLZXQvYjPw5kRxisJXSCSCFxY4BBpsI8PrZOiyINBg4gsADMAPpJNDTsxtHjh6d+Ghr6ZvJytOr2Wzm6cOHfyGXw7KTC9QBwRGM1ggCVWvKNN+bqDz7/eyZM98+ePBn5/Ubtw9MTT353IAneWRRh1ezJW9W+z9ntvn+XjAPIJuhQCTDETZUSNzmHEXynHnvzcGs+BwAOIdRtrcvINC6Loe9/btwYP+XKLZ3XEwmk+e1VmOPyv/oIAhWhNgIdUNwBGMMtNae6zZ6DasbrgG4eeLkqeTduz/vGh6+twekdmqtGxl3BBeOUMqqETlns+a0rbzDbNjQ2F0WFTe3agiAjIF5HQAMx1r/tpDSnhTGWLE8t6oUMrbjqJQCzc0pdHbuQF/fHnyyuyfIpdO/OY48BbCfAPhL+W/7X1B3BEcwJtImw4/FYhNg7FJLS8ul0yePJy5f+WHb6OgfO6vVqa6GROJDz/fiZHjMkHC4YIJR2GaVcxtgNCxsNaNAwuZW2eCBAYMECz1i9lImaD8KCmhwTog5McTjAtnsehTb29Hb24uPu7fr9AeZquPEfwXUBUAMAuS/izhhKVC3BC+CGoChmZmZoVJHCZ9+tjc++OO19sfl8c3j4483+N7zDa7rrgdDaxDoZq0IWpPVCxAsB2ChowJgCCAEh5SAlAJSSkgpscZtRSGfp3yhQKXiFlPq2KLa2tqCpibXCb/hsjbed4b0fV5fnM7D+0jwLIgISimPiH4hohGEKqmDAwMxzuXhv8fGzpbL5URlsoKaV7MKztBYgyEwIWqJVateplIplkmnkS/kWT6Xm05n0qpQKLzYuGmTn3LdmhB8WggxyRh7BCCrlD8quPweC1VydQj2b8Vb/+P9xCsI6J0P9LoMiQAAAABJRU5ErkJggg==
// ==/UserScript==
/**
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program. If not, see .
*/
// The /web application owns its DOM. Never run the legacy body.innerHTML rewrites
// inside it; InitializeContestWebApp maps its Bootstrap 3 markup to Bootstrap 5.
function IsContestWebApp(pathname = location.pathname) {
return pathname === "/web" || pathname.startsWith("/web/");
}
function GetContestRoute(url = location.href) {
const path = new URL(url, location.origin).pathname;
const match = path.match(/^\/web\/contest(?:\/(\d+)(?:\/([^/]+)(?:\/(std|solution))?)?)?\/?$/);
if (!match) return null;
const cid = match[1] || null;
const num = match[2] ? decodeURIComponent(match[2]) : null;
return {cid, num: num === "rank-correct" ? null : num,
page: !cid ? "list" : num === "rank-correct" ? "rank" : match[3] || (num ? "problem" : "contest")};
}
function GetContestProblemURL(cid, num) {
return "/web/contest/" + encodeURIComponent(cid) + (num == null ? "" : "/" + encodeURIComponent(num));
}
function GetContestProblemIndex(num) {
// XMOJ's new API uses the letter; submit.php still uses its zero-based index.
return typeof num === "string" && num.length === 1 ? num.charCodeAt(0) - 65 : -1;
}
async function FetchContestAPI(path, signal) {
const response = await fetch("/api/" + path, {credentials: "same-origin", signal});
const data = await response.json();
if (!response.ok) throw new Error(data.detail || "无法读取比赛数据 (" + response.status + ")");
return data;
}
function CacheContestProblems(cid, data) {
if (!Array.isArray(data.problems)) return;
const prefix = "UserScript-Contest-" + cid;
if (data.contest) localStorage.setItem(prefix + "-Name", data.contest.title || "");
for (const problem of data.problems) {
const index = GetContestProblemIndex(problem.num);
if (index >= 0 && problem.problemId) {
localStorage.setItem(prefix + "-Problem-" + index + "-PID", problem.problemId);
localStorage.setItem("UserScript-Problem-" + problem.problemId + "-Name", problem.problemTitle || problem.title || problem.num);
}
}
}
// The contest pages moved to the /web app and the site no longer links to the old
// ones. Returns the new address for an old contest page, or null.
function GetContestWebRedirect(url = location.href) {
const address = new URL(url, location.origin);
const cid = address.searchParams.get("cid");
const validCid = cid !== null && /^\d+$/.test(cid);
const pid = address.searchParams.get("pid");
const num = pid !== null && /^\d+$/.test(pid) && Number(pid) < 26 ? String.fromCharCode(65 + Number(pid)) : null;
let target = null;
if (address.pathname === "/contest.php") {
if (cid === null) {
const page = address.searchParams.get("page");
target = "/web/contest" + (page ? "?page=" + encodeURIComponent(page) : "");
} else if (validCid) {
target = GetContestProblemURL(cid);
}
} else if (address.pathname === "/contestrank-correct.php" && validCid) {
const user = address.searchParams.get("user_id");
target = GetContestProblemURL(cid) + "/rank-correct" + (user ? "?user_id=" + encodeURIComponent(user) : "");
} else if (validCid && num) {
if (address.pathname === "/problem.php") target = GetContestProblemURL(cid, num);
else if (address.pathname === "/problem_std.php") target = GetContestProblemURL(cid, num) + "/std";
else if (address.pathname === "/problem_solution.php") target = GetContestProblemURL(cid, num) + "/solution";
}
return target === null ? null : new URL(target, address.origin).href;
}
async function GetContestProblemData(cid, index, signal) {
if (!/^\d+$/.test(String(cid)) || !/^\d+$/.test(String(index))) throw new Error("无效的比赛题号");
const num = String.fromCharCode(65 + Number(index));
const data = await FetchContestAPI("contest/" + encodeURIComponent(cid) + "/" + encodeURIComponent(num), signal);
if (!data.problem || !data.problem.problemId) throw new Error("无法确定原题题号");
CacheContestProblems(cid, data);
localStorage.setItem("UserScript-Contest-" + cid + "-Problem-" + index + "-PID", data.problem.problemId);
localStorage.setItem("UserScript-Problem-" + data.problem.problemId + "-Name", data.problem.title || "");
localStorage.setItem("UserScript-Problem-" + data.problem.problemId + "-IOFilename", data.problem.name || "");
return data.problem;
}
const MonochromeSkinCSS = `
/* Fonts loaded via to avoid layout shift */
:root {
--mono-black: #000;
--mono-white: #fff;
--mono-gray-100: #f5f5f5;
--mono-gray-200: #e5e5e5;
--mono-gray-300: #d4d4d4;
--mono-gray-400: #a3a3a3;
--mono-gray-500: #737373;
--mono-border: 2px solid var(--mono-black);
--mono-border-thin: 1px solid var(--mono-gray-300);
--mono-font-heading: 'Playfair Display', Georgia, serif;
--mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
--mono-font-mono: 'JetBrains Mono', 'Consolas', monospace;
--mono-transition: 100ms ease;
}
[data-bs-theme='dark'] {
--mono-black: #e5e5e5;
--mono-white: #1a1a1a;
--mono-gray-100: #222;
--mono-gray-200: #2a2a2a;
--mono-gray-300: #404040;
--mono-gray-400: #737373;
--mono-gray-500: #a3a3a3;
}
* {
border-radius: 0 !important;
box-shadow: none !important;
}
body {
font-family: var(--mono-font-body) !important;
color: var(--mono-black) !important;
background-color: var(--mono-white) !important;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--mono-font-heading) !important;
font-weight: 700 !important;
}
code, pre, .CodeMirror, kbd, samp {
font-family: var(--mono-font-mono) !important;
}
a {
color: var(--mono-black) !important;
text-decoration: none !important;
transition: var(--mono-transition) !important;
}
.container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link) {
border-bottom: 1px solid var(--mono-gray-400) !important;
padding-bottom: 1px !important;
}
.container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link):hover {
border-bottom-color: var(--mono-black) !important;
}
blockquote {
border-left: 4px solid var(--mono-black) !important;
padding: 0.5em 1em;
}
/* Navbar */
.navbar, nav.navbar {
border-bottom: 4px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
opacity: 1 !important;
}
.navbar .nav-link {
color: var(--mono-black) !important;
text-decoration: none !important;
font-family: var(--mono-font-body) !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
font-size: 0.85rem !important;
}
.navbar .nav-link:hover, .navbar .nav-link.active {
background-color: var(--mono-black) !important;
color: var(--mono-white) !important;
}
/* Buttons */
.btn {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
text-transform: uppercase !important;
letter-spacing: 0.1em !important;
font-family: var(--mono-font-body) !important;
font-weight: 600 !important;
transition: var(--mono-transition) !important;
}
.btn:hover, .btn:focus, .btn:active, .btn.active {
background-color: var(--mono-black) !important;
color: var(--mono-white) !important;
border-color: var(--mono-black) !important;
}
.btn-primary {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-black) !important;
color: var(--mono-white) !important;
}
.btn-primary:hover {
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
}
.btn-secondary {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
}
.btn-secondary:hover {
background-color: var(--mono-black) !important;
color: var(--mono-white) !important;
}
.btn-success {
background-color: var(--mono-white) !important;
border: 2px solid #52c41a !important;
color: #52c41a !important;
}
.btn-danger {
background-color: var(--mono-white) !important;
border: 2px solid #fe4c61 !important;
color: #fe4c61 !important;
}
.btn-warning {
background-color: var(--mono-white) !important;
border: 2px solid #ffa900 !important;
color: #ffa900 !important;
}
.btn-info {
background-color: var(--mono-white) !important;
border: 2px solid #0dcaf0 !important;
color: #0dcaf0 !important;
}
/* Cards */
.card {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
}
.card-header {
background-color: var(--mono-black) !important;
color: var(--mono-white) !important;
border-bottom: none !important;
font-family: var(--mono-font-heading) !important;
}
.card-header * {
color: var(--mono-white) !important;
}
.card-body {
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
}
.card-footer {
border-top: 1px solid var(--mono-gray-300) !important;
background-color: var(--mono-white) !important;
}
/* Modals */
.modal-content {
border: 4px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
}
.modal-header {
border-bottom: 1px solid var(--mono-gray-300) !important;
background-color: var(--mono-white) !important;
}
.modal-footer {
border-top: 1px solid var(--mono-gray-300) !important;
background-color: var(--mono-white) !important;
}
.modal-title {
font-family: var(--mono-font-heading) !important;
}
/* Toasts */
.toast {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
}
.toast-header {
background-color: var(--mono-gray-100) !important;
color: var(--mono-black) !important;
border-bottom: 1px solid var(--mono-gray-300) !important;
}
/* Tables */
.table {
border-color: var(--mono-gray-300) !important;
}
thead th, th.header, th.headerSortUp, th.headerSortDown {
background-color: var(--mono-black) !important;
background-image: none !important;
color: var(--mono-white) !important;
border-bottom: none !important;
font-family: var(--mono-font-heading) !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
font-size: 0.85rem !important;
}
/* Header cells are inverted, so their links must be too: the global link
color would otherwise match the header background. */
thead th a, thead td a, th.header a, th.headerSortUp a, th.headerSortDown a {
color: var(--mono-white) !important;
border-bottom-color: var(--mono-white) !important;
}
td, th {
border-color: var(--mono-gray-300) !important;
text-align: center !important;
}
.table-striped > tbody > tr:nth-of-type(odd) > * {
background-color: var(--mono-gray-100) !important;
}
table {
margin-top: 16px !important;
}
/* List groups */
.list-group-item {
border: none !important;
border-bottom: 1px solid var(--mono-gray-300) !important;
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
}
.list-group-item-success {
border-left: 4px solid #52c41a !important;
}
.list-group-item-warning {
border-left: 4px solid #ffa900 !important;
}
.list-group-item-danger {
border-left: 4px solid #fe4c61 !important;
}
/* Dropdowns */
.dropdown-menu {
border: 2px solid var(--mono-black) !important;
padding: 0 !important;
background-color: var(--mono-white) !important;
}
.dropdown-item {
border-bottom: 1px solid var(--mono-gray-200) !important;
color: var(--mono-black) !important;
transition: var(--mono-transition) !important;
text-decoration: none !important;
}
.dropdown-item:last-child {
border-bottom: none !important;
}
.dropdown-item:hover, .dropdown-item:focus {
background-color: var(--mono-black) !important;
color: var(--mono-white) !important;
}
/* Forms */
.form-control, .form-select {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
font-family: var(--mono-font-body) !important;
}
.form-control:focus, .form-select:focus {
outline: 2px solid var(--mono-black) !important;
outline-offset: 2px !important;
border-color: var(--mono-black) !important;
}
/* Alerts */
.alert {
border: 2px solid var(--mono-black) !important;
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
}
.alert-primary {
border-left: 8px solid var(--mono-black) !important;
}
/* Status indicators */
.status_y {
background-color: #52c41a !important;
color: #fff !important;
border-color: #52c41a !important;
}
.status_n {
background-color: #fe4c61 !important;
color: #fff !important;
border-color: #fe4c61 !important;
}
.status_w {
background-color: #ffa900 !important;
color: #fff !important;
border-color: #ffa900 !important;
}
.test-case:hover {
border: 2px solid var(--mono-black) !important;
}
.software_list {
width: unset !important;
}
.software_item {
margin: 5px 10px !important;
background-color: var(--mono-gray-100) !important;
border: 1px solid var(--mono-gray-300) !important;
}
.software_item img {
width: 50px !important;
height: 50px !important;
object-fit: contain !important;
}
.item-txt {
color: var(--mono-black) !important;
}
.cnt-row {
justify-content: inherit;
align-items: stretch;
width: 100% !important;
padding: 1rem 0;
}
.cnt-row-head {
padding: 0.8em 1em;
background-color: var(--mono-black);
color: var(--mono-white);
width: 100%;
font-family: var(--mono-font-heading);
}
.cnt-row-head * {
color: var(--mono-white) !important;
}
.cnt-row-body {
padding: 1em;
border: 2px solid var(--mono-black);
border-top: none;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--mono-white);
}
::-webkit-scrollbar-thumb {
background: var(--mono-black);
}
/* Copy button in inverted headers */
.cnt-row-head .copy-btn, .card-header .copy-btn {
border-color: var(--mono-white) !important;
color: var(--mono-white) !important;
background-color: transparent !important;
}
.cnt-row-head .copy-btn:hover, .card-header .copy-btn:hover {
background-color: var(--mono-white) !important;
color: var(--mono-black) !important;
}
/* Problem switcher responsive */
@media (max-width: 768px) {
.problem-switcher-container {
display: none !important;
}
}
/* Contain images */
img {
max-width: 100% !important;
height: auto !important;
}
/* Hide blur overlay */
#blur-overlay { display: none !important; }`;
const NewBootstrapSkinCSS = `
nav {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
blockquote {
border-left: 5px solid var(--bs-secondary-bg);
padding: 0.5em 1em;
}
.status_y:hover {
box-shadow: #52c41a 1px 1px 10px 0px !important;
}
.status_n:hover {
box-shadow: #fe4c61 1px 1px 10px 0px !important;
}
.status_w:hover {
box-shadow: #ffa900 1px 1px 10px 0px !important;
}
.test-case {
border-radius: 5px !important;
}
.test-case:hover {
box-shadow: rgba(0, 0, 0, 0.3) 0px 10px 20px 3px !important;
}
.data[result-item] {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.software_list {
width: unset !important;
}
.software_item {
margin: 5px 10px !important;
background-color: var(--bs-secondary-bg) !important;
}
.item-txt {
color: var(--bs-emphasis-color) !important;
}
.cnt-row {
justify-content: inherit;
align-items: stretch;
width: 100% !important;
padding: 1rem 0;
}
.cnt-row-head {
padding: 0.8em 1em;
background-color: var(--bs-secondary-bg);
border-radius: 0.3rem 0.3rem 0 0;
width: 100%;
}
.cnt-row-body {
padding: 1em;
border: 1px solid var(--bs-secondary-bg);
border-top: none;
border-radius: 0 0 0.3rem 0.3rem;
}`;
// Paints the theme's page background on the canvas, with a matching color-scheme,
// so that while a page is hidden, and below short pages, a dark theme never shows
// the browser's white default. Uses the active skin's background variable.
const ThemeCanvasCSS = `
html[data-bs-theme='dark'] { background: var(--mono-white, var(--bs-body-bg, #1a1a1a)) !important; color-scheme: dark; }
html[data-bs-theme='light'] { background: var(--mono-white, var(--bs-body-bg, #fff)); color-scheme: light; }
`;
// Set to true by the early block if Bootstrap CSS was injected from the @resource
// cache. Checked in the IIFE to decide whether a CDN fallback is needed.
let _earlyBootstrapInjected = false;
// Shared between the early block and the IIFE so the IIFE can perform the
// reveal and observer teardown at DOMContentLoaded time (more reliable than
// adding a DOMContentLoaded listener inside the sandboxed early block).
let _foucStyle = null;
// Shows a page hidden by the early block. Called once the page reaches its styled
// state (navbar converted, top bar applied), with a safety delay as a fallback.
function RevealPage() {
if (_foucStyle) { _foucStyle.remove(); _foucStyle = null; }
}
let _earlyObs = null;
// Runs synchronously at document-start. When NewBootstrap is enabled, we apply
// the saved theme and inject Bootstrap CSS + the skin CSS before the first paint,
// and we block the page's own old stylesheets from loading at all.
(() => {
// Old contest pages: move to the /web app before anything is drawn.
const ContestWebRedirect = GetContestWebRedirect();
if (ContestWebRedirect) {
ApplyContestWebTheme();
const Hide = document.createElement("style");
Hide.textContent = "body { visibility: hidden !important; }";
(document.head || document.documentElement).appendChild(Hide);
location.replace(ContestWebRedirect);
return;
}
// /web gets the same Bootstrap 5 and skin setup below as the legacy pages.
if (IsContestWebApp()) ApplyContestWebTheme();
try {
let get = (k) => {
let v = localStorage.getItem("UserScript-Setting-" + k);
return v === null ? !["DebugMode", "SuperDebug", "ReplaceXM"].includes(k) : v === "true";
};
if (!get("NewBootstrap")) return;
let savedTheme = localStorage.getItem("UserScript-Setting-Theme") || "auto";
let dark = savedTheme === "auto"
? window.matchMedia("(prefers-color-scheme: dark)").matches
: savedTheme === "dark";
document.documentElement.setAttribute("data-bs-theme", dark ? "dark" : "light");
let head = document.head || document.documentElement;
let bootstrapCSS = GM_getResourceText("BootstrapCSS");
if (!bootstrapCSS) {
// @resource not cached yet (first install/update). Let the old stylesheets
// load normally and let the late CDN fallback in the IIFE handle Bootstrap.
return;
}
_earlyBootstrapInjected = true;
let bsStyle = document.createElement("style");
bsStyle.textContent = bootstrapCSS;
head.appendChild(bsStyle);
let isMono = get("MonochromeUI");
let skinCSS = isMono ? MonochromeSkinCSS : NewBootstrapSkinCSS;
if (get("AddAnimation")) skinCSS += `.status, .test-case { transition: ${isMono ? "100ms ease" : "0.5s"} !important; }`;
if (get("AddColorText")) skinCSS += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`;
skinCSS += ThemeCanvasCSS;
let skinStyle = document.createElement("style");
skinStyle.textContent = skinCSS;
head.appendChild(skinStyle);
// Hide the page until old stylesheets are evicted and the page is styled; see
// RevealPage. Only the body is hidden: opacity on the root would also hide the
// themed canvas and show the browser's white default instead. Opacity rather than
// visibility, because innerText skips invisible text and handlers read it before
// the reveal (for example the scores on status.php).
_foucStyle = document.createElement("style");
_foucStyle.textContent = "body { opacity: 0 !important; }";
head.appendChild(_foucStyle);
let blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"];
_earlyObs = new MutationObserver(mutations => {
for (let m of mutations)
for (let node of m.addedNodes)
if (node.tagName === "LINK" && blocked.some(h => node.href && node.href.indexOf(h) !== -1))
node.remove();
});
_earlyObs.observe(document.documentElement, { childList: true, subtree: true });
// The script can start after the parser has already read
: the observer
// only sees links added from now on, so remove the ones already there as well.
for (let link of document.querySelectorAll("link"))
if (blocked.some(h => link.href && link.href.indexOf(h) !== -1)) link.remove();
} catch (e) {
console.error("[XMOJ-Script] early init error:", e);
}
})();
const CaptchaSiteKey = "0x4AAAAAAALBT58IhyDViNmv";
// XMOJ turns on its own image captcha (vcode.php) whenever the judge queue is busy. submitpage.php
// renders the field at >10 pending solutions but submit.php only enforces it at >50, so the two can
// disagree; see GetCaptchaParameter below for how that gap is handled.
// vcode.php draws 4 digits with imagettftext(size 15, Vera.ttf) at a fixed origin, so every glyph is
// pixel identical between challenges and template matching reads them exactly. That beats sending the
// image to a vision model: llava-1.5-7b, llama-4-scout and mistral-small-3.1 were all measured at 0/7
// on real captchas even after preprocessing, and a confidently wrong guess is worse than an empty box
// because it burns the attempt and trips vfail. Glyphs below are Vera.ttf rendered at 20px, which is
// the 15px cap height GD produces; '#' would be ink, stored as 1 bits row by row.
const CaptchaGlyphs = {
"0": "001111000|011111110|011000110|111000111|110000011|110000011|110011011|110011011|110000011|110000011|110000011|111000111|011000110|011111110|001111000",
"1": "00111000|11111000|11011000|00011000|00011000|00011000|00011000|00011000|00011000|00011000|00011000|00011000|00011000|11111111|11111111",
"2": "011111100|111111110|100000111|000000011|000000011|000000011|000000111|000000110|000001100|000011000|000110000|001100000|011000000|111111111|111111111",
"3": "011111100|111111110|100000111|000000011|000000011|000000111|000111110|000111100|000000110|000000011|000000011|000000011|100000111|111111110|011111100",
"4": "0000011100|0000011100|0000111100|0000101100|0001101100|0001001100|0011001100|0110001100|0110001100|1100001100|1111111111|1111111111|0000001100|0000001100|0000001100",
"5": "011111110|011111110|011000000|011000000|011000000|011111100|011111110|010000111|000000011|000000011|000000011|000000011|100000110|111111110|011111000",
"6": "000111100|001111110|011100010|011000000|110000000|110000000|110111100|111111110|111000111|110000011|110000011|110000011|011000111|011111110|001111100",
"7": "111111111|111111111|000000110|000000110|000000110|000001100|000001100|000001100|000011000|000011000|000011000|000110000|000110000|000110000|001100000",
"8": "001111100|011111110|111000111|110000011|110000011|011000110|001111100|011111110|011000110|110000011|110000011|110000011|111000111|011111110|001111100",
"9": "001111100|011111110|111000110|110000011|110000011|110000011|111000111|011111111|001111011|000000011|000000011|000000110|010001110|011111100|001111000"
};
// How far ahead the best matching glyph has to score before its digit is trusted. Measured over 300
// generated and 40 live captchas, 4 is the point where every wrong answer turns into a decline: it
// still fills in about three quarters of them and never once guessed wrong.
const CaptchaMinMargin = 4;
// With about three quarters of challenges read, five images leave well under a 1% chance of giving up.
const CaptchaMaxAttempts = 5;
// Settings that start off rather than on. Both UtilityEnabled and the settings list seed missing
// values, so they have to agree or whichever runs first decides the default.
const DefaultOffSettings = ["DebugMode", "SuperDebug", "ReplaceXM"];
// 0.53.0 leaks its minified helper variables (m, r, o, ...) into the global scope from every
// chunk file, so whichever chunk happens to be evaluated last clobbers the others and the
// editor randomly fails to load (microsoft/monaco-editor#5015). 0.52.2 ships a single bundle
// and is not affected. cdnjs has no fixed release newer than 0.53.0 yet.
const MonacoCDN = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs";
const AdminUserList = ["zhuchenrui2", "shanwenxiao", "chenlangning", "admin"];
// Pre-declared so that closures defined before the async init block can reference them
let CurrentUsername;
let initTheme;
let SearchParams;
let escapeHTML = (str) => {
return str.replace(/[&<>"']/g, function (match) {
const escape = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return escape[match];
});
};
let PurifyHTML = (Input) => {
try {
return DOMPurify.sanitize(Input, {
"ALLOWED_TAGS": ["a", "b", "big", "blockquote", "br", "code", "dd", "del", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "hr", "i", "img", "ins", "kbd", "li", "ol", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "tt", "ul", "var"],
"ALLOWED_ATTR": ["abbr", "accept", "accept-charset", "accesskey", "action", "align", "alt", "axis", "border", "cellpadding", "cellspacing", "char", "charoff", "charset", "checked", "cite", "clear", "color", "cols", "colspan", "compact", "coords", "datetime", "dir", "disabled", "enctype", "for", "frame", "headers", "height", "href", "hreflang", "hspace", "ismap", "itemprop", "label", "lang", "longdesc", "maxlength", "media", "method", "multiple", "name", "nohref", "noshade", "nowrap", "prompt", "readonly", "rel", "rev", "rows", "rowspan", "rules", "scope", "selected", "shape", "size", "span", "src", "start", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "vspace", "width"]
});
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
let SmartAlert = (Message) => {
if (localStorage.getItem("UserScript-Alert") !== Message) {
alert(Message);
}
localStorage.setItem("UserScript-Alert", Message);
}
/**
* Calculates the relative time based on the input date.
* @param {string|Date} Input - The input date.
* @returns {string} The relative time in a formatted string.
*/
let GetRelativeTime = (Input) => {
try {
Input = new Date(parseInt(Input));
let Now = new Date().getTime();
let Delta = Now - Input.getTime();
let RelativeName = "";
if (Delta < 0) {
RelativeName = "未来";
} else if (Delta <= 1000 * 60) {
RelativeName = "刚刚";
} else if (Delta <= 1000 * 60 * 60) {
RelativeName = Math.floor((Now - Input) / 1000 / 60) + "分钟前";
} else if (Delta <= 1000 * 60 * 60 * 24) {
RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60) + "小时前";
} else if (Delta <= 1000 * 60 * 60 * 24 * 31) {
RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24) + "天前";
} else if (Delta <= 1000 * 60 * 60 * 24 * 365) {
RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 31) + "个月前";
} else {
RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 365) + "年前";
}
return "" + RelativeName + " ";
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
function compareVersions(currVer, remoteVer) {
const currParts = currVer.split('.').map(Number);
const remoteParts = remoteVer.split('.').map(Number);
const maxLen = Math.max(currParts.length, remoteParts.length);
for (let i = 0; i < maxLen; i++) {
const curr = currParts[i] !== undefined ? currParts[i] : 0;
const remote = remoteParts[i] !== undefined ? remoteParts[i] : 0;
if (remote > curr) {
return true; // update needed
} else if (remote < curr) {
return false; // no update needed
}
}
return false; // versions are equal
}
let RenderMathJax = async () => {
try {
if (document.getElementById("MathJax-script") === null) {
var ScriptElement = document.createElement("script");
ScriptElement.id = "MathJax-script";
ScriptElement.type = "text/javascript";
ScriptElement.src = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.0.5/es5/tex-chtml.js";
document.body.appendChild(ScriptElement);
await new Promise((Resolve) => {
ScriptElement.onload = () => {
Resolve();
};
});
}
if (MathJax !== undefined) { //If there is a Math expression
MathJax.startup.input[0].findTeX.options.inlineMath.push(["$", "$"]);
MathJax.startup.input[0].findTeX.getPatterns();
MathJax.typeset();
}
} catch (e) {
console.error(e);
}
};
let GetUserInfo = async (Username) => {
try {
if (localStorage.getItem("UserScript-User-" + Username + "-UserRating") != null && new Date().getTime() - parseInt(localStorage.getItem("UserScript-User-" + Username + "-LastUpdateTime")) < 1000 * 60 * 60 * 24) {
return {
"Rating": localStorage.getItem("UserScript-User-" + Username + "-UserRating"),
"EmailHash": localStorage.getItem("UserScript-User-" + Username + "-EmailHash")
}
}
return await fetch("https://www.xmoj.tech/userinfo.php?user=" + Username).then((Response) => {
return Response.text();
}).then((Response) => {
if (Response.indexOf("No such User!") !== -1) {
return null;
}
const ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
let Rating = (ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)") != null && ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)") != null) ? (parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)").innerText.trim()) / parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)").innerText.trim())).toFixed(3) * 1000 : 0;
let Temp = (ParsedDocument.querySelector("#statics > tbody") != null) ? ParsedDocument.querySelector("#statics > tbody").children : [];
//页面结构异常(如403/404页面)时返回默认值,不写入缓存
if (Temp.length == 0) {
return {
"Rating": Rating, "EmailHash": undefined
}
}
let Email = Temp[Temp.length - 1].children[1].innerText.trim();
let EmailHash = CryptoJS.MD5(Email).toString();
localStorage.setItem("UserScript-User-" + Username + "-UserRating", Rating);
if (Email == "") {
EmailHash = undefined;
} else {
localStorage.setItem("UserScript-User-" + Username + "-EmailHash", EmailHash);
}
localStorage.setItem("UserScript-User-" + Username + "-LastUpdateTime", new Date().getTime());
return {
"Rating": Rating, "EmailHash": EmailHash
}
});
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
/**
* Retrieves the badge information for a given user.
*
* @param {string} Username - The username of the user.
* @returns {Promise} - A promise that resolves to an object containing the badge information.
* @property {string} BackgroundColor - The background color of the badge.
* @property {string} Color - The color of the badge.
* @property {string} Content - The content of the badge.
*/
let GetUserBadge = async (Username) => {
try {
if (localStorage.getItem("UserScript-User-" + Username + "-Badge-LastUpdateTime") != null && new Date().getTime() - parseInt(localStorage.getItem("UserScript-User-" + Username + "-Badge-LastUpdateTime")) < 1000 * 60 * 60 * 24) {
return {
"BackgroundColor": localStorage.getItem("UserScript-User-" + Username + "-Badge-BackgroundColor"),
"Color": localStorage.getItem("UserScript-User-" + Username + "-Badge-Color"),
"Content": localStorage.getItem("UserScript-User-" + Username + "-Badge-Content")
}
} else {
let BackgroundColor = "";
let Color = "";
let Content = "";
await new Promise((Resolve) => {
RequestAPI("GetBadge", {
"UserID": String(Username)
}, (Response) => {
if (Response.Success) {
BackgroundColor = Response.Data.BackgroundColor;
Color = Response.Data.Color;
Content = Response.Data.Content;
}
Resolve();
});
});
localStorage.setItem("UserScript-User-" + Username + "-Badge-BackgroundColor", BackgroundColor);
localStorage.setItem("UserScript-User-" + Username + "-Badge-Color", Color);
localStorage.setItem("UserScript-User-" + Username + "-Badge-Content", Content);
localStorage.setItem("UserScript-User-" + Username + "-Badge-LastUpdateTime", String(new Date().getTime()));
return {
"BackgroundColor": BackgroundColor, "Color": Color, "Content": Content
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
let MonacoLoadPromise = null;
async function ensureMonaco() {
if (typeof monaco !== 'undefined') return;
// Callers can overlap (submit page editor, merge view, freopen snippets). Without this guard
// each of them appends its own loader.js, and the second loader resets the AMD registry while
// the first one is still resolving modules.
if (MonacoLoadPromise === null) {
MonacoLoadPromise = loadMonaco();
MonacoLoadPromise.catch(() => { MonacoLoadPromise = null; });
}
return MonacoLoadPromise;
}
async function loadMonaco() {
const loaderUrl = MonacoCDN + '/loader.js';
if (typeof require === 'undefined' || typeof require.config === 'undefined') {
await new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = loaderUrl;
s.onload = () => {
try { require.config({ paths: { vs: MonacoCDN } }); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
resolve();
};
s.onerror = reject;
document.head.appendChild(s);
});
} else {
try { require.config({ paths: { vs: MonacoCDN } }); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
await new Promise((resolve, reject) => {
let check = null;
const done = () => { if (check) clearInterval(check); clearTimeout(timeout); resolve(); };
const timeout = setTimeout(() => { if (check) clearInterval(check); reject(new Error('Monaco load timeout')); }, 30000);
try {
require(['vs/editor/editor.main'], function() { done(); });
} catch (e) {
check = setInterval(() => { if (typeof monaco !== 'undefined') done(); }, 50);
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
});
}
async function createMonacoEditor(containerOrId, options = {}) {
await ensureMonaco();
let container = null;
if (typeof containerOrId === 'string') container = document.getElementById(containerOrId);
else container = containerOrId;
if (!container) throw new Error('Monaco container not found');
if (!container.id) container.id = 'monaco-' + Math.random().toString(36).slice(2,9);
// Auto-fit settings: when true Monaco will fill width and height from the page header to window bottom
let autoFitEnabled = !!options.fitToViewport;
let _autoFitHandler = null;
let innerHost = null;
const computeAvailableHeight = () => {
try {
const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header');
const top = header ? header.getBoundingClientRect().bottom : 0;
const bottomOffset = Number(options.bottomOffset || 0);
const available = Math.max(80, window.innerHeight - top - bottomOffset);
return available;
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
return null;
}
};
const applyAutoFit = (callLayout, ed) => {
try {
if (!autoFitEnabled) return;
const available = computeAvailableHeight();
if (available != null) {
try {
container.style.width = options.width || '100%';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'center';
container.style.boxSizing = 'border-box';
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try { container.style.height = available + 'px'; } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
if (innerHost) {
try {
const innerRatio = (typeof options.innerRatio !== 'undefined') ? Number(options.innerRatio) : 0.95;
const wrapperHeight = Math.max(200, Math.min(Math.floor(available * innerRatio), available));
innerHost.style.height = wrapperHeight + 'px';
innerHost.style.width = options.width || '95%';
if (options.maxWidth) innerHost.style.maxWidth = String(options.maxWidth);
innerHost.style.boxSizing = 'border-box';
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
if (callLayout) try { if (ed && ed.layout) ed.layout(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
const key = options.localStorageKey || ('XMOJ-Monaco-' + location.pathname + ':' + container.id);
const theme = options.theme || (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs');
const readOnly = !!options.readOnly;
const language = options.language || (options.mode === 'text/x-c++src' ? 'cpp' : (options.mode || 'cpp'));
// create an inner host so we can center the editor both horizontally and vertically
try {
innerHost = document.createElement('div');
innerHost.className = 'monaco-editor-host';
innerHost.style.boxSizing = 'border-box';
// default sizing; will be recalculated by applyAutoFit when enabled
innerHost.style.width = options.width || (autoFitEnabled ? '95%' : '100%');
if (!autoFitEnabled) {
if (options.height) innerHost.style.height = (typeof options.height === 'number' ? options.height + 'px' : options.height);
}
container.appendChild(innerHost);
} catch (e) { console.error(e); }
const editor = monaco.editor.create(innerHost || container, {
value: options.value || '',
language: language,
automaticLayout: !!options.automaticLayout,
theme: theme,
minimap: (typeof options.minimap !== 'undefined' ? options.minimap : { enabled: false }),
readOnly: readOnly,
lineNumbers: typeof options.lineNumbers !== 'undefined' ? options.lineNumbers : 'on',
tabSize: options.tabSize || 4
});
// apply initial auto-fit (no layout call yet because editor is not fully initialized until after creation)
try { applyAutoFit(false, editor); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
// after creation, ensure Monaco layout matches the computed size and listen for viewport changes
try {
applyAutoFit(true, editor);
if (autoFitEnabled && typeof window !== 'undefined') {
_autoFitHandler = () => applyAutoFit(true, editor);
window.addEventListener('resize', _autoFitHandler);
window.addEventListener('orientationchange', _autoFitHandler);
window.addEventListener('scroll', _autoFitHandler);
try { editor.onDidDispose(() => { window.removeEventListener('resize', _autoFitHandler); window.removeEventListener('orientationchange', _autoFitHandler); window.removeEventListener('scroll', _autoFitHandler); }); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try {
if (options.restoreOnLoad !== false) {
const saved = localStorage.getItem(key);
if (saved !== null && saved !== 'null') editor.setValue(saved);
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
let saveTimer = null;
const doSave = () => { try { localStorage.setItem(key, editor.getValue()); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} };
editor.onDidChangeModelContent(() => { if (saveTimer) clearTimeout(saveTimer); saveTimer = setTimeout(doSave, options.saveDebounce || 500); });
const adapter = {
getValue: () => editor.getValue(),
setValue: (v) => { editor.setValue(v); },
setSize: (w, h) => { const el = innerHost || container; if (w) el.style.width = w; if (h) { try { if (h !== 'auto') autoFitEnabled = false; } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} if (h === 'auto') { try { const lines = editor.getModel().getLineCount(); el.style.height = Math.max(80, Math.min(1200, lines * 18)) + 'px'; } catch (e) { el.style.height = '80px'; } } else el.style.height = h; } try { editor.layout(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
getWrapperElement: () => innerHost || container,
focus: () => { try { editor.focus(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
_monacoEditor: editor,
showFind: () => { try { editor.trigger('', 'editor.action.startFindReplaceAction'); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
goToLine: (line) => { try { editor.setPosition({ lineNumber: parseInt(line) || 1, column: 1 }); editor.revealPositionInCenter({ lineNumber: parseInt(line) || 1, column: 1 }); editor.focus(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
selectRange: (sLine, sCol, eLine, eCol) => { try { editor.setSelection({ startLineNumber: sLine, startColumn: sCol, endLineNumber: eLine, endColumn: eCol }); editor.revealRangeInCenter({ startLineNumber: sLine, startColumn: sCol, endLineNumber: eLine, endColumn: eCol }); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
saveToLocal: doSave,
localStorageKey: key
,
dispose: () => {
try {
if (_autoFitHandler && typeof window !== 'undefined') {
window.removeEventListener('resize', _autoFitHandler);
window.removeEventListener('orientationchange', _autoFitHandler);
window.removeEventListener('scroll', _autoFitHandler);
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try {
if (editor) {
try {
const model = (editor.getModel && editor.getModel()) || null;
if (model && model.dispose) model.dispose();
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try { editor.dispose(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
};
return adapter;
}
(function() {
const shim = function(containerOrTextArea, options) {
let container = containerOrTextArea;
let initialValue = '';
if (container && container.tagName && container.tagName.toLowerCase() === 'textarea') {
initialValue = container.value || container.textContent || '';
const div = document.createElement('div');
// copy most attributes from the original textarea to the replacement div (except value)
try {
for (let i = 0; i < container.attributes.length; i++) {
const a = container.attributes[i];
if (!a) continue;
if (a.name === 'value') continue;
try { div.setAttribute(a.name, a.value); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
div.classList.add('codemirror-shim-host');
// preserve inline styles if any
try { if (container.style && container.style.cssText) div.style.cssText = container.style.cssText; } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
container.parentNode.replaceChild(div, container);
container = div;
} else if (typeof containerOrTextArea === 'string') {
container = document.querySelector(containerOrTextArea) || document.getElementById(containerOrTextArea);
}
if (!container) { container = document.createElement('div'); document.body.appendChild(container); }
container._cmValue = (options && options.value) ? options.value : initialValue;
// show a centered Loading... placeholder while Monaco initializes
try {
const _monacoLoadingEl = document.createElement('div');
_monacoLoadingEl.className = 'monaco-loading';
_monacoLoadingEl.style.width = '100%';
_monacoLoadingEl.style.height = '100%';
_monacoLoadingEl.style.display = 'flex';
_monacoLoadingEl.style.alignItems = 'center';
_monacoLoadingEl.style.justifyContent = 'center';
_monacoLoadingEl.textContent = 'Loading...';
container.appendChild(_monacoLoadingEl);
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
let _lastSetSizeArgs = null;
const placeholderAdapter = {
getValue: () => container._cmValue || '',
setValue: (v) => { container._cmValue = v; if (container._cmEditor) try { container._cmEditor.setValue(v); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
setSize: (w, h) => { _lastSetSizeArgs = [w, h]; if (w) container.style.width = w; if (h) { if (h === 'auto') container.style.height = 'auto'; else container.style.height = h; } if (container._cmEditor) try { container._cmEditor.layout(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
getWrapperElement: () => container,
focus: () => { if (container._cmEditor) try { container._cmEditor.focus(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
} },
_monacoEditor: null
};
(async () => {
try {
const opts = options || {};
await ensureMonaco();
const lang = opts.mode === 'text/x-c++src' || (opts.mode && opts.mode.indexOf('c++') !== -1) ? 'cpp' : (opts.language || 'cpp');
const monacoAdapter = await createMonacoEditor(container, Object.assign({ language: lang, value: container._cmValue || '', readOnly: !!opts.readOnly, theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs') }, opts));
container._cmEditor = monacoAdapter._monacoEditor;
placeholderAdapter.getValue = monacoAdapter.getValue;
placeholderAdapter.setValue = monacoAdapter.setValue;
placeholderAdapter.setSize = monacoAdapter.setSize;
placeholderAdapter.getWrapperElement = monacoAdapter.getWrapperElement;
placeholderAdapter.focus = monacoAdapter.focus;
placeholderAdapter._monacoEditor = monacoAdapter._monacoEditor;
try { const _l = container.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
if (_lastSetSizeArgs) monacoAdapter.setSize(_lastSetSizeArgs[0], _lastSetSizeArgs[1]);
} catch (e) { console.error(e); try { const _l = container.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (_) {} }
})();
return placeholderAdapter;
};
shim.fromTextArea = function(textarea, options) { return shim(textarea, options); };
shim.MergeView = function(container, options) {
options = options || {};
let el = container;
if (typeof container === 'string') el = document.getElementById(container) || document.querySelector(container);
if (!el) { el = document.createElement('div'); document.body.appendChild(el); }
// show a centered Loading... placeholder while Monaco initializes
try {
const _mergeLoadingEl = document.createElement('div');
_mergeLoadingEl.className = 'monaco-loading';
_mergeLoadingEl.style.width = '100%';
_mergeLoadingEl.style.height = '100%';
_mergeLoadingEl.style.display = 'flex';
_mergeLoadingEl.style.alignItems = 'center';
_mergeLoadingEl.style.justifyContent = 'center';
_mergeLoadingEl.textContent = 'Loading...';
el.appendChild(_mergeLoadingEl);
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
const wrapper = { ignoreWhitespace: !!options.ignoreWhitespace, _diffEditor: null, _originalModel: null, _modifiedModel: null };
(async () => {
try {
await ensureMonaco();
// create an inner host so the diff editor can be centered if requested
let mvInner = null;
try {
mvInner = document.createElement('div');
mvInner.className = 'monaco-merge-host';
mvInner.style.boxSizing = 'border-box';
mvInner.style.width = options.width || '95%';
if (options.fitToViewport) {
try { el.style.display = 'flex'; el.style.alignItems = 'center'; el.style.justifyContent = 'center'; } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header');
const top = header ? header.getBoundingClientRect().bottom : 0;
const bottomOffset = Number(options.bottomOffset || 0);
const available = Math.max(80, window.innerHeight - top - bottomOffset);
const wrapperHeight = Math.max(200, Math.min(Math.floor(available * 0.95), available));
mvInner.style.height = wrapperHeight + 'px';
} else {
if (options.height) mvInner.style.height = (typeof options.height === 'number' ? options.height + 'px' : options.height);
}
el.appendChild(mvInner);
} catch (e) { mvInner = null; }
const diffEditor = monaco.editor.createDiffEditor(mvInner || el, { readOnly: !!options.readOnly, theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'), minimap: { enabled: false }, automaticLayout: true, ignoreTrimWhitespace: !!wrapper.ignoreWhitespace });
const orig = options.value || '';
const mod = options.orig || '';
const isCpp = options.mode === 'text/x-c++src' || (typeof options.mode === 'string' && options.mode.indexOf('c++') !== -1);
const lang = isCpp ? 'cpp' : (options.language || 'cpp');
const originalModel = monaco.editor.createModel(orig, lang);
const modifiedModel = monaco.editor.createModel(mod, lang);
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
wrapper._diffEditor = diffEditor;
wrapper._originalModel = originalModel;
wrapper._modifiedModel = modifiedModel;
try { const _l = el.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
} catch (e) { console.error(e); try { const _l = el.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (_) {} }
})();
return wrapper;
};
window.CodeMirror = shim;
})();
/**
* Dispose any transient Monaco editors created inside the ErrorMessage area (freopen snippets).
*/
function _xmoj_disposeErrorMessageEditors() {
try {
const arr = window._xmoj_temp_error_editors || [];
if (arr.length) {
arr.forEach((ed) => {
try {
const m = (ed && ed.getModel) ? ed.getModel() : null;
if (m && m.dispose) try { m.dispose(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try { if (ed && ed.dispose) ed.dispose(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
});
}
window._xmoj_temp_error_editors = [];
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try {
const hosts = document.querySelectorAll('[data-xmoj-error-editor]');
hosts.forEach((h) => {
try { if (h._monacoEditor && h._monacoEditor.dispose) h._monacoEditor.dispose(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try { delete h._monacoEditor; } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
try { h.removeAttribute('data-xmoj-error-editor'); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
});
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
/**
* Sets the HTML content of an element to display a username with optional additional information.
* @param {HTMLElement} Element - The element to set the HTML content.
* @param {string} Username - The username to display.
* @param {boolean} [Simple=false] - Indicates whether to display additional information or not.
* @param {string} [Href="https://www.xmoj.tech/userinfo.php?user="] - The URL to link the username to.
* @returns {Promise} - A promise that resolves when the HTML content is set.
*/
let GetUsernameHTML = async (Element, Username, Simple = false, Href = "https://www.xmoj.tech/userinfo.php?user=") => {
try {
//Username = Username.replaceAll(/[^a-zA-Z0-9]/g, "");
let ID = "Username-" + Username + "-" + Math.random();
Element.id = ID;
Element.innerHTML = `
`;
Element.appendChild(document.createTextNode(Username));
let UserInfo = await GetUserInfo(Username);
if (UserInfo === null) {
document.getElementById(ID).innerHTML = "";
document.getElementById(ID).appendChild(document.createTextNode(Username));
return;
}
let HTMLData = "";
if (!Simple) {
HTMLData += ` `;
}
HTMLData += ` 500) {
HTMLData += "link-danger";
} else if (Rating >= 400) {
HTMLData += "link-warning";
} else if (Rating >= 300) {
HTMLData += "link-success";
} else {
HTMLData += "link-info";
}
} else {
HTMLData += "link-info";
}
HTMLData += `\";"> `;
if (!Simple) {
if (AdminUserList.includes(Username)) {
HTMLData += `脚本管理员 `;
}
let BadgeInfo = await GetUserBadge(Username);
if (BadgeInfo.Content != "") {
HTMLData += `${BadgeInfo.Content} `;
}
}
if (document.getElementById(ID) !== null) {
document.getElementById(ID).innerHTML = HTMLData;
document.getElementById(ID).getElementsByTagName("a")[0].appendChild(document.createTextNode(Username));
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
/**
* Converts the given number of seconds to a formatted string representation of hours, minutes, and seconds.
* @param {number} InputSeconds - The number of seconds to convert.
* @returns {string} The formatted string representation of the input seconds.
*/
let SecondsToString = (InputSeconds) => {
try {
let Hours = Math.floor(InputSeconds / 3600);
let Minutes = Math.floor((InputSeconds % 3600) / 60);
let Seconds = InputSeconds % 60;
return (Hours < 10 ? "0" : "") + Hours + ":" + (Minutes < 10 ? "0" : "") + Minutes + ":" + (Seconds < 10 ? "0" : "") + Seconds;
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
/**
* Converts a string in the format "hh:mm:ss" to the equivalent number of seconds.
* @param {string} InputString - The input string to convert.
* @returns {number} The number of seconds equivalent to the input string.
*/
let StringToSeconds = (InputString) => {
try {
let SplittedString = InputString.split(":");
return parseInt(SplittedString[0]) * 60 * 60 + parseInt(SplittedString[1]) * 60 + parseInt(SplittedString[2]);
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
/**
* Converts a memory size in bytes to a human-readable string representation.
* @param {number} Memory - The memory size in bytes.
* @returns {string} The human-readable string representation of the memory size.
*/
let SizeToStringSize = (Memory) => {
try {
if (UtilityEnabled("AddUnits")) {
if (Memory < 1024) {
return Memory + "KB";
} else if (Memory < 1024 * 1024) {
return (Memory / 1024).toFixed(2) + "MB";
} else if (Memory < 1024 * 1024 * 1024) {
return (Memory / 1024 / 1024).toFixed(2) + "GB";
} else {
return (Memory / 1024 / 1024 / 1024).toFixed(2) + "TB";
}
} else {
return Memory;
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
let CodeSizeToStringSize = (Memory) => {
try {
if (UtilityEnabled("AddUnits")) {
if (Memory < 1024) {
return Memory + "B";
} else if (Memory < 1024 * 1024) {
return (Memory / 1024).toFixed(2) + "KB";
} else if (Memory < 1024 * 1024 * 1024) {
return (Memory / 1024 / 1024).toFixed(2) + "MB";
} else {
return (Memory / 1024 / 1024 / 1024).toFixed(2) + "GB";
}
} else {
return Memory;
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
/**
* Converts a time value to a string representation.
* @param {number} Time - The time value to convert.
* @returns {string|number} - The converted time value as a string, or the original value if UtilityEnabled("AddUnits") is false.
*/
let TimeToStringTime = (Time) => {
try {
if (UtilityEnabled("AddUnits")) {
if (Time < 1000) {
return Time + "ms";
} else if (Time < 1000 * 60) {
return (Time / 1000).toFixed(2) + "s";
}
} else {
return Time;
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
/**
* Tidies up the given table by applying Bootstrap styling and removing unnecessary attributes.
*
* @param {HTMLElement} Table - The table element to be tidied up.
*/
let TidyTable = (Table) => {
try {
if (UtilityEnabled("NewBootstrap") && Table != null) {
Table.className = "table table-hover";
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
let UtilityEnabled = (Name) => {
try {
if (localStorage.getItem("UserScript-Setting-" + Name) == null) {
localStorage.setItem("UserScript-Setting-" + Name, DefaultOffSettings.includes(Name) ? "false" : "true");
}
return localStorage.getItem("UserScript-Setting-" + Name) == "true";
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
let storeCredential = async (username, password) => {
if ('credentials' in navigator && window.PasswordCredential) {
try {
const credential = new PasswordCredential({id: username, password: password});
await navigator.credentials.store(credential);
} catch (e) {
console.error(e);
}
}
};
let getCredential = async () => {
if ('credentials' in navigator && window.PasswordCredential) {
try {
return await navigator.credentials.get({password: true, mediation: 'optional'});
} catch (e) {
console.error(e);
}
}
return null;
};
let clearCredential = async () => {
if ('credentials' in navigator && window.PasswordCredential) {
try {
await navigator.credentials.preventSilentAccess();
} catch (e) {
console.error(e);
}
}
};
let RequestAPI = (Action, Data, CallBack) => {
try {
let Session = "";
let Temp = document.cookie.split(";");
for (let i = 0; i < Temp.length; i++) {
if (Temp[i].includes("PHPSESSID")) {
Session = Temp[i].split("=")[1];
}
}
if (Session === "") { //The cookie is httpOnly
GM.cookie.set({
name: 'PHPSESSID',
value: (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).substring(0, 28),
path: "/"
})
.then(() => {
console.log('Reset PHPSESSID successfully.');
location.reload(); //Refresh the page to auth with the new PHPSESSID
})
.catch((error) => {
console.error(error);
});
}
let PostData = {
"Authentication": {
"SessionID": Session, "Username": CurrentUsername,
}, "Data": Data, "Version": GM_info.script.version, "DebugMode": UtilityEnabled("DebugMode")
};
let DataString = JSON.stringify(PostData);
if (UtilityEnabled("DebugMode")) {
console.log("Sent for", Action + ":", DataString);
}
GM_xmlhttpRequest({
method: "POST",
url: (UtilityEnabled("SuperDebug") ? "http://127.0.0.1:8787/" : "https://api.xmoj-script.uk/") + Action,
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
"XMOJ-UserID": CurrentUsername,
"XMOJ-Script-Version": GM_info.script.version,
"DebugMode": UtilityEnabled("DebugMode")
},
data: DataString,
onload: (Response) => {
if (UtilityEnabled("DebugMode")) {
console.log("Received for", Action + ":", Response.responseText);
}
try {
CallBack(JSON.parse(Response.responseText));
} catch (Error) {
console.log(Response.responseText);
}
}
});
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
};
let SyncSettingsToCloud = (CallBack) => {
if (!CurrentUsername) {
if (CallBack) CallBack({ Success: false, Message: "用户未登录" });
return;
}
if (!UtilityEnabled("CloudSync")) {
if (CallBack) CallBack({ Success: false, Message: "云同步已禁用" });
return;
}
let Settings = {};
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (key && key.startsWith("UserScript-Setting-")) {
Settings[key.replace("UserScript-Setting-", "")] = localStorage.getItem(key);
}
}
RequestAPI("SetUserSettings", {"Settings": JSON.stringify(Settings)}, (Response) => {
if (UtilityEnabled("DebugMode")) {
if (Response.Success) {
console.log("设置已同步到云端");
} else {
console.error("设置云端同步失败:", Response.Message);
}
}
if (CallBack) CallBack(Response);
});
};
let PeriodicCloudSync = () => {
if (!CurrentUsername || !UtilityEnabled("CloudSync")) return;
const lastSync = parseInt(localStorage.getItem("UserScript-CloudSync-LastSync") || "0");
if (Date.now() - lastSync < 60 * 60 * 1000) return;
RequestAPI("GetUserSettings", {}, (Response) => {
if (Response.Success) {
localStorage.setItem("UserScript-CloudSync-LastSync", String(Date.now()));
const cloudSettings = (Response.Data && Response.Data.Settings) || {};
if (Object.keys(cloudSettings).length > 0) {
let themeChanged = false;
for (let key in cloudSettings) {
const rawValue = String(cloudSettings[key]);
const localKey = "UserScript-Setting-" + key;
if (localStorage.getItem(localKey) !== rawValue) {
localStorage.setItem(localKey, rawValue);
if (key === "Theme") themeChanged = true;
}
}
if (themeChanged) initTheme();
}
SyncSettingsToCloud();
}
});
};
// WebSocket Notification System
let NotificationSocket = null;
let NotificationSocketReconnectAttempts = 0;
let NotificationSocketReconnectDelay = 1000;
let NotificationSocketPingInterval = null;
let NotificationSocketReconnectTimer = null;
function GetPHPSESSID() {
let Session = "";
let Temp = document.cookie.split(";");
for (let i = 0; i < Temp.length; i++) {
if (Temp[i].includes("PHPSESSID")) {
Session = Temp[i].split("=")[1];
break;
}
}
return Session;
}
function ConnectNotificationSocket() {
try {
// Clear any pending reconnection timer to prevent duplicate connections
if (NotificationSocketReconnectTimer) {
clearTimeout(NotificationSocketReconnectTimer);
NotificationSocketReconnectTimer = null;
}
let Session = GetPHPSESSID();
if (Session === "") {
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: PHPSESSID not available, skipping connection");
}
return;
}
let wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-script.uk") + "/ws/notifications?SessionID=" + Session;
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Connecting to", wsUrl);
}
NotificationSocket = new WebSocket(wsUrl);
NotificationSocket.onopen = () => {
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Connected successfully");
}
NotificationSocketReconnectAttempts = 0;
NotificationSocketReconnectDelay = 1000;
// Start ping keepalive
if (NotificationSocketPingInterval) {
clearInterval(NotificationSocketPingInterval);
}
NotificationSocketPingInterval = setInterval(() => {
if (NotificationSocket && NotificationSocket.readyState === WebSocket.OPEN) {
NotificationSocket.send(JSON.stringify({ type: 'ping' }));
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Sent ping");
}
} else {
clearInterval(NotificationSocketPingInterval);
}
}, 30000);
};
NotificationSocket.onmessage = (event) => {
HandleNotificationMessage(event);
};
NotificationSocket.onerror = (error) => {
if (UtilityEnabled("DebugMode")) {
console.error("WebSocket: Error", error);
}
};
NotificationSocket.onclose = (event) => {
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Connection closed", event.code, event.reason);
}
if (NotificationSocketPingInterval) {
clearInterval(NotificationSocketPingInterval);
}
ReconnectNotificationSocket();
};
} catch (e) {
console.error("WebSocket: Failed to connect", e);
ReconnectNotificationSocket();
}
}
function ReconnectNotificationSocket() {
const delay = Math.min(NotificationSocketReconnectDelay * Math.pow(2, NotificationSocketReconnectAttempts), 30000);
NotificationSocketReconnectAttempts++;
if (UtilityEnabled("DebugMode")) {
console.log(`WebSocket: Reconnecting in ${delay}ms (attempt ${NotificationSocketReconnectAttempts})`);
}
NotificationSocketReconnectTimer = setTimeout(() => {
ConnectNotificationSocket();
}, delay);
}
function HandleNotificationMessage(event) {
try {
const notification = JSON.parse(event.data);
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Received message", notification);
}
if (notification.type === 'connected') {
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Server confirmed connection at timestamp", notification.timestamp);
}
} else if (notification.type === 'bbs_mention') {
if (UtilityEnabled("BBSPopup")) {
CreateAndShowBBSMentionToast(notification.data);
}
} else if (notification.type === 'mail_mention') {
if (UtilityEnabled("MessagePopup")) {
CreateAndShowMailMentionToast(notification.data);
}
} else if (notification.type === 'pong') {
if (UtilityEnabled("DebugMode")) {
console.log("WebSocket: Received pong");
}
}
} catch (e) {
console.error("WebSocket: Failed to handle message", e);
}
}
function CreateAndShowBBSMentionToast(mention) {
let ToastContainer = document.querySelector(".toast-container");
if (!ToastContainer) return;
let Toast = document.createElement("div");
Toast.classList.add("toast");
Toast.setAttribute("role", "alert");
let ToastHeader = document.createElement("div");
ToastHeader.classList.add("toast-header");
let ToastTitle = document.createElement("strong");
ToastTitle.classList.add("me-auto");
ToastTitle.innerHTML = "提醒:有人@你";
ToastHeader.appendChild(ToastTitle);
let ToastTime = document.createElement("small");
ToastTime.classList.add("text-body-secondary");
ToastTime.innerHTML = GetRelativeTime(mention.MentionTime);
ToastHeader.appendChild(ToastTime);
let ToastCloseButton = document.createElement("button");
ToastCloseButton.type = "button";
ToastCloseButton.classList.add("btn-close");
ToastCloseButton.setAttribute("data-bs-dismiss", "toast");
ToastHeader.appendChild(ToastCloseButton);
Toast.appendChild(ToastHeader);
let ToastBody = document.createElement("div");
ToastBody.classList.add("toast-body");
ToastBody.innerHTML = "讨论" + escapeHTML(mention.PostTitle) + "有新回复";
let ToastFooter = document.createElement("div");
ToastFooter.classList.add("mt-2", "pt-2", "border-top");
let ToastDismissButton = document.createElement("button");
ToastDismissButton.type = "button";
ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2");
ToastDismissButton.innerText = "忽略";
ToastDismissButton.addEventListener("click", () => {
RequestAPI("ReadBBSMention", {
"MentionID": Number(mention.MentionID)
}, () => {
});
Toast.remove();
});
ToastFooter.appendChild(ToastDismissButton);
let ToastViewButton = document.createElement("button");
ToastViewButton.type = "button";
ToastViewButton.classList.add("btn", "btn-primary", "btn-sm");
ToastViewButton.innerText = "查看";
ToastViewButton.addEventListener("click", () => {
open("https://www.xmoj.tech/discuss3/thread.php?tid=" + mention.PostID + '&page=' + mention.PageNumber, "_blank");
RequestAPI("ReadBBSMention", {
"MentionID": Number(mention.MentionID)
}, () => {
});
});
ToastFooter.appendChild(ToastViewButton);
ToastBody.appendChild(ToastFooter);
Toast.appendChild(ToastBody);
ToastContainer.appendChild(Toast);
new bootstrap.Toast(Toast).show();
}
function CreateAndShowMailMentionToast(mention) {
let ToastContainer = document.querySelector(".toast-container");
if (!ToastContainer) return;
let Toast = document.createElement("div");
Toast.classList.add("toast");
Toast.setAttribute("role", "alert");
let ToastHeader = document.createElement("div");
ToastHeader.classList.add("toast-header");
let ToastTitle = document.createElement("strong");
ToastTitle.classList.add("me-auto");
ToastTitle.innerHTML = "提醒:有新消息";
ToastHeader.appendChild(ToastTitle);
let ToastTime = document.createElement("small");
ToastTime.classList.add("text-body-secondary");
ToastTime.innerHTML = GetRelativeTime(mention.MentionTime);
ToastHeader.appendChild(ToastTime);
let ToastCloseButton = document.createElement("button");
ToastCloseButton.type = "button";
ToastCloseButton.classList.add("btn-close");
ToastCloseButton.setAttribute("data-bs-dismiss", "toast");
ToastHeader.appendChild(ToastCloseButton);
Toast.appendChild(ToastHeader);
let ToastBody = document.createElement("div");
ToastBody.classList.add("toast-body");
let ToastUser = document.createElement("span");
GetUsernameHTML(ToastUser, mention.FromUserID);
ToastBody.appendChild(ToastUser);
ToastBody.appendChild(document.createTextNode(" 给你发了一封短消息"));
let ToastFooter = document.createElement("div");
ToastFooter.classList.add("mt-2", "pt-2", "border-top");
let ToastDismissButton = document.createElement("button");
ToastDismissButton.type = "button";
ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2");
ToastDismissButton.setAttribute("data-bs-dismiss", "toast");
ToastDismissButton.innerText = "忽略";
ToastDismissButton.addEventListener("click", () => {
RequestAPI("ReadMailMention", {
"MentionID": Number(mention.MentionID)
}, () => {
});
});
ToastFooter.appendChild(ToastDismissButton);
let ToastViewButton = document.createElement("button");
ToastViewButton.type = "button";
ToastViewButton.classList.add("btn", "btn-primary", "btn-sm");
ToastViewButton.innerText = "查看";
ToastViewButton.addEventListener("click", () => {
open("https://www.xmoj.tech/mail.php?to_user=" + mention.FromUserID, "_blank");
RequestAPI("ReadMailMention", {
"MentionID": Number(mention.MentionID)
}, () => {
});
});
ToastFooter.appendChild(ToastViewButton);
ToastBody.appendChild(ToastFooter);
Toast.appendChild(ToastBody);
ToastContainer.appendChild(Toast);
new bootstrap.Toast(Toast).show();
}
function PollNotifications() {
// Clear toast container once before fetching to prevent race condition
if (UtilityEnabled("BBSPopup") || UtilityEnabled("MessagePopup")) {
let ToastContainer = document.querySelector(".toast-container");
if (ToastContainer) {
ToastContainer.innerHTML = "";
}
}
if (UtilityEnabled("BBSPopup")) {
RequestAPI("GetBBSMentionList", {}, (Response) => {
if (Response.Success) {
let MentionList = Response.Data.MentionList;
for (let i = 0; i < MentionList.length; i++) {
CreateAndShowBBSMentionToast(MentionList[i]);
}
}
});
}
if (UtilityEnabled("MessagePopup")) {
RequestAPI("GetMailMentionList", {}, (Response) => {
if (Response.Success) {
let MentionList = Response.Data.MentionList;
for (let i = 0; i < MentionList.length; i++) {
CreateAndShowMailMentionToast(MentionList[i]);
}
}
});
}
}
GM_registerMenuCommand("清除缓存", () => {
let Temp = [];
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i).startsWith("UserScript-User-")) {
Temp.push(localStorage.key(i));
}
}
for (let i = 0; i < Temp.length; i++) {
localStorage.removeItem(Temp[i]);
}
location.reload();
});
GM_registerMenuCommand("重置数据", () => {
if (confirm("确定要重置数据吗?")) {
localStorage.clear();
location.reload();
}
});
// The contest problem switcher, fixed at the left edge, as on the old contest
// problem pages. Labels come from each problem's URL, since the list order does not
// have to follow the problem letters.
function CreateProblemSwitcher(ProblemList, IsCurrent) {
let problemSwitcher = document.createElement("div");
problemSwitcher.classList.add("problem-switcher-container");
problemSwitcher.style.position = "fixed";
problemSwitcher.style.top = "50%";
problemSwitcher.style.left = "0";
problemSwitcher.style.transform = "translateY(-50%)";
problemSwitcher.style.maxHeight = "80vh";
problemSwitcher.style.overflowY = "auto";
if (document.documentElement.getAttribute("data-bs-theme") == "dark") {
problemSwitcher.style.backgroundColor = UtilityEnabled("MonochromeUI") ? "#000" : "rgba(0, 0, 0, 0.8)";
} else {
problemSwitcher.style.backgroundColor = UtilityEnabled("MonochromeUI") ? "#FFF" : "rgba(255, 255, 255, 0.8)";
}
problemSwitcher.style.padding = "10px";
problemSwitcher.style.borderRadius = UtilityEnabled("MonochromeUI") ? "0" : "0 10px 10px 0";
if (UtilityEnabled("MonochromeUI")) problemSwitcher.style.borderRight = "4px solid";
problemSwitcher.style.display = "flex";
problemSwitcher.style.flexDirection = "column";
problemSwitcher.style.zIndex = "990";
for (let i = 0; i < ProblemList.length; i++) {
let Label = GetContestRoute(ProblemList[i].url)?.num;
if (!Label) Label = i < 26 ? String.fromCharCode(65 + i) : String.fromCharCode(97 + (i - 26));
let Button = document.createElement("a");
Button.href = ProblemList[i].url;
Button.title = String(ProblemList[i].title || "").trim();
Button.className = "btn btn-outline-secondary mb-2" + (IsCurrent(i, ProblemList[i]) ? " active" : "");
Button.textContent = Label;
problemSwitcher.appendChild(Button);
}
return problemSwitcher;
}
// The ResetType user menu. Shared by the legacy navbar and the /web app so both
// menus offer the same entries and animate the same way.
function CreateUserMenuItems() {
let Entries = [
["修改帐号", () => { location.href = "https://www.xmoj.tech/modifypage.php"; }],
["个人中心", () => { location.href = "https://www.xmoj.tech/userinfo.php?user=" + CurrentUsername; }],
["短消息", () => { location.href = "https://www.xmoj.tech/mail.php"; }],
["插件设置", () => { location.href = "https://www.xmoj.tech/index.php?ByUserScript=1"; }],
["插件更新日志", () => { location.href = "https://www.xmoj.tech/modifypage.php?ByUserScript=1"; }],
["注销", () => {
clearCredential();
GM.cookie.set({
name: 'PHPSESSID',
value: (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).substring(0, 28),
path: "/"
})
.then(() => {
console.log('Reset PHPSESSID successfully.');
})
.catch((error) => {
console.error(error);
}); //We can no longer rely of the server to set the cookie for us
location.href = "https://www.xmoj.tech/logout.php";
}]
];
return Entries.map(([Text, Action]) => {
let Item = document.createElement("li");
Item.className = "dropdown-item";
Item.innerText = Text;
Item.addEventListener("click", Action);
return Item;
});
}
function InitializeUserMenu(PopupUL, ParentLi, Items) {
PopupUL.style.cursor = 'pointer';
Items.forEach(item => {
item.style.opacity = 0;
item.style.transform = 'translateY(-16px)';
item.style.transition = UtilityEnabled("MonochromeUI") ? 'transform 100ms ease, opacity 100ms ease' : 'transform 0.3s ease, opacity 0.5s ease';
});
let showDropdownItems = () => {
PopupUL.style.display = 'block';
Items.forEach((item, index) => {
clearTimeout(item._timeout);
item.style.opacity = 0;
item.style.transform = 'translateY(-4px)';
item._timeout = setTimeout(() => {
item.style.opacity = 1;
item.style.transform = 'translateY(2px)';
}, index * (UtilityEnabled("MonochromeUI") ? 20 : 36));
});
};
let hideDropdownItems = () => {
Items.forEach((item) => {
clearTimeout(item._timeout);
item.style.opacity = 0;
item.style.transform = 'translateY(-16px)';
});
setTimeout(() => {
PopupUL.style.display = 'none';
}, UtilityEnabled("MonochromeUI") ? 80 : 100);
};
let toggleDropdownItems = () => {
if (PopupUL.style.display === 'block') {
hideDropdownItems();
} else {
showDropdownItems();
}
};
ParentLi.addEventListener("click", toggleDropdownItems);
document.addEventListener("click", (event) => {
if (!ParentLi.contains(event.target) && PopupUL.style.display === 'block') {
hideDropdownItems();
}
});
}
// Synchronous and dependency-free: paint the saved theme before Vue, API
// responses, fonts, or any CDN resources are ready. Reused for later changes.
// With NewBootstrap the early block gives /web the same Bootstrap 5 and skin CSS
// as the legacy pages, so this only adds what the app's own markup still needs.
function ApplyContestWebTheme() {
const get = name => {
const value = localStorage.getItem("UserScript-Setting-" + name);
return value === null ? !["DebugMode", "SuperDebug", "ReplaceXM"].includes(name) : value === "true";
};
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
let style = document.getElementById("xmoj-web-style");
if (!style) {
style = document.createElement("style");
style.id = "xmoj-web-style";
(document.head || document.documentElement).appendChild(style);
}
const saved = localStorage.getItem("UserScript-Setting-Theme") || "auto";
const dark = saved === "auto" ? prefersDark.matches : saved === "dark";
document.documentElement.setAttribute("data-bs-theme", dark ? "dark" : "light");
localStorage.setItem("UserScript-Setting-DarkMode", String(dark));
const modern = get("NewBootstrap");
style.textContent = ThemeCanvasCSS + `
#app .xmoj-script-tools { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
#app .copy-btn { margin-left: 10px; }
#app .xmoj-script-countdown { margin-left: 8px; white-space: nowrap; }
#app .xmoj-script-editor { margin: 12px 0; }
#app .xmoj-script-has-editors .syntaxhighlighter,
#app .xmoj-script-has-editors .xmoj-std-code { display: none !important; }
#app .xmoj-std-overlay { pointer-events: none; }
#app .xmoj-script-code-ready { display: none !important; }
#app #rank td.well { color: #222 !important; }
#app .xmoj-problem-head h3 { font-size: 1rem; font-weight: inherit !important; font-family: inherit !important; margin: 0; }
#app .xmoj-problem-actions .btn { margin: 0 5px; }
#app .xmoj-problem-body pre { font-size: 1rem; padding: 0.3em 0.5em; margin: 0.5em 0; }
#app .in-out { overflow: hidden; display: flex; padding: 0.5em 0; }
#app .in-out .in-out-item { flex: 1; overflow: hidden; }
#app .cnt-row .title { font-weight: bolder; font-size: 1.1rem; }
#app a.copy-btn { float: right; margin-left: 0; padding: 0 0.4em; border: 1px solid var(--bs-primary, #337ab7); border-radius: 3px; color: var(--bs-primary, #337ab7); cursor: pointer; text-decoration: none; }
#app a.copy-btn:hover { background-color: var(--bs-secondary-bg, #f5f5f5); }
@media (max-width: 600px) { #app .in-out { flex-direction: column; } #app .in-out-item { margin: 0 !important; } }
#app .dropdown-menu[data-xmoj-script-menu] > li:not([data-xmoj-script]) { display: none !important; }
` + (modern ? `
/* The app still renders Bootstrap 3 markup. Its classes are mapped to
Bootstrap 5 in InitializeContestWebApp; these cover the layout-only ones. */
#app .navbar-header { display: flex; align-items: center; justify-content: space-between; }
#app .navbar .icon-bar, #app .navbar .caret { display: none; }
#app .well { padding: 1rem; margin-bottom: 1rem; background-color: var(--bs-secondary-bg); border: var(--bs-border-width) solid var(--bs-border-color); border-radius: var(--bs-border-radius); }
#app .form-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
#app .form-inline .form-control { width: auto; }
` : `
:root { --bs-secondary-bg: #f5f5f5; }
[data-bs-theme='dark'] { --bs-secondary-bg: #292929; }
[data-bs-theme='dark'] body, [data-bs-theme='dark'] #app .jumbotron,
[data-bs-theme='dark'] #app .navbar, [data-bs-theme='dark'] #app .dropdown-menu,
[data-bs-theme='dark'] #app .form-control, [data-bs-theme='dark'] #app pre {
background: #1a1a1a !important; color: #eee !important;
}
[data-bs-theme='dark'] #app a { color: #b8d6ff; }
[data-bs-theme='dark'] #app .pagination > li > a,
[data-bs-theme='dark'] #app .pagination > li > span,
[data-bs-theme='dark'] #app .well { background-color: #292929; color: #eee; }
[data-bs-theme='dark'] #app .navbar-default .navbar-nav > li > a,
[data-bs-theme='dark'] #app .dropdown-menu > li > a { color: #eee; }
[data-bs-theme='dark'] #app .navbar-nav > .active > a,
[data-bs-theme='dark'] #app .dropdown-menu > li > a:hover,
[data-bs-theme='dark'] #app .table-striped > tbody > tr:nth-of-type(odd),
[data-bs-theme='dark'] #app .table-hover > tbody > tr:hover { background: #292929; }
[data-bs-theme='dark'] #app .btn { background: #292929; color: #eee; border-color: #737373; }
`);
// The legacy problem switcher replaces the app's own problem bar.
if (get("ProblemSwitcher")) style.textContent += "#app .xmoj-problem-nav { display: none !important; }";
// Like the legacy pages, which remove the broadcast marquee.
if (get("RemoveUseless")) style.textContent += "#app .xmoj-broadcast { display: none !important; }";
// With NewBootstrap the early block already adds these to the skin CSS.
if (!modern && get("AddColorText")) style.textContent += ".red {color: #e64747;} .green {color: #399a39;} .blue {color: #428bca;}";
if (!modern && get("AddAnimation")) style.textContent += "#app .btn {transition: background-color 100ms, color 100ms;}";
return dark;
}
// Enhancements for the Vue contest app. Vue's elements and router links keep their
// identity: only Bootstrap 5 classes and our own controls are added to them.
async function InitializeContestWebApp() {
const root = document.getElementById("app");
if (!root) return;
const owned = "data-xmoj-script";
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
let routeKey = "";
let route = null;
let routeData = null;
let controller = null;
let scheduled = false;
let editors = [];
let serverOffset = 0;
let revealed = false;
const countdowns = new Map();
initTheme = () => {
const dark = ApplyContestWebTheme();
for (const entry of editors) entry.editor?._monacoEditor.updateOptions({theme: dark ? "vs-dark" : "vs"});
};
initTheme();
prefersDark.addEventListener("change", initTheme);
InitializeImageEnlarger();
if (UtilityEnabled("NewBootstrap") && !_earlyBootstrapInjected) {
// The @resource was not cached yet (first install/update): load Bootstrap 5
// and the skin the same way the legacy pages do.
const blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"];
for (const link of document.querySelectorAll("link")) {
if (blocked.some(name => link.href && link.href.indexOf(name) !== -1)) link.remove();
}
const bootstrap = document.createElement("link");
bootstrap.rel = "stylesheet";
bootstrap.href = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css";
document.head.appendChild(bootstrap);
const mono = UtilityEnabled("MonochromeUI");
const skin = document.createElement("style");
skin.textContent = mono ? MonochromeSkinCSS : NewBootstrapSkinCSS;
if (UtilityEnabled("AddAnimation")) skin.textContent += `.status, .test-case { transition: ${mono ? "100ms ease" : "0.5s"} !important; }`;
if (UtilityEnabled("AddColorText")) skin.textContent += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`;
document.head.appendChild(skin);
}
function MakeControl(tag, name, text) {
const node = document.createElement(tag);
node.setAttribute(owned, name);
node.textContent = text;
if (tag === "button") {
node.type = "button";
node.className = "btn btn-outline-secondary";
}
return node;
}
function AddLink(parent, name, label, href, className = "btn btn-outline-secondary") {
if (parent.querySelector(`[${owned}="${name}"]`)) return;
const link = MakeControl("a", name, label);
link.href = href;
link.className = className;
parent.appendChild(link);
}
function AddCopy(parent, name, readText) {
if (parent.querySelector(`[${owned}="${name}"]`)) return;
const button = MakeControl("button", name, "复制");
button.className = "btn btn-sm btn-outline-secondary copy-btn";
button.addEventListener("click", () => {
GM_setClipboard(readText());
button.textContent = "复制成功";
setTimeout(() => { button.textContent = "复制"; }, 1000);
});
parent.appendChild(button);
}
// Mirrors the legacy navbar conversion in main(), plus the other Bootstrap 3
// classes the app renders. Runs on every render, because Vue rewrites the class
// attribute of elements whose classes are bound (e.g. the active tab).
function ApplyBootstrap5Markup() {
const nav = root.querySelector(".navbar");
if (nav) {
nav.classList.add("navbar-expand-lg", "bg-body-tertiary");
nav.querySelector("#xmoj-navbar > .navbar-nav:not(.navbar-right)")?.classList.add("me-auto", "mb-2", "mb-lg-0");
for (const item of nav.querySelectorAll(".navbar-nav > li")) {
item.classList.add("nav-item");
const link = item.firstElementChild;
if (!link) continue;
link.classList.add("nav-link");
link.classList.toggle("active", item.classList.contains("active"));
}
for (const toggle of nav.querySelectorAll('[data-toggle="dropdown"]')) {
toggle.setAttribute("data-bs-toggle", "dropdown");
toggle.removeAttribute("data-toggle");
toggle.parentElement.classList.add("dropdown");
}
const toggler = nav.querySelector(".navbar-toggle");
if (toggler) {
toggler.classList.add("navbar-toggler");
if (toggler.hasAttribute("data-toggle")) {
toggler.setAttribute("data-bs-toggle", "collapse");
toggler.setAttribute("data-bs-target", toggler.getAttribute("data-target") || "#xmoj-navbar");
toggler.removeAttribute("data-toggle");
toggler.removeAttribute("data-target");
}
if (!toggler.querySelector(".navbar-toggler-icon")) {
const icon = MakeControl("span", "toggler-icon", "");
icon.className = "navbar-toggler-icon";
toggler.appendChild(icon);
}
}
}
for (const node of root.querySelectorAll(".dropdown-menu > li > a")) node.classList.add("dropdown-item");
for (const node of root.querySelectorAll(".btn-default")) node.classList.add("btn-outline-secondary");
for (const node of root.querySelectorAll(".btn-xs")) node.classList.add("btn-sm");
for (const node of root.querySelectorAll(".sr-only")) node.classList.add("visually-hidden");
for (const node of root.querySelectorAll(".label")) node.classList.add("badge");
for (const node of root.querySelectorAll(".label-info")) node.classList.add("text-bg-info");
for (const item of root.querySelectorAll(".pagination > li")) {
item.classList.add("page-item");
item.firstElementChild?.classList.add("page-link");
}
}
function EnhanceNav() {
const nav = root.querySelector("#xmoj-navbar");
if (!nav) return;
const menu = nav.querySelector(".dropdown-menu");
if (menu && UtilityEnabled("ResetType")) {
if (!menu.hasAttribute("data-xmoj-script-menu")) {
// Same menu as the legacy navbar. Vue keeps rendering its own items, so
// they are hidden by CSS instead of replacing the list.
menu.setAttribute("data-xmoj-script-menu", "");
const items = CreateUserMenuItems();
for (const item of items) {
item.setAttribute(owned, "user-menu");
menu.appendChild(item);
}
InitializeUserMenu(menu, menu.parentElement, items);
}
} else if (menu && !menu.querySelector(`[${owned}="settings"]`)) {
const item = MakeControl("li", "settings", "");
const link = document.createElement("a");
link.href = "/index.php?ByUserScript=1";
link.textContent = "插件设置";
item.appendChild(link);
menu.appendChild(item);
}
const links = nav.querySelector("ul");
if (links && UtilityEnabled("Discussion") && !links.querySelector(`[${owned}="discussion-nav"]`)) {
const item = MakeControl("li", "discussion-nav", "");
const link = document.createElement("a");
link.href = "/discuss3/discuss.php";
link.textContent = "讨论";
item.appendChild(link);
links.appendChild(item);
}
if (UtilityEnabled("Translate")) {
// Same rename as the legacy navbar. Edit the text node Vue renders, so
// switching the app's language can still replace it.
const problems = nav.querySelector(".navbar-nav > li:nth-child(2) > a");
const text = problems && [...problems.childNodes].find(node => node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() === "问题");
if (text) text.nodeValue = text.nodeValue.replace("问题", "题库");
}
}
function ClearRoute() {
controller?.abort();
countdowns.clear();
for (const entry of editors) {
entry.editor?.dispose();
entry.original?.classList.remove("xmoj-script-code-ready", "xmoj-script-has-editors");
}
editors = [];
root.querySelectorAll(`[${owned}]:not([${owned}="settings"]):not([${owned}="discussion-nav"]):not([${owned}="toggler-icon"]):not([${owned}="user-menu"])`).forEach(node => node.remove());
document.querySelectorAll(`[${owned}="problem-switcher"]`).forEach(node => node.remove());
routeData = null;
}
function AddCountdown(parent, endTime, label) {
if (!UtilityEnabled("AutoCountdown") || !parent || !Number.isFinite(endTime)) return;
let node = parent.querySelector(`[${owned}="countdown"]`);
if (!node) {
node = MakeControl("span", "countdown", "");
node.className = "xmoj-script-countdown";
parent.appendChild(node);
}
countdowns.set(node, {endTime, label});
}
function ParseServerTime(value) {
// The API returns China local time without an offset, regardless of browser timezone.
return Date.parse(String(value || "").replace(" ", "T") + "+08:00");
}
function UpdateCountdowns() {
for (const [node, item] of countdowns) {
if (!node.isConnected) { countdowns.delete(node); continue; }
const seconds = Math.max(0, Math.ceil((item.endTime - Date.now() - serverOffset) / 1000));
const days = Math.floor(seconds / 86400);
const hours = String(Math.floor(seconds / 3600) % 24).padStart(2, "0");
const minutes = String(Math.floor(seconds / 60) % 60).padStart(2, "0");
const text = item.label + (days ? days + "天 " : "") + hours + ":" + minutes + ":" + String(seconds % 60).padStart(2, "0");
if (node.textContent !== text) node.textContent = text;
}
}
async function LoadRouteData(signal, key, current) {
try {
let path = "contest/" + current.cid;
if (current.page === "list") {
const query = new URL(key).searchParams;
path = "contest/list?" + new URLSearchParams({pageNum: query.get("page") || "1", pageSize: "20", keyword: query.get("keyword") || ""});
} else if (current.page === "rank") {
// The native view owns sorting and incremental ranking requests.
return;
} else if (current.num) {
path += "/" + encodeURIComponent(current.num) + (current.page === "problem" ? "" : "/" + current.page);
}
const data = await FetchContestAPI(path, signal);
if (signal.aborted || location.href !== key) return;
routeData = data;
if (data.serverTime) serverOffset = ParseServerTime(data.serverTime) - Date.now();
if (!Number.isFinite(serverOffset)) serverOffset = 0;
if (current.cid) CacheContestProblems(current.cid, data);
if (data.problem?.problemId) {
const problem = data.problem;
localStorage.setItem("UserScript-Contest-" + current.cid + "-Problem-" + GetContestProblemIndex(current.num) + "-PID", problem.problemId);
localStorage.setItem("UserScript-Problem-" + problem.problemId + "-IOFilename", problem.name || "");
localStorage.setItem("UserScript-Problem-" + problem.problemId + "-Name", problem.title || "");
}
ScheduleEnhance();
} catch (error) {
if (!signal.aborted) console.error("[XMOJ-Script] Contest API:", error);
}
}
function EnhanceList() {
if (!routeData?.list) return;
for (const row of root.querySelectorAll(".xmoj-scroll-x tbody tr")) {
const link = row.querySelector('a[href*="/web/contest/"]');
if (!link) continue;
const cid = GetContestRoute(link.href)?.cid;
const data = routeData.list.find(item => String(item.contestId) === cid);
if (!data) continue;
localStorage.setItem("UserScript-Contest-" + cid + "-Name", data.title);
const status = data.status;
if (status?.state === "pending" || status?.state === "running") {
AddCountdown(row.cells[2], ParseServerTime(status.state === "pending" ? status.startTime : status.endTime), status.state === "pending" ? "距开始 " : "剩余 ");
}
const creator = row.cells[4];
if (creator && data.createdBy) {
// Like the legacy contest list, the creator's name becomes the link. The
// name is a text node Vue owns: blank it rather than removing it.
for (const node of creator.childNodes) {
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() === data.createdBy) node.nodeValue = "";
}
AddLink(creator, "creator", data.createdBy, "/userinfo.php?user=" + encodeURIComponent(data.createdBy), "");
}
}
}
function EnhanceContest() {
const table = root.querySelector(".xmoj-problems-table");
if (!table || !routeData?.problems) return;
const head = root.querySelector(".xmoj-contest-head");
const contest = routeData.contest;
const timeline = contest?.myTimeline;
const state = contest?.status?.state;
if (timeline?.enabled || state === "running" || state === "pending") {
AddCountdown(head, ParseServerTime(timeline?.enabled ? timeline.endTime : state === "pending" ? contest.startTime : contest.endTime), state === "pending" ? "距开始 " : "剩余 ");
}
let toolbar = root.querySelector(`[${owned}="contest-tools"]`);
if (!toolbar) {
toolbar = MakeControl("div", "contest-tools", "");
toolbar.className = "xmoj-script-tools";
table.parentElement.before(toolbar);
if (UtilityEnabled("OpenAllProblem")) {
for (const unsolved of [false, true]) {
const button = MakeControl("button", "open-problems", unsolved ? "打开未解决题目" : "打开全部题目");
button.addEventListener("click", () => {
for (const problem of routeData.problems) {
if (!unsolved || problem.myStatus !== "AC") window.open(GetContestProblemURL(route.cid, problem.num), "_blank", "noopener");
}
});
toolbar.appendChild(button);
}
}
if (UtilityEnabled("AutoCheat")) AddResubmitButton(toolbar);
}
if (UtilityEnabled("MoreSTD") && contest?.enableStd) {
for (const row of table.tBodies[0].rows) {
const link = row.querySelector('a[href*="/web/contest/"]');
const num = link && GetContestRoute(link.href)?.num;
if (!num || row.querySelector('a[href$="/std"]')) continue;
const headers = Array.from(table.tHead.rows[0].cells);
const index = headers.findIndex(cell => /标程|Std/.test(cell.textContent));
if (index >= 0) AddLink(row.cells[index], "std-link", "打开", GetContestProblemURL(route.cid, num) + "/std", "");
}
}
}
function AddResubmitButton(toolbar) {
const button = MakeControl("button", "resubmit", "自动提交当年代码");
toolbar.appendChild(button);
button.addEventListener("click", async () => {
if (!CurrentUsername || !routeData) return;
const current = route;
const signal = controller.signal;
const problems = routeData.problems;
button.disabled = true;
try {
for (const problem of problems) {
if (signal.aborted) return;
if (problem.myStatus === "AC" || !problem.problemId) continue;
// Only retrieve this user's accepted code, never another user's submission.
const query = new URLSearchParams({problem_id: problem.problemId, user_id: CurrentUsername, jresult: "4"});
const response = await fetch("/status.php?" + query, {signal});
if (!response.ok) throw new Error("无法读取提交记录");
const page = new DOMParser().parseFromString(await response.text(), "text/html");
const sid = page.querySelector("#result-tab > tbody > tr > td:nth-child(2)")?.textContent.trim();
if (!/^\d+$/.test(sid || "")) continue;
const sourceResponse = await fetch("/getsource.php?id=" + sid, {signal});
if (!sourceResponse.ok) throw new Error("无法读取代码");
const source = (await sourceResponse.text()).split("/**************************************************************")[0].trim();
if (!source || /^\s* setTimeout(resolve, 1000));
}
button.textContent = "处理完成,请刷新查看状态";
} catch (error) {
if (!signal.aborted) button.textContent = error.message;
} finally { if (!signal.aborted) button.disabled = false; }
});
}
function EnhanceProblem() {
const problem = routeData?.problem;
if (problem?.problemId) {
const heading = root.querySelector(".xmoj-problem-head h2");
if (heading && !heading.querySelector(`[${owned}="problem-id"]`)) heading.appendChild(MakeControl("small", "problem-id", " (" + problem.problemId + ")"));
if (UtilityEnabled("Discussion")) {
for (const actions of root.querySelectorAll(".xmoj-problem-actions")) {
AddLink(actions, "discuss-problem", "讨论", "/discuss3/discuss.php?pid=" + encodeURIComponent(problem.problemId));
}
}
}
// Same buttons as the legacy problem page: full size and all outlined.
for (const button of root.querySelectorAll(".xmoj-problem-actions .btn")) {
button.classList.remove("btn-sm", "btn-primary", "btn-default");
button.classList.add("btn-outline-secondary");
}
// The legacy page removes the repeated buttons below the statement.
if (UtilityEnabled("RemoveUseless")) {
for (const head of root.querySelectorAll(".xmoj-problem-head")) {
if (!head.querySelector("h2") && head.querySelector(".xmoj-problem-actions")) head.style.display = "none";
}
}
// Sample boxes are cards, as on the legacy page.
for (const pre of root.querySelectorAll(".xmoj-problem-body .in-out pre")) pre.classList.add("card");
if (UtilityEnabled("CopyMD")) InitializeProblemMarkdownCopy(root, root, AddCopy);
// Built from this page's API response rather than a cached copy.
if (UtilityEnabled("ProblemSwitcher") && Array.isArray(routeData?.problems) && !document.querySelector(`[${owned}="problem-switcher"]`)) {
const list = routeData.problems.map(item => ({title: item.problemTitle || item.title || item.num, url: new URL(GetContestProblemURL(route.cid, item.num), location.origin).href}));
if (list.length) {
const switcher = CreateProblemSwitcher(list, (index, item) => GetContestRoute(item.url)?.num === route.num);
switcher.setAttribute(owned, "problem-switcher");
// Switch problems through the app's own (hidden) router links, so the
// page does not reload.
switcher.addEventListener("click", event => {
const link = event.target.closest("a[href]");
const target = link && GetContestRoute(link.href);
const routerLink = target && [...root.querySelectorAll(".xmoj-problem-nav a")].find(a => GetContestRoute(a.href)?.num === target.num);
if (routerLink) {
event.preventDefault();
routerLink.click();
}
});
document.body.appendChild(switcher);
}
}
}
function AddCodeEditor(parent, original, code) {
if (editors.some(entry => entry.original === original && entry.code === code)) return;
const host = MakeControl("div", "code-editor", "");
host.className = "xmoj-script-editor";
const codeHost = document.createElement("div");
const height = Math.max(100, Math.min(600, code.split("\n").length * 20 + 24));
codeHost.style.height = height + "px";
host.appendChild(codeHost);
AddCopy(host, "copy-code", () => code);
parent.appendChild(host);
const entry = {original, host, code, editor: null};
editors.push(entry);
const signal = controller.signal;
createMonacoEditor(codeHost, {value: code, language: "cpp", readOnly: true, height: height + "px", automaticLayout: true}).then(editor => {
if (signal.aborted || !host.isConnected) { editor.dispose(); return; }
entry.editor = editor;
original.classList.add(original.classList.contains("xmoj-std-stage") ? "xmoj-script-has-editors" : "xmoj-script-code-ready");
}).catch(error => {
host.remove();
console.error("[XMOJ-Script] Read-only editor:", error);
});
}
function EnhanceCode() {
if (route.page === "std" && routeData?.stdList) {
const stage = root.querySelector(".xmoj-std-stage");
const body = stage?.querySelector(".jumbotron");
if (body) for (const code of routeData.stdList) AddCodeEditor(body, stage, code);
}
if (route.page === "solution") {
const body = root.querySelector(".xmoj-solution-body");
const heading = root.querySelector(".xmoj-problem-head h2");
if (body && heading && UtilityEnabled("CopyMD")) AddCopy(heading, "copy-solution", () => GetMDText(body).trim());
if (body) for (const code of body.querySelectorAll("pre.prettyprint")) AddCodeEditor(code.parentElement, code, code.textContent);
}
}
function Enhance() {
scheduled = false;
observer.disconnect();
try {
if (routeKey !== location.href) {
ClearRoute();
routeKey = location.href;
route = GetContestRoute();
controller = new AbortController();
if (route) void LoadRouteData(controller.signal, routeKey, route);
}
// Vue can replace a loaded view on the same route (e.g. retry/language switch).
editors = editors.filter(entry => {
if (entry.host.isConnected) return true;
entry.editor?.dispose();
entry.original?.classList.remove("xmoj-script-code-ready", "xmoj-script-has-editors");
return false;
});
if (UtilityEnabled("NewBootstrap")) ApplyBootstrap5Markup();
EnhanceNav();
if (!revealed && root.querySelector(".navbar")) {
// As on the legacy pages: top bar first, then show the page.
revealed = true;
if (UtilityEnabled("NewTopBar")) new NavbarStyler();
RevealPage();
}
if (!route) return;
if (route.page === "list") EnhanceList();
if (route.page === "contest") EnhanceContest();
if (route.page === "problem") EnhanceProblem();
if (route.page === "std" || route.page === "solution") EnhanceCode();
if (UtilityEnabled("NewBootstrap")) root.querySelector("#rank")?.classList.add("table", "table-hover");
if (UtilityEnabled("Translate")) {
const labels = {Rank: "排名", User: "用户", Nick: "昵称", Name: "姓名", Solved: "AC数", Mark: "得分"};
for (const cell of root.querySelectorAll("#rank thead th")) {
if (labels[cell.textContent]) cell.textContent = labels[cell.textContent];
}
}
UpdateCountdowns();
} finally { observer.observe(root, {childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ["class"]}); }
}
function ScheduleEnhance() {
if (!scheduled) { scheduled = true; requestAnimationFrame(Enhance); }
}
const observer = new MutationObserver(ScheduleEnhance);
Enhance();
window.addEventListener("popstate", ScheduleEnhance);
setInterval(() => {
UpdateCountdowns();
if (UtilityEnabled("NewTopBar")) new NavbarStyler();
}, 1000);
window.addEventListener("focus", () => {
if (!UtilityEnabled("AutoRefresh") || !["list", "contest", "rank"].includes(route?.page)) return;
if (root.querySelector('input:focus, textarea:focus') || root.querySelector(`[${owned}="resubmit"]:disabled`)) return;
// Reload lets Vue refresh both its data and sort state; editing its rendered
// table alone would cause stale rows to return on the next native sort.
location.reload();
});
// The app's #profile replacement arrives asynchronously. Authentication must
// come from /api/nav, never from the absence of the legacy DOM element.
try {
const nav = await FetchContestAPI("nav");
if (!nav.loggedIn) {
if (UtilityEnabled("AutoLogin")) {
localStorage.setItem("UserScript-LastPage", location.pathname + location.search + location.hash);
location.href = "/loginpage.php";
}
return;
}
CurrentUsername = nav.userId || "";
PeriodicCloudSync();
setInterval(PeriodicCloudSync, 60 * 60 * 1000);
} catch (error) { console.error("[XMOJ-Script] Navigation API:", error); }
}
class NavbarStyler {
constructor() {
try {
this.navbar = document.querySelector('.navbar.navbar-expand-lg.bg-body-tertiary');
if (this.navbar && UtilityEnabled("NewTopBar")) {
this.init();
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
init() {
try {
this.applyStyles();
this.createOverlay();
this.createSpacer();
window.addEventListener('resize', () => this.updateBlurOverlay());
this.updateBlurOverlay();
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
applyStyles() {
try {
let n = this.navbar;
n.classList.add('fixed-top', 'container', 'ml-auto');
if (UtilityEnabled("MonochromeUI")) {
Object.assign(n.style, {
position: 'fixed',
borderRadius: '0',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)',
margin: '0',
maxWidth: '100%',
backgroundColor: 'rgba(255, 255, 255, 0)',
opacity: '0.75',
zIndex: '1000'
});
} else {
Object.assign(n.style, {
position: 'fixed',
borderRadius: '28px',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)',
margin: '16px auto',
backgroundColor: 'rgba(255, 255, 255, 0)',
opacity: '0.75',
zIndex: '1000'
});
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
createOverlay() {
try {
if (!document.getElementById('blur-overlay')) {
let overlay = document.createElement('div');
overlay.id = 'blur-overlay';
document.body.appendChild(overlay);
let style = document.createElement('style');
style.textContent = UtilityEnabled("MonochromeUI") ? `
#blur-overlay {
display: none !important;
}
` : `
#blur-overlay {
position: fixed;
backdrop-filter: blur(4px);
z-index: 999;
pointer-events: none;
border-radius: 28px;
}
`;
document.head.appendChild(style);
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
updateBlurOverlay() {
try {
let overlay = document.getElementById('blur-overlay');
let n = this.navbar;
Object.assign(overlay.style, {
top: `${n.offsetTop}px`,
left: `${n.offsetLeft}px`,
width: `${n.offsetWidth}px`,
height: `${n.offsetHeight}px`
});
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
createSpacer() {
try {
let spacer = document.getElementById('navbar-spacer');
let newHeight = this.navbar.offsetHeight + 24;
if (!spacer) {
spacer = document.createElement('div');
spacer.id = 'navbar-spacer';
spacer.style.height = `${newHeight}px`;
spacer.style.width = '100%';
document.body.insertBefore(spacer, document.body.firstChild);
} else {
let currentHeight = parseInt(spacer.style.height, 10);
if (currentHeight !== newHeight) {
document.body.removeChild(spacer);
spacer = document.createElement('div');
spacer.id = 'navbar-spacer';
spacer.style.height = `${newHeight}px`;
spacer.style.width = '100%';
document.body.insertBefore(spacer, document.body.firstChild);
}
}
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
}
// Wrapped in an async IIFE so that `await` is valid in Violentmonkey,
// which executes userscripts as classic scripts (not ES modules).
(async () => {
if (GetContestWebRedirect()) return;
if (document.readyState === "loading") {
await new Promise(r => document.addEventListener("DOMContentLoaded", r, { once: true }));
}
// Remove any old Bootstrap stylesheets the preload scanner fetched (un-applies them
// from the CSSOM). The page stays hidden until it is styled: main() and the /web app
// call RevealPage once the navbar is in its final state, so there is no unstyled frame
// or navbar jump. The timeouts make sure the page is shown even if that never happens:
// 1.5 seconds after main() starts (it first waits for the login check), and at the
// latest 4 seconds after DOMContentLoaded.
if (_earlyObs) { _earlyObs.disconnect(); _earlyObs = null; }
if (_foucStyle) {
let _blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"];
for (let _link of document.querySelectorAll("link")) {
if (_blocked.some(h => _link.href && _link.href.indexOf(h) !== -1)) _link.remove();
}
setTimeout(RevealPage, 4000);
}
if (IsContestWebApp()) {
await InitializeContestWebApp();
return;
}
//otherwise CurrentUsername might be undefined
let loginStatus;
await fetch("https://www.xmoj.tech/loginpage.php")
.then((response) => response.text())
.then((data) => (loginStatus = data));
const logined = loginStatus == "Please logout First! ";
if (UtilityEnabled("AutoLogin") && document.querySelector("body > a:nth-child(1)") != null && document.querySelector("body > a:nth-child(1)").innerText == "请登录后继续操作") {
localStorage.setItem("UserScript-LastPage", location.pathname + location.search);
location.href = "https://www.xmoj.tech/loginpage.php";
return;
}
SearchParams = new URLSearchParams(location.search);
let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-script.uk/" : "https://www.xmoj-script.uk")
const profileElement = document.querySelector("#profile");
if (profileElement === null) {
RevealPage();
if (!logined) {
location.href = "https://www.xmoj.tech/loginpage.php";
}
return;
}
CurrentUsername = profileElement.innerText;
CurrentUsername = CurrentUsername.replaceAll(/[^a-zA-Z0-9]/g, "");
let IsAdmin = AdminUserList.indexOf(CurrentUsername) !== -1;
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
const applyTheme = (theme) => {
if (document.querySelector("html") != null) document.querySelector("html").setAttribute("data-bs-theme", theme);
localStorage.setItem("UserScript-Setting-DarkMode", theme === "dark" ? "true" : "false");
};
const applySystemTheme = (e) => applyTheme(e.matches ? "dark" : "light");
initTheme = () => {
const saved = localStorage.getItem("UserScript-Setting-Theme") || "auto";
if (saved === "auto") {
applyTheme(prefersDark.matches ? "dark" : "light");
prefersDark.addEventListener("change", applySystemTheme);
} else {
applyTheme(saved);
prefersDark.removeEventListener("change", applySystemTheme);
}
};
initTheme();
PeriodicCloudSync();
setInterval(PeriodicCloudSync, 60 * 60 * 1000);
function replaceMarkdownImages(text, string) {
return text.replace(/!\[.*?\]\(.*?\)/g, string);
}
async function main() {
try {
if (location.href.startsWith('http://')) {
//use https
location.href = location.href.replace('http://', 'https://');
}
if (location.host != "www.xmoj.tech") {
location.host = "www.xmoj.tech";
} else {
if (location.href === 'https://www.xmoj.tech/open_contest_sign_up.php') {
return;
}
document.body.classList.add("placeholder-glow");
if (document.querySelector("#navbar") != null) {
if (document.querySelector("body > div > div.jumbotron") != null) {
document.querySelector("body > div > div.jumbotron").className = "mt-3";
}
if (UtilityEnabled("AutoLogin") && document.querySelector("#profile") != null && document.querySelector("#profile").innerHTML == "登录" && location.pathname != "/login.php" && location.pathname != "/loginpage.php" && location.pathname != "/lostpassword.php" && !logined) {
localStorage.setItem("UserScript-LastPage", location.pathname + location.search);
location.href = "https://www.xmoj.tech/loginpage.php";
}
let Discussion = null;
if (UtilityEnabled("Discussion")) {
Discussion = document.createElement("li");
if (document.querySelector("#navbar > ul:nth-child(1)") != null) document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion);
Discussion.innerHTML = "讨论 ";
}
if (UtilityEnabled("Translate")) {
if (document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a") != null) document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a").innerText = "题库";
}
//send analytics
RequestAPI("SendData", {});
if (UtilityEnabled("ReplaceLinks")) {
document.body.innerHTML = String(document.body.innerHTML).replaceAll(/\[([^<]*)<\/a>\]/g, "$2 ");
}
if (UtilityEnabled("ReplaceXM")) {
document.body.innerHTML = String(document.body.innerHTML).replaceAll("我", "高老师");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("小明", "高老师");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("下海", "上海");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("海上", "上海");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("小红", "徐师娘");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("小粉", "彩虹");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("提交上节课的代码", "自动提交当年代码");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("高老师们", "我们");
document.body.innerHTML = String(document.body.innerHTML).replaceAll("自高老师", "自我");
document.title = String(document.title).replaceAll("小明", "高老师");
}
if (UtilityEnabled("NewBootstrap")) {
// Remove any old Bootstrap/theme stylesheets that the browser's preload
// scanner may have fetched and applied before the document-start
// MutationObserver could intercept them. Removing a from the DOM
// un-applies its stylesheet from the CSSOM immediately.
let Temp = document.querySelectorAll("link");
for (var i = 0; i < Temp.length; i++) {
if (Temp[i].href.indexOf("bootstrap.min.css") != -1) {
Temp[i].remove();
} else if (Temp[i].href.indexOf("white.css") != -1) {
Temp[i].remove();
} else if (Temp[i].href.indexOf("semantic.min.css") != -1) {
Temp[i].remove();
} else if (Temp[i].href.indexOf("bootstrap-theme.min.css") != -1) {
Temp[i].remove();
} else if (Temp[i].href.indexOf("problem.css") != -1) {
Temp[i].remove();
}
}
if (UtilityEnabled("MonochromeUI")) {
let fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = "https://fonts.loli.net/css2?family=Playfair+Display:wght@400;700&family=Source+Serif+4:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap";
document.head.appendChild(fontLink);
}
var resources = [{
type: 'link',
href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css',
rel: 'stylesheet'
}, {
type: 'link',
href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/darcula.min.css',
rel: 'stylesheet'
}, {
type: 'link',
href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.css',
rel: 'stylesheet'
}];
// If the @resource wasn't cached yet (first install/update), the early
// block bailed out and Bootstrap CSS still needs to load from CDN.
if (!_earlyBootstrapInjected) {
resources.push({
type: 'link',
href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css',
rel: 'stylesheet'
});
}
let loadResources = async () => {
let promises = resources.map(resource => {
return new Promise((resolve, reject) => {
let element;
if (resource.type === 'script') {
element = document.createElement('script');
element.src = resource.src;
if (resource.isModule) {
element.type = 'module';
}
element.onload = resolve;
element.onerror = reject;
} else if (resource.type === 'link') {
element = document.createElement('link');
element.href = resource.href;
element.rel = resource.rel;
resolve(); // Stylesheets don't have an onload event
}
document.head.appendChild(element);
});
});
await Promise.all(promises);
};
if (location.pathname == "/submitpage.php") {
await loadResources();
} else {
loadResources();
}
if (document.querySelector("nav") != null) document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary";
if (document.querySelector("#navbar > ul:nth-child(1)") != null) document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0";
if (document.querySelector("body > div > nav > div > div.navbar-header") != null) document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = ` ${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ `;
if (document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li") != null) document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown";
if (document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a") != null) document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle";
if (document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret") != null) document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove();
Temp = (document.querySelector("#navbar > ul:nth-child(1)") != null) ? document.querySelector("#navbar > ul:nth-child(1)").children : [];
for (var i = 0; i < Temp.length; i++) {
if (Temp[i].classList.contains("active")) {
Temp[i].classList.remove("active");
Temp[i].children[0].classList.add("active");
}
Temp[i].classList.add("nav-item");
Temp[i].children[0].classList.add("nav-link");
}
if (document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a") != null) document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown");
if (document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a") != null) document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle");
// The navbar is in its final state: apply the top bar now rather than on
// the first interval tick, then show the page.
if (UtilityEnabled("NewTopBar")) new NavbarStyler();
RevealPage();
}
if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) {
document.getElementsByTagName("marquee")[0].remove();
}
let Style = document.createElement("style");
document.body.appendChild(Style);
if (!_earlyBootstrapInjected) {
let _isMono = UtilityEnabled("MonochromeUI");
Style.innerHTML = _isMono ? MonochromeSkinCSS : NewBootstrapSkinCSS;
if (UtilityEnabled("AddAnimation")) Style.innerHTML += `.status, .test-case { transition: ${_isMono ? "100ms ease" : "0.5s"} !important; }`;
if (UtilityEnabled("AddColorText")) Style.innerHTML += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`;
}
if (UtilityEnabled("RemoveUseless")) {
if (document.getElementsByClassName("footer")[0] != null) {
document.getElementsByClassName("footer")[0].remove();
}
}
if (UtilityEnabled("ReplaceYN")) {
Temp = document.getElementsByClassName("status_y");//AC
for (let i = 0; i < Temp.length; i++) {
Temp[i].innerText = "✓";
}
Temp = document.getElementsByClassName("status_n");//WA
for (let i = 0; i < Temp.length; i++) {
Temp[i].innerText = "✗";
}
Temp = document.getElementsByClassName("status_w");//Waiting
for (let i = 0; i < Temp.length; i++) {
Temp[i].innerText = "⏳";
}
}
Temp = document.getElementsByClassName("page-item");
for (let i = 0; i < Temp.length; i++) {
Temp[i].children[0].className = "page-link";
}
if (document.getElementsByClassName("pagination")[0] != null) {
document.getElementsByClassName("pagination")[0].classList.add("justify-content-center");
}
Temp = document.getElementsByTagName("table");
for (let i = 0; i < Temp.length; i++) {
if (Temp[i].querySelector("thead") != null) {
TidyTable(Temp[i]);
}
}
setInterval(() => {
try {
let CurrentDate = new Date(new Date().getTime() + diff);
let Year = CurrentDate.getFullYear();
if (Year > 3000) {
Year -= 1900;
}
let Month = CurrentDate.getMonth() + 1;
let _Date = CurrentDate.getDate();
let Hours = CurrentDate.getHours();
let Minutes = CurrentDate.getMinutes();
let Seconds = CurrentDate.getSeconds();
document.getElementById("nowdate").innerHTML = Year + "-" + (Month < 10 ? "0" : "") + Month + "-" + (_Date < 10 ? "0" : "") + _Date + " " + (Hours < 10 ? "0" : "") + Hours + ":" + (Minutes < 10 ? "0" : "") + Minutes + ":" + (Seconds < 10 ? "0" : "") + Seconds;
} catch (Error) {
}
if (UtilityEnabled("NewTopBar")) {
new NavbarStyler();
}
if (UtilityEnabled("ResetType")) {
if (document.querySelector("#profile") != undefined && document.querySelector("#profile").innerHTML == "登录") {
let PopupUL = document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > ul");
PopupUL.innerHTML = `登录 `;
PopupUL.children[0].addEventListener("click", () => {
location.href = "https://www.xmoj.tech/loginpage.php";
});
let parentLi = document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li");
document.addEventListener("click", (event) => {
if (!parentLi.contains(event.target) && PopupUL.style.display === 'block') {
hideDropdownItems();
}
});
} else if (document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > ul") != undefined && document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > ul > li:nth-child(2)") != null && document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > ul > li:nth-child(2)").innerText != "个人中心") {
let PopupUL = document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > ul");
PopupUL.innerHTML = "";
let MenuItems = CreateUserMenuItems();
for (let Item of MenuItems) PopupUL.appendChild(Item);
InitializeUserMenu(PopupUL, document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li"), MenuItems);
}
}
if (UtilityEnabled("AutoCountdown")) {
let Temp = document.getElementsByClassName("UpdateByJS");
for (let i = 0; i < Temp.length; i++) {
let EndTime = Temp[i].getAttribute("EndTime");
if (EndTime === null) {
Temp[i].classList.remove("UpdateByJS");
continue;
}
let TimeStamp = parseInt(EndTime) - new Date().getTime();
if (TimeStamp < 3000) {
Temp[i].classList.remove("UpdateByJS");
location.reload();
}
let CurrentDate = new Date(TimeStamp);
let Day = parseInt((TimeStamp / 1000 / 60 / 60 / 24).toFixed(0));
let Hour = CurrentDate.getUTCHours();
let Minute = CurrentDate.getUTCMinutes();
let Second = CurrentDate.getUTCSeconds();
Temp[i].innerHTML = (Day !== 0 ? Day + "天" : "") + (Hour !== 0 ? (Hour < 10 ? "0" : "") + Hour + "小时" : "") + (Minute !== 0 ? (Minute < 10 ? "0" : "") + Minute + "分" : "") + (Second !== 0 ? (Second < 10 ? "0" : "") + Second + "秒" : "");
}
}
}, 100);
fetch(ServerURL + "/Update.json", {cache: "no-cache"})
.then((Response) => {
return Response.json();
})
.then((Response) => {
let CurrentVersion = GM_info.script.version;
let LatestVersion;
for (let i = Object.keys(Response.UpdateHistory).length - 1; i >= 0; i--) {
let VersionInfo = Object.keys(Response.UpdateHistory)[i];
if (UtilityEnabled("DebugMode") || Response.UpdateHistory[VersionInfo].Prerelease == false) {
LatestVersion = VersionInfo;
break;
}
}
if (compareVersions(CurrentVersion, LatestVersion)) {
let UpdateDiv = document.createElement("div");
UpdateDiv.innerHTML = `
XMOJ用户脚本发现新版本${LatestVersion},当前版本${CurrentVersion},点击
此处
更新
`;
if (UtilityEnabled("NewTopBar")) {
UpdateDiv.style.position = 'fixed';
UpdateDiv.style.top = '72px';
UpdateDiv.style.left = '50%';
UpdateDiv.style.transform = 'translateX(-50%)';
UpdateDiv.style.zIndex = '1001';
let spacer = document.createElement("div");
spacer.style.height = '48px';
document.body.insertBefore(spacer, document.body.firstChild);
if (UpdateDiv.querySelector(".btn-close") != null) UpdateDiv.querySelector(".btn-close").addEventListener("click", function () {
document.body.removeChild(spacer);
});
}
document.body.appendChild(UpdateDiv);
if (document.querySelector("body > div") != null) document.querySelector("body > div").insertBefore(UpdateDiv, document.querySelector("body > div > div.mt-3"));
}
if (localStorage.getItem("UserScript-Update-LastVersion") != GM_info.script.version) {
localStorage.setItem("UserScript-Update-LastVersion", GM_info.script.version);
let UpdateDiv = document.createElement("div");
if (document.querySelector("body") != null) document.querySelector("body").appendChild(UpdateDiv);
UpdateDiv.className = "modal fade";
UpdateDiv.id = "UpdateModal";
UpdateDiv.tabIndex = -1;
let UpdateDialog = document.createElement("div");
UpdateDiv.appendChild(UpdateDialog);
UpdateDialog.className = "modal-dialog";
let UpdateContent = document.createElement("div");
UpdateDialog.appendChild(UpdateContent);
UpdateContent.className = "modal-content";
let UpdateHeader = document.createElement("div");
UpdateContent.appendChild(UpdateHeader);
UpdateHeader.className = "modal-header";
let UpdateTitle = document.createElement("h5");
UpdateHeader.appendChild(UpdateTitle);
UpdateTitle.className = "modal-title";
UpdateTitle.innerText = "更新日志";
let UpdateCloseButton = document.createElement("button");
UpdateHeader.appendChild(UpdateCloseButton);
UpdateCloseButton.type = "button";
UpdateCloseButton.className = "btn-close";
UpdateCloseButton.setAttribute("data-bs-dismiss", "modal");
let UpdateBody = document.createElement("div");
UpdateContent.appendChild(UpdateBody);
UpdateBody.className = "modal-body";
let UpdateFooter = document.createElement("div");
UpdateContent.appendChild(UpdateFooter);
UpdateFooter.className = "modal-footer";
let UpdateButton = document.createElement("button");
UpdateFooter.appendChild(UpdateButton);
UpdateButton.type = "button";
UpdateButton.className = "btn btn-secondary";
UpdateButton.setAttribute("data-bs-dismiss", "modal");
UpdateButton.innerText = "关闭";
let Version = Object.keys(Response.UpdateHistory)[Object.keys(Response.UpdateHistory).length - 1]
let Data = Response.UpdateHistory[Version];
let UpdateDataCard = document.createElement("div");
UpdateBody.appendChild(UpdateDataCard);
UpdateDataCard.className = "card mb-3";
let UpdateDataCardBody = document.createElement("div");
UpdateDataCard.appendChild(UpdateDataCardBody);
UpdateDataCardBody.className = "card-body";
let UpdateDataCardTitle = document.createElement("h5");
UpdateDataCardBody.appendChild(UpdateDataCardTitle);
UpdateDataCardTitle.className = "card-title";
UpdateDataCardTitle.innerText = Version;
let UpdateDataCardSubtitle = document.createElement("h6");
UpdateDataCardBody.appendChild(UpdateDataCardSubtitle);
UpdateDataCardSubtitle.className = "card-subtitle mb-2 text-muted";
UpdateDataCardSubtitle.innerHTML = GetRelativeTime(Data.UpdateDate);
let UpdateDataCardText = document.createElement("p");
UpdateDataCardBody.appendChild(UpdateDataCardText);
UpdateDataCardText.className = "card-text";
//release notes
if (Data.Notes != undefined) {
UpdateDataCardText.innerHTML = Data.Notes;
}
let UpdateDataCardList = document.createElement("ul");
UpdateDataCardText.appendChild(UpdateDataCardList);
UpdateDataCardList.className = "list-group list-group-flush";
for (let j = 0; j < Data.UpdateContents.length; j++) {
let UpdateDataCardListItem = document.createElement("li");
UpdateDataCardList.appendChild(UpdateDataCardListItem);
UpdateDataCardListItem.className = "list-group-item";
UpdateDataCardListItem.innerHTML = "(" + "#" + Data.UpdateContents[j].PR + " ) " + escapeHTML(Data.UpdateContents[j].Description);
}
let UpdateDataCardLink = document.createElement("a");
UpdateDataCardBody.appendChild(UpdateDataCardLink);
UpdateDataCardLink.className = "card-link";
UpdateDataCardLink.href = "https://github.com/XMOJ-Script-dev/XMOJ-Script/releases/tag/" + Version;
UpdateDataCardLink.target = "_blank";
UpdateDataCardLink.innerText = "查看该版本";
new bootstrap.Modal(document.getElementById("UpdateModal")).show();
}
});
RequestAPI("GetAddOnScript", {}, (Response) => {
if (Response.Success) {
eval(Response.Data["Script"]);
} else {
console.warn("Fetch AddOnScript failed: " + Response.Message);
}
});
let ToastContainer = document.createElement("div");
ToastContainer.classList.add("toast-container", "position-fixed", "bottom-0", "end-0", "p-3");
document.body.appendChild(ToastContainer);
// Initialize WebSocket notification system
if (CurrentUsername && (UtilityEnabled("BBSPopup") || UtilityEnabled("MessagePopup"))) {
ConnectNotificationSocket();
}
// Fallback polling when WebSocket is not connected
addEventListener("focus", () => {
if (!NotificationSocket || NotificationSocket.readyState !== WebSocket.OPEN) {
PollNotifications();
}
});
// Periodic fallback polling every 60 seconds when WebSocket is down
setInterval(() => {
if (!NotificationSocket || NotificationSocket.readyState !== WebSocket.OPEN) {
PollNotifications();
}
}, 60000);
// Handle tab visibility changes - reconnect if connection dropped
document.addEventListener('visibilitychange', () => {
if (!document.hidden && NotificationSocket &&
NotificationSocket.readyState !== WebSocket.OPEN &&
NotificationSocket.readyState !== WebSocket.CONNECTING) {
ConnectNotificationSocket();
}
});
dispatchEvent(new Event("focus"));
if ((location.pathname == "/index.php" || location.pathname == "/") && document.querySelector("body > div > div.mt-3 > div > div.col-md-4") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/index.php" || location.pathname == "/") {
if (new URL(location.href).searchParams.get("ByUserScript") != null) {
document.title = "脚本设置";
localStorage.setItem("UserScript-Opened", "true");
let Container = document.getElementsByClassName("mt-3")[0];
Container.innerHTML = "";
let Alert = document.createElement("div");
Alert.classList.add("alert");
Alert.classList.add("alert-primary");
Alert.role = "alert";
Alert.innerHTML = `欢迎您使用XMOJ增强脚本!点击
此处
查看更新日志。`;
Container.appendChild(Alert);
let UtilitiesCard = document.createElement("div");
UtilitiesCard.classList.add("card");
UtilitiesCard.classList.add("mb-3");
let UtilitiesCardHeader = document.createElement("div");
UtilitiesCardHeader.classList.add("card-header");
UtilitiesCardHeader.innerText = "XMOJ增强脚本功能列表";
UtilitiesCard.appendChild(UtilitiesCardHeader);
let UtilitiesCardBody = document.createElement("div");
UtilitiesCardBody.classList.add("card-body");
let CreateList = (Data) => {
let List = document.createElement("ul");
List.classList.add("list-group");
for (let i = 0; i < Data.length; i++) {
let Row = document.createElement("li");
Row.classList.add("list-group-item");
if (Data[i].Type == "A") {
Row.classList.add("list-group-item-success");
} else if (Data[i].Type == "F") {
Row.classList.add("list-group-item-warning");
} else if (Data[i].Type == "D") {
Row.classList.add("list-group-item-danger");
}
if (Data[i].ID == "Theme") {
let Label = document.createElement("label");
Label.classList.add("me-2");
Label.htmlFor = "UserScript-Setting-Theme";
Label.innerText = Data[i].Name;
Row.appendChild(Label);
let Select = document.createElement("select");
Select.classList.add("form-select", "form-select-sm", "w-auto", "d-inline");
Select.id = "UserScript-Setting-Theme";
[
["light", "亮色"],
["dark", "暗色"],
["auto", "跟随系统"]
].forEach(opt => {
let option = document.createElement("option");
option.value = opt[0];
option.innerText = opt[1];
Select.appendChild(option);
});
Select.value = localStorage.getItem("UserScript-Setting-Theme") || "auto";
Select.addEventListener("change", () => {
localStorage.setItem("UserScript-Setting-Theme", Select.value);
initTheme();
SyncSettingsToCloud();
});
Row.appendChild(Select);
} else if (Data[i].Children == undefined) {
let CheckBox = document.createElement("input");
CheckBox.classList.add("form-check-input");
CheckBox.classList.add("me-1");
CheckBox.type = "checkbox";
CheckBox.id = Data[i].ID;
if (localStorage.getItem("UserScript-Setting-" + Data[i].ID) == null) {
localStorage.setItem("UserScript-Setting-" + Data[i].ID, DefaultOffSettings.includes(Data[i].ID) ? "false" : "true");
}
if (localStorage.getItem("UserScript-Setting-" + Data[i].ID) == "false") {
CheckBox.checked = false;
} else {
CheckBox.checked = true;
}
CheckBox.addEventListener("change", () => {
localStorage.setItem("UserScript-Setting-" + Data[i].ID, CheckBox.checked);
// Don't sync when disabling CloudSync itself (it's already off)
if (Data[i].ID !== "CloudSync" || CheckBox.checked) {
SyncSettingsToCloud();
}
});
Row.appendChild(CheckBox);
let Label = document.createElement("label");
Label.classList.add("form-check-label");
Label.htmlFor = Data[i].ID;
Label.innerText = Data[i].Name;
Row.appendChild(Label);
} else {
let Label = document.createElement("label");
Label.innerText = Data[i].Name;
Row.appendChild(Label);
}
if (Data[i].Children != undefined) {
Row.appendChild(CreateList(Data[i].Children));
}
List.appendChild(Row);
}
return List;
};
UtilitiesCardBody.appendChild(CreateList([{
"ID": "Discussion",
"Type": "F",
"Name": "恢复讨论与短消息功能"
}, {
"ID": "MoreSTD", "Type": "F", "Name": "查看到更多标程"
}, {"ID": "ApplyData", "Type": "A", "Name": "获取数据功能"}, {
"ID": "AutoCheat", "Type": "A", "Name": "自动提交当年代码"
}, {"ID": "Rating", "Type": "A", "Name": "添加用户评分和用户名颜色"}, {
"ID": "AutoRefresh", "Type": "A", "Name": "比赛列表、比赛排名界面自动刷新"
}, {
"ID": "AutoCountdown", "Type": "A", "Name": "比赛列表等界面的时间自动倒计时"
}, {"ID": "DownloadPlayback", "Type": "A", "Name": "回放视频增加下载功能"}, {
"ID": "ImproveACRate", "Type": "A", "Name": "自动提交已AC题目以提高AC率"
}, {"ID": "AutoO2", "Type": "F", "Name": "代码提交界面自动选择O2优化"}, {
"ID": "AutoCaptcha", "Type": "A", "Name": "自动识别提交界面的验证码(本地识别,把握不大时留空由您填写)"
}, {
"ID": "Beautify", "Type": "F", "Name": "美化界面", "Children": [{
"ID": "NewTopBar", "Type": "F", "Name": "使用新的顶部导航栏"
}, {
"ID": "NewBootstrap", "Type": "F", "Name": "使用新版的Bootstrap样式库*"
}, {"ID": "ResetType", "Type": "F", "Name": "重新排版*"}, {
"ID": "AddColorText", "Type": "A", "Name": "增加彩色文字"
}, {"ID": "AddUnits", "Type": "A", "Name": "状态界面内存与耗时添加单位"}, {
"ID": "Theme", "Type": "A", "Name": "界面主题"
}, {"ID": "AddAnimation", "Type": "A", "Name": "增加动画"}, {
"ID": "ReplaceYN", "Type": "F", "Name": "题目前状态提示替换为好看的图标"
}, {"ID": "RemoveAlerts", "Type": "D", "Name": "去除多余反复的提示"}, {
"ID": "Translate", "Type": "F", "Name": "统一使用中文,翻译了部分英文*"
}, {
"ID": "ReplaceLinks", "Type": "F", "Name": "将网站中所有以方括号包装的链接替换为按钮"
}, {"ID": "RemoveUseless", "Type": "D", "Name": "删去无法使用的功能*"}, {
"ID": "ReplaceXM",
"Type": "F",
"Name": "将网站中所有\"小明\"和\"我\"关键字替换为\"高老师\",所有\"小红\"替换为\"徐师娘\",所有\"小粉\"替换为\"彩虹\",所有\"下海\"、\"海上\"替换为\"上海\" (此功能默认关闭)"
}, {
"ID": "MonochromeUI", "Type": "F", "Name": "极简黑白界面风格"
}]
}, {
"ID": "AutoLogin", "Type": "A", "Name": "在需要登录的界面自动跳转到登录界面"
}, {
"ID": "SavePassword", "Type": "A", "Name": "自动保存用户名与密码,免去每次手动输入密码的繁琐"
}, {
"ID": "CopySamples", "Type": "F", "Name": "题目界面测试样例有时复制无效"
}, {
"ID": "RefreshSolution", "Type": "F", "Name": "状态页面结果自动刷新每次只能刷新一个"
}, {"ID": "CopyMD", "Type": "A", "Name": "复制题目或题解内容"}, {
"ID": "ProblemSwitcher", "Type": "A", "Name": "比赛题目切换器"
}, {
"ID": "OpenAllProblem", "Type": "A", "Name": "比赛题目界面一键打开所有题目"
}, {
"ID": "CheckCode", "Type": "A", "Name": "提交代码前对代码进行检查", "Children": [{
"ID": "IOFile", "Type": "A", "Name": "是否使用了文件输入输出(如果需要使用)"
}, {"ID": "CompileError", "Type": "A", "Name": "是否有编译错误"}]
}, {
"ID": "ExportACCode", "Type": "F", "Name": "导出AC代码每一道题目一个文件"
}, {"ID": "LoginFailed", "Type": "F", "Name": "修复登录后跳转失败*"}, {
"ID": "NewDownload", "Type": "A", "Name": "下载页面增加下载内容"
}, {"ID": "CompareSource", "Type": "A", "Name": "比较代码"}, {
"ID": "BBSPopup", "Type": "A", "Name": "讨论提醒"
}, {"ID": "MessagePopup", "Type": "A", "Name": "短消息提醒"}, {
"ID": "ImageEnlarger", "Type": "A", "Name": "图片放大功能"
}, {
"ID": "CloudSync", "Type": "A", "Name": "将设置同步至云端(跨设备同步)"
}, {
"ID": "DebugMode", "Type": "A", "Name": "调试模式(仅供开发者使用)"
}, {
"ID": "SuperDebug", "Type": "A", "Name": "本地调试模式(仅供开发者使用) (未经授权的擅自开启将导致大部分功能不可用!)"
}]));
let UtilitiesCardFooter = document.createElement("div");
UtilitiesCardFooter.className = "card-footer text-muted";
UtilitiesCardFooter.innerText = "* 不建议关闭,可能会导致系统不稳定、界面错乱、功能缺失等问题\n绿色:增加功能 黄色:修改功能 红色:删除功能";
UtilitiesCardBody.appendChild(UtilitiesCardFooter);
UtilitiesCard.appendChild(UtilitiesCardBody);
Container.appendChild(UtilitiesCard);
let SyncCard = document.createElement("div");
SyncCard.className = "card mb-3";
let SyncCardHeader = document.createElement("div");
SyncCardHeader.className = "card-header";
SyncCardHeader.innerText = "设置云同步";
SyncCard.appendChild(SyncCardHeader);
let SyncCardBody = document.createElement("div");
SyncCardBody.className = "card-body";
let SyncStatusText = document.createElement("p");
SyncStatusText.className = "card-text mb-2";
SyncStatusText.id = "UserScript-SyncStatus";
SyncStatusText.innerText = "正在从云端加载设置…";
SyncCardBody.appendChild(SyncStatusText);
let SyncButtonGroup = document.createElement("div");
SyncButtonGroup.className = "d-flex gap-2";
let ApplyCloudSettings = (cloudSettings) => {
for (let key in cloudSettings) {
const rawValue = cloudSettings[key];
localStorage.setItem("UserScript-Setting-" + key, String(rawValue));
if (key === "Theme") {
let themeSelect = document.getElementById("UserScript-Setting-Theme");
if (themeSelect) themeSelect.value = String(rawValue);
initTheme();
} else {
let checkbox = document.getElementById(key);
if (checkbox) {
const normalizedChecked = (typeof rawValue === "boolean") ? rawValue : (String(rawValue).toLowerCase() === "true");
checkbox.checked = normalizedChecked;
}
}
}
};
let UploadBtn = document.createElement("button");
UploadBtn.className = "btn btn-sm btn-primary";
UploadBtn.innerText = "上传设置到云端";
UploadBtn.addEventListener("click", () => {
SyncStatusText.innerText = "正在上传…";
SyncSettingsToCloud((Response) => {
SyncStatusText.innerText = Response.Success ? "上传成功" : ("上传失败: " + Response.Message);
});
});
SyncButtonGroup.appendChild(UploadBtn);
let DownloadBtn = document.createElement("button");
DownloadBtn.className = "btn btn-sm btn-secondary";
DownloadBtn.innerText = "从云端下载设置";
DownloadBtn.addEventListener("click", () => {
SyncStatusText.innerText = "正在下载…";
RequestAPI("GetUserSettings", {}, (Response) => {
if (Response.Success) {
ApplyCloudSettings(Response.Data.Settings);
SyncStatusText.innerText = "下载成功,设置已应用(部分设置需刷新页面后生效)";
} else {
SyncStatusText.innerText = "下载失败: " + Response.Message;
}
});
});
SyncButtonGroup.appendChild(DownloadBtn);
SyncCardBody.appendChild(SyncButtonGroup);
SyncCard.appendChild(SyncCardBody);
Container.appendChild(SyncCard);
if (UtilityEnabled("CloudSync")) {
RequestAPI("GetUserSettings", {}, (Response) => {
let SyncStatusEl = document.getElementById("UserScript-SyncStatus");
if (Response.Success) {
const cloudSettings = (Response.Data && Response.Data.Settings) || {};
if (Object.keys(cloudSettings).length === 0) {
if (SyncStatusEl) SyncStatusEl.innerText = "正在上传本地设置至云端…";
SyncSettingsToCloud((Resp) => {
if (SyncStatusEl) SyncStatusEl.innerText = Resp.Success ? "已将本地设置上传至云端" : ("上传失败: " + Resp.Message);
});
} else {
ApplyCloudSettings(cloudSettings);
if (SyncStatusEl) SyncStatusEl.innerText = "已从云端加载设置";
}
} else {
if (SyncStatusEl) SyncStatusEl.innerText = "云端设置加载失败: " + Response.Message;
}
});
} else {
let SyncStatusEl = document.getElementById("UserScript-SyncStatus");
if (SyncStatusEl) SyncStatusEl.innerText = "云同步已禁用";
}
let FeedbackCard = document.createElement("div");
FeedbackCard.className = "card mb-3";
let FeedbackCardHeader = document.createElement("div");
FeedbackCardHeader.className = "card-header";
FeedbackCardHeader.innerText = "反馈、源代码、联系作者";
FeedbackCard.appendChild(FeedbackCardHeader);
let FeedbackCardBody = document.createElement("div");
FeedbackCardBody.className = "card-body";
let FeedbackCardText = document.createElement("p");
FeedbackCardText.className = "card-text";
FeedbackCardText.innerText = "如果您有任何建议或者发现了 bug,请前往本项目的 GitHub 页面并提交 issue。提交 issue 前请先搜索是否有相同的 issue,如果有请在该 issue 下留言。请在 issue 中尽可能详细地描述您的问题,并且附上您的浏览器版本、操作系统版本、脚本版本、复现步骤等信息。谢谢您支持本项目。";
FeedbackCardBody.appendChild(FeedbackCardText);
let FeedbackCardLink = document.createElement("a");
FeedbackCardLink.className = "card-link";
FeedbackCardLink.innerText = "GitHub";
FeedbackCardLink.href = "https://github.com/XMOJ-Script-dev/XMOJ-Script";
FeedbackCardBody.appendChild(FeedbackCardLink);
FeedbackCard.appendChild(FeedbackCardBody);
Container.appendChild(FeedbackCard);
} else {
let Temp = (document.querySelector("body > div > div.mt-3 > div > div.col-md-8") != null) ? document.querySelector("body > div > div.mt-3 > div > div.col-md-8").children : [];
let NewsData = [];
for (let i = 0; i < Temp.length; i += 2) {
let Title = Temp[i].children[0].innerText;
let Time = 0;
if (Temp[i].children[1] != null) {
Time = Temp[i].children[1].innerText;
}
let Body = Temp[i + 1].innerHTML;
NewsData.push({"Title": Title, "Time": new Date(Time), "Body": Body});
}
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-8") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-8").innerHTML = "";
for (let i = 0; i < NewsData.length; i++) {
let NewsRow = document.createElement("div");
NewsRow.className = "cnt-row";
let NewsRowHead = document.createElement("div");
NewsRowHead.className = "cnt-row-head title";
NewsRowHead.innerText = NewsData[i].Title;
if (NewsData[i].Time != 0) {
NewsRowHead.innerHTML += "" + NewsData[i].Time.toLocaleDateString() + " ";
}
NewsRow.appendChild(NewsRowHead);
let NewsRowBody = document.createElement("div");
NewsRowBody.className = "cnt-row-body";
NewsRowBody.innerHTML = NewsData[i].Body;
NewsRow.appendChild(NewsRowBody);
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-8") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-8").appendChild(NewsRow);
}
let CountDownData = (document.querySelector("#countdown_list") != null) ? document.querySelector("#countdown_list").innerHTML : "";
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-4") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-4").innerHTML = ``;
let Tables = document.getElementsByTagName("table");
for (let i = 0; i < Tables.length; i++) {
TidyTable(Tables[i]);
}
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-4") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-4").innerHTML += ``;
RequestAPI("GetNotice", {}, (Response) => {
if (Response.Success) {
if (document.querySelector("body > div.container > div > div > div.col-md-4 > div:nth-child(2) > div.cnt-row-body") != null) document.querySelector("body > div.container > div > div > div.col-md-4 > div:nth-child(2) > div.cnt-row-body").innerHTML = marked.parse(Response.Data["Notice"]).replaceAll(/@([a-zA-Z0-9]+)/g, `@ $1 `);
RenderMathJax();
let UsernameElements = document.getElementsByClassName("Usernames");
for (let i = 0; i < UsernameElements.length; i++) {
GetUsernameHTML(UsernameElements[i], UsernameElements[i].innerText, true);
}
} else {
if (document.querySelector("body > div.container > div > div > div.col-md-4 > div:nth-child(2) > div.cnt-row-body") != null) document.querySelector("body > div.container > div > div > div.col-md-4 > div:nth-child(2) > div.cnt-row-body").innerHTML = "加载失败: " + Response.Message;
}
});
}
} else if (location.pathname == "/problemset.php") {
if (UtilityEnabled("Translate")) {
if (document.querySelector("body > div > div.mt-3 > center > table:nth-child(2) > tbody > tr > td:nth-child(2) > form > input") != null) document.querySelector("body > div > div.mt-3 > center > table:nth-child(2) > tbody > tr > td:nth-child(2) > form > input").placeholder = "题目编号";
if (document.querySelector("body > div > div.mt-3 > center > table:nth-child(2) > tbody > tr > td:nth-child(2) > form > button") != null) document.querySelector("body > div > div.mt-3 > center > table:nth-child(2) > tbody > tr > td:nth-child(2) > form > button").innerText = "确认";
if (document.querySelector("body > div > div.mt-3 > center > table:nth-child(2) > tbody > tr > td:nth-child(3) > form > input") != null) document.querySelector("body > div > div.mt-3 > center > table:nth-child(2) > tbody > tr > td:nth-child(3) > form > input").placeholder = "标题或内容";
if (document.querySelector("#problemset > thead > tr > th:nth-child(1)") != null) document.querySelector("#problemset > thead > tr > th:nth-child(1)").innerText = "状态";
}
if (UtilityEnabled("ResetType")) {
if (document.querySelector("#problemset > thead > tr > th:nth-child(1)") != null) document.querySelector("#problemset > thead > tr > th:nth-child(1)").style.width = "5%";
if (document.querySelector("#problemset > thead > tr > th:nth-child(2)") != null) document.querySelector("#problemset > thead > tr > th:nth-child(2)").style.width = "10%";
if (document.querySelector("#problemset > thead > tr > th:nth-child(3)") != null) document.querySelector("#problemset > thead > tr > th:nth-child(3)").style.width = "75%";
if (document.querySelector("#problemset > thead > tr > th:nth-child(4)") != null) document.querySelector("#problemset > thead > tr > th:nth-child(4)").style.width = "5%";
if (document.querySelector("#problemset > thead > tr > th:nth-child(5)") != null) document.querySelector("#problemset > thead > tr > th:nth-child(5)").style.width = "5%";
}
if (document.querySelector("body > div > div.mt-3 > center > table:nth-child(2)") != null) document.querySelector("body > div > div.mt-3 > center > table:nth-child(2)").outerHTML = `
`;
if (SearchParams.get("search") != null) {
if (document.querySelector("body > div > div.mt-3 > center > div > div:nth-child(3) > form > input") != null) document.querySelector("body > div > div.mt-3 > center > div > div:nth-child(3) > form > input").value = SearchParams.get("search");
}
let Temp = (document.querySelector("#problemset") != null) ? document.querySelector("#problemset").rows : [];
for (let i = 1; i < Temp.length; i++) {
localStorage.setItem("UserScript-Problem-" + Temp[i].children[1].innerText + "-Name", Temp[i].children[2].innerText);
}
} else if (location.pathname == "/problem.php") {
let transZhEn = document.getElementById("lang_cn_to_en");
let transEnZh = document.getElementById("lang_en_to_cn");
if (transZhEn !== null) transZhEn.remove();
if (transEnZh !== null) transEnZh.remove();
await RenderMathJax();
if (document.querySelector("body > div > div.mt-3 > h2") != null) {
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = "没有此题目或题目对你不可见";
setTimeout(() => {
location.href = "https://www.xmoj.tech/problemset.php";
}, 1000);
} else {
let PID = SearchParams.get("id");
if (document.querySelector("body > div > div.mt-3 > center") != null && document.querySelector("body > div > div.mt-3 > center").lastElementChild !== null) {
document.querySelector("body > div > div.mt-3 > center").lastElementChild.style.marginLeft = "10px";
}
//修复提交按钮
const links = document.querySelectorAll('.mt-3 > center:nth-child(1) > a');
const SubmitLink = Array.from(links).find(a => a.textContent.trim() === '提交');
let SubmitButton = document.createElement('button');
SubmitButton.id = 'SubmitButton';
SubmitButton.className = 'btn btn-outline-secondary';
SubmitButton.textContent = '提交';
SubmitButton.href = SubmitLink.href;
SubmitButton.onclick = function () {
window.location.href = SubmitLink.href;
console.log(SubmitLink.href);
};
// Replace the element with the button
SubmitLink.parentNode.replaceChild(SubmitButton, SubmitLink);
// Remove the button's outer []
let str = (document.querySelector('.mt-3 > center:nth-child(1)') != null) ? document.querySelector('.mt-3 > center:nth-child(1)').innerHTML : "";
let target = SubmitButton.outerHTML;
if (document.querySelector('.mt-3 > center:nth-child(1)') != null) document.querySelector('.mt-3 > center:nth-child(1)').innerHTML = str.replace(new RegExp(`(.?)${target}(.?)`, 'g'), target);
if (document.querySelector('html body.placeholder-glow div.container div.mt-3 center button#SubmitButton.btn.btn-outline-secondary') != null) document.querySelector('html body.placeholder-glow div.container div.mt-3 center button#SubmitButton.btn.btn-outline-secondary').onclick = function () {
window.location.href = SubmitLink.href;
console.log(SubmitLink.href);
};
var Temp = document.querySelectorAll(".sampledata");
for (var i = 0; i < Temp.length; i++) {
Temp[i].parentElement.className = "card";
}
if (UtilityEnabled("RemoveUseless")) {
if (document.querySelector("h2.lang_en") != null) document.querySelector("h2.lang_en").remove();
document.getElementsByTagName("center")[1].remove();
}
if (UtilityEnabled("CopySamples")) {
$(".copy-btn").click((Event) => {
let CurrentButton = $(Event.currentTarget);
let span = CurrentButton.parent().last().find(".sampledata");
if (!span.length) {
CurrentButton.text("未找到代码块").addClass("done");
setTimeout(() => {
$(".copy-btn").text("复制").removeClass("done");
}, 1000);
return;
}
GM_setClipboard(span.text());
CurrentButton.text("复制成功").addClass("done");
setTimeout(() => {
$(".copy-btn").text("复制").removeClass("done");
}, 1000);
//document.body.removeChild(textarea[0]);
});
}
let IOFileElement = document.querySelector("body > div > div.mt-3 > center > h3");
if (IOFileElement != null) {
while (IOFileElement.childNodes.length >= 1) {
IOFileElement.parentNode.insertBefore(IOFileElement.childNodes[0], IOFileElement);
}
IOFileElement.parentNode.insertBefore(document.createElement("br"), IOFileElement);
IOFileElement.remove();
let Temp = (document.querySelector("body > div > div.mt-3 > center") != null) ? document.querySelector("body > div > div.mt-3 > center").childNodes[2].data.trim() : "";
let IOFilename = Temp.substring(0, Temp.length - 3);
localStorage.setItem("UserScript-Problem-" + PID + "-IOFilename", IOFilename);
}
if (UtilityEnabled("CopyMD")) {
await fetch(location.href).then((Response) => {
return Response.text();
}).then((Response) => {
let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
InitializeProblemMarkdownCopy(document.querySelector(".mt-3") || document,
ParsedDocument.querySelector(".jumbotron") || ParsedDocument);
});
}
if (UtilityEnabled("Discussion")) {
let DiscussButton = document.createElement("button");
DiscussButton.className = "btn btn-outline-secondary position-relative";
DiscussButton.innerHTML = `讨论`;
DiscussButton.style.marginLeft = "10px";
DiscussButton.type = "button";
DiscussButton.addEventListener("click", () => {
open("https://www.xmoj.tech/discuss3/discuss.php?pid=" + PID, "_blank");
});
if (document.querySelector("body > div > div.mt-3 > center") != null) document.querySelector("body > div > div.mt-3 > center").appendChild(DiscussButton);
let UnreadBadge = document.createElement("span");
UnreadBadge.className = "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger";
UnreadBadge.style.display = "none";
DiscussButton.appendChild(UnreadBadge);
let RefreshCount = () => {
RequestAPI("GetPostCount", {
"ProblemID": Number(PID)
}, (Response) => {
if (Response.Success) {
if (Response.Data.DiscussCount != 0) {
UnreadBadge.innerText = Response.Data.DiscussCount;
UnreadBadge.style.display = "";
}
}
});
};
RefreshCount();
addEventListener("focus", RefreshCount);
}
let Tables = document.getElementsByTagName("table");
for (let i = 0; i < Tables.length; i++) {
TidyTable(Tables[i]);
}
}
Style.innerHTML += "code, kbd, pre, samp {";
Style.innerHTML += " font-family: monospace, Consolas, 'Courier New';";
Style.innerHTML += " font-size: 1rem;";
Style.innerHTML += "}";
Style.innerHTML += "pre {";
Style.innerHTML += " padding: 0.3em 0.5em;";
Style.innerHTML += " margin: 0.5em 0;";
Style.innerHTML += "}";
Style.innerHTML += ".in-out {";
Style.innerHTML += " overflow: hidden;";
Style.innerHTML += " display: flex;";
Style.innerHTML += " padding: 0.5em 0;";
Style.innerHTML += "}";
Style.innerHTML += ".in-out .in-out-item {";
Style.innerHTML += " flex: 1;";
Style.innerHTML += " overflow: hidden;";
Style.innerHTML += "}";
Style.innerHTML += ".cnt-row .title {";
Style.innerHTML += " font-weight: bolder;";
Style.innerHTML += " font-size: 1.1rem;";
Style.innerHTML += "}";
Style.innerHTML += ".cnt-row .content {";
Style.innerHTML += " overflow: hidden;";
Style.innerHTML += "}";
Style.innerHTML += "a.copy-btn {";
Style.innerHTML += " float: right;";
Style.innerHTML += " padding: 0 0.4em;";
Style.innerHTML += " border: 1px solid var(--bs-primary);";
Style.innerHTML += " border-radius: 3px;";
Style.innerHTML += " color: var(--bs-primary);";
Style.innerHTML += " cursor: pointer;";
Style.innerHTML += "}";
Style.innerHTML += "a.copy-btn:hover {";
Style.innerHTML += " background-color: var(--bs-secondary-bg);";
Style.innerHTML += "}";
Style.innerHTML += "a.done, a.done:hover {";
Style.innerHTML += " background-color: var(--bs-primary);";
Style.innerHTML += " color: white;";
Style.innerHTML += "}";
} else if (location.pathname == "/status.php") {
if (SearchParams.get("ByUserScript") == null) {
document.title = "提交状态";
if (document.querySelector("body > script:nth-child(5)") != null) document.querySelector("body > script:nth-child(5)").remove();
if (UtilityEnabled("NewBootstrap")) {
var checkNum = function(str) {
var patrn = /^[0-9]{1,20}$/;
var ans = true;
if (!patrn.exec(str)) ans = false;
return ans;
}
const params = new URL(location.href).searchParams;
let CurrentProblemId = checkNum(params.get("problem_id")) ? Number(params.get("problem_id")) : "";
let CurrentLanguageParam = params.get("language");
let CurrentLanguage = checkNum(CurrentLanguageParam) && -1 <= CurrentLanguageParam && CurrentLanguageParam <= 2 ? Number(CurrentLanguageParam) : "-1";
let CurrentJresultParam = params.get("jresult");
let CurrentJresult = checkNum(CurrentJresultParam) && -1 <= CurrentJresultParam && CurrentJresultParam <= 11 ? Number(CurrentJresultParam) : "-1";
if (document.querySelector("#simform") != null) document.querySelector("#simform").outerHTML = ``;
var selectElement = document.getElementById('problem_id');
selectElement.value = CurrentProblemId;
selectElement = document.getElementById('language');
selectElement.value = CurrentLanguage;
selectElement = document.getElementById('jresult');
selectElement.value = CurrentJresult;
}
if (UtilityEnabled("ImproveACRate")) {
let ImproveACRateButton = document.createElement("button");
if (document.querySelector("body > div.container > div > div.input-append") != null) document.querySelector("body > div.container > div > div.input-append").appendChild(ImproveACRateButton);
ImproveACRateButton.className = "btn btn-outline-secondary";
ImproveACRateButton.innerText = "提高正确率";
ImproveACRateButton.disabled = true;
let ACProblems = [];
fetch("https://www.xmoj.tech/userinfo.php?user=" + CurrentUsername)
.then((Response) => {
return Response.text();
}).then((Response) => {
let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
if (ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)") != null && ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)") != null) ImproveACRateButton.innerText += "(" + (parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)").innerText) / parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)").innerText) * 100).toFixed(2) + "%)";
let Temp = (ParsedDocument.querySelector("#statics > tbody > tr:nth-child(2) > td:nth-child(3) > script") != null) ? ParsedDocument.querySelector("#statics > tbody > tr:nth-child(2) > td:nth-child(3) > script").innerText.split("\n")[5].split(";") : [];
for (let i = 0; i < Temp.length; i++) {
ACProblems.push(Number(Temp[i].substring(2, Temp[i].indexOf(","))));
}
//没有解析到已通过的题目时保持禁用,避免用无效的题号提交
if (ACProblems.length > 0) {
ImproveACRateButton.disabled = false;
}
});
ImproveACRateButton.addEventListener("click", async () => {
ImproveACRateButton.disabled = true;
let SubmitTimes = 3;
let Count = 0;
let SubmitInterval = setInterval(async () => {
if (Count >= SubmitTimes) {
clearInterval(SubmitInterval);
location.reload();
return;
}
ImproveACRateButton.innerText = "正在提交 (" + (Count + 1) + "/" + SubmitTimes + ")";
let PID = ACProblems[Math.floor(Math.random() * ACProblems.length)];
let SID = 0;
await fetch("https://www.xmoj.tech/status.php?problem_id=" + PID + "&jresult=4")
.then((Result) => {
return Result.text();
}).then((Result) => {
let ParsedDocument = new DOMParser().parseFromString(Result, "text/html");
if (ParsedDocument.querySelector("#result-tab > tbody > tr:nth-child(1) > td:nth-child(2)") != null) SID = ParsedDocument.querySelector("#result-tab > tbody > tr:nth-child(1) > td:nth-child(2)").innerText;
});
//没有找到提交记录时跳过本次,避免提交错误的代码
if (SID == 0) {
Count++;
return;
}
let Code = "";
await fetch("https://www.xmoj.tech/getsource.php?id=" + SID)
.then((Response) => {
return Response.text();
}).then((Response) => {
Code = Response.substring(0, Response.indexOf("/**************************************************************")).trim();
});
await fetch("https://www.xmoj.tech/submit.php", {
"headers": {
"content-type": "application/x-www-form-urlencoded"
},
"referrer": "https://www.xmoj.tech/submitpage.php?id=" + PID,
"method": "POST",
"body": "id=" + PID + "&" + "language=1&" + "source=" + encodeURIComponent(Code) + "&" + "enable_O2=on"
});
Count++;
}, 1000);
});
ImproveACRateButton.style.marginBottom = ImproveACRateButton.style.marginRight = "7px";
ImproveACRateButton.style.marginRight = "7px";
}
if (UtilityEnabled("CompareSource")) {
let CompareButton = document.createElement("button");
if (document.querySelector("body > div.container > div > div.input-append") != null) document.querySelector("body > div.container > div > div.input-append").appendChild(CompareButton);
CompareButton.className = "btn btn-outline-secondary";
CompareButton.innerText = "比较提交记录";
CompareButton.addEventListener("click", () => {
location.href = "https://www.xmoj.tech/comparesource.php";
});
CompareButton.style.marginBottom = "7px";
}
if (UtilityEnabled("ResetType")) {
if (document.querySelector("#result-tab > thead > tr > th:nth-child(1)") != null) document.querySelector("#result-tab > thead > tr > th:nth-child(1)").remove();
if (document.querySelector("#result-tab > thead > tr > th:nth-child(2)") != null) document.querySelector("#result-tab > thead > tr > th:nth-child(2)").remove();
if (document.querySelector("#result-tab > thead > tr > th:nth-child(10)") != null) document.querySelector("#result-tab > thead > tr > th:nth-child(10)").innerHTML = "开启O2";
}
let Temp = (document.querySelector("#result-tab > tbody") != null) ? document.querySelector("#result-tab > tbody").childNodes : [];
let SolutionIDs = [];
for (let i = 1; i < Temp.length; i += 2) {
let SID = Number(Temp[i].childNodes[1].innerText);
SolutionIDs.push(SID);
if (UtilityEnabled("ResetType")) {
Temp[i].childNodes[0].remove();
let resubmitLink = Temp[i].childNodes[6].children[1] ?? null;
let sourceCell = Temp[i].childNodes[0];
let sourceLink = document.createElement("a");
sourceLink.href = "https://www.xmoj.tech/showsource.php?id=" + SID;
sourceLink.innerText = SID;
sourceCell.replaceChildren(sourceLink);
// Submissions with PID 0 do not have a resubmit link.
if (resubmitLink != null) {
let newResubmitLink = document.createElement("a");
newResubmitLink.href = resubmitLink.href;
newResubmitLink.innerText = "重交";
sourceCell.appendChild(document.createTextNode(" "));
sourceCell.appendChild(newResubmitLink);
}
Temp[i].childNodes[1].remove();
Temp[i].childNodes[1].children[0].removeAttribute("class");
Temp[i].childNodes[3].childNodes[0].innerText = SizeToStringSize(Temp[i].childNodes[3].childNodes[0].innerText);
Temp[i].childNodes[4].childNodes[0].innerText = TimeToStringTime(Temp[i].childNodes[4].childNodes[0].innerText);
Temp[i].childNodes[5].innerText = Temp[i].childNodes[5].childNodes[0].innerText;
Temp[i].childNodes[6].innerText = CodeSizeToStringSize(Temp[i].childNodes[6].innerText.substring(0, Temp[i].childNodes[6].innerText.length - 1));
Temp[i].childNodes[9].innerText = (Temp[i].childNodes[9].innerText == "" ? "否" : "是");
}
if (SearchParams.get("cid") === null) {
localStorage.setItem("UserScript-Solution-" + SID + "-Problem", Temp[i].childNodes[1].innerText);
} else {
localStorage.setItem("UserScript-Solution-" + SID + "-Contest", SearchParams.get("cid"));
localStorage.setItem("UserScript-Solution-" + SID + "-PID-Contest", Temp[i].childNodes[1].innerText.charAt(0));
}
}
if (UtilityEnabled("RefreshSolution")) {
let StdList = null;
let StdListReady = new Promise((Resolve) => {
RequestAPI("GetStdList", {}, async (Result) => {
if (Result.Success) {
StdList = Result.Data.StdList;
}
Resolve();
})
});
let Rows = document.getElementById("result-tab").rows;
let Points = Array();
for (let i = 1; i <= SolutionIDs.length; i++) {
Rows[i].cells[2].className = "td_result";
let SolutionID = SolutionIDs[i - 1];
if (Rows[i].cells[2].children.length == 2) {
Points[SolutionID] = Rows[i].cells[2].children[1].textContent.trim();
Rows[i].cells[2].children[1].remove();
}
Rows[i].cells[2].innerHTML += UtilityEnabled("MonochromeUI") ? " " : " ";
setTimeout(() => {
RefreshResult(SolutionID);
}, 0);
}
let RefreshResult = async (SolutionID) => {
let CurrentRow = null;
let Rows = document.getElementById("result-tab").rows;
for (let i = 0; i < SolutionIDs.length; i++) {
if (SolutionIDs[i] == SolutionID) {
CurrentRow = Rows[i + 1];
break;
}
}
await fetch("status-ajax.php?solution_id=" + SolutionID)
.then((Response) => {
return Response.text();
})
.then(async (Response) => {
let PID = 0;
if (SearchParams.get("cid") === null) {
PID = localStorage.getItem("UserScript-Solution-" + SolutionID + "-Problem");
} else {
PID = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-Problem-" + (CurrentRow.cells[1].innerText.charCodeAt(0) - 65) + "-PID");
}
let ResponseData = Response.split(",");
CurrentRow.cells[3].innerHTML = "" + SizeToStringSize(ResponseData[1]) + "
";
CurrentRow.cells[4].innerHTML = "" + TimeToStringTime(ResponseData[2]) + "
";
let TempHTML = " ";
TempHTML += judge_result[ResponseData[0]];
TempHTML += " ";
if (Points[SolutionID] != undefined) {
TempHTML += "" + Points[SolutionID] + " ";
if (Points[SolutionID].substring(0, Points[SolutionID].length - 1) >= 50) {
TempHTML += `查看标程 `;
}
}
if (ResponseData[0] < 4) {
setTimeout(() => {
RefreshResult(SolutionID)
}, 500);
TempHTML += UtilityEnabled("MonochromeUI") ? " " : " ";
} else if (ResponseData[0] == 4 && UtilityEnabled("UploadStd")) {
await StdListReady;
if (!StdList) { /* skip upload if list fetch failed */ }
else {
if (SearchParams.get("cid") == null) CurrentRow.cells[1].innerText;
let Std = StdList.find((Element) => {
return Element == Number(PID);
});
if (Std != undefined) {
TempHTML += UtilityEnabled("MonochromeUI") ? "[STD] " : "✅";
} else {
RequestAPI("UploadStd", {
"ProblemID": Number(PID),
}, (Result) => {
if (Result.Success) {
CurrentRow.cells[2].innerHTML += UtilityEnabled("MonochromeUI") ? "[OK] " : "🆗";
} else {
CurrentRow.cells[2].innerHTML += UtilityEnabled("MonochromeUI") ? "[ERR] " : "⚠️";
}
});
}
}
}
CurrentRow.cells[2].innerHTML = TempHTML;
});
};
}
}
} else if (location.pathname == "/contestrank-oi.php" && document.querySelector("#rank") == null && document.querySelector("body > div > div.mt-3") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/contestrank-oi.php") {
if (document.querySelector("#rank") == null) {
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = "比赛排名 ";
}
if (SearchParams.get("ByUserScript") == null) {
if (document.querySelector("body > div > div.mt-3 > center > h3") != null && document.querySelector("body > div > div.mt-3 > center > h3").innerText == "比赛排名") {
if (document.querySelector("#rank") != null) document.querySelector("#rank").innerText = "比赛暂时还没有排名";
} else {
if (document.querySelector("body > div > div.mt-3 > center > h3") != null) document.querySelector("body > div > div.mt-3 > center > h3").innerText = document.querySelector("body > div > div.mt-3 > center > h3").innerText.substring(document.querySelector("body > div > div.mt-3 > center > h3").innerText.indexOf(" -- ") + 4) + "(OI排名)";
let HeaderCells = document.querySelectorAll("#rank > thead > tr > *");
HeaderCells[0].innerText = "排名";
HeaderCells[1].innerText = "用户";
HeaderCells[2].innerText = "昵称";
HeaderCells[3].innerText = "AC数";
HeaderCells[4].innerText = "得分";
if (UtilityEnabled("MonochromeUI")) {
for (let j = 0; j < HeaderCells.length; j++) {
HeaderCells[j].removeAttribute("bgcolor");
HeaderCells[j].style.setProperty("background-color", "black", "important");
HeaderCells[j].style.setProperty("color", "white", "important");
let Links = HeaderCells[j].querySelectorAll("a");
for (let k = 0; k < Links.length; k++) {
Links[k].style.setProperty("color", "white", "important");
}
}
}
let RefreshOIRank = async () => {
await fetch(location.href)
.then((Response) => {
return Response.text()
})
.then(async (Response) => {
let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
TidyTable(ParsedDocument.getElementById("rank"));
let Temp = ParsedDocument.getElementById("rank").rows;
for (var i = 1; i < Temp.length; i++) {
if (UtilityEnabled("MonochromeUI")) {
Temp[i].style.backgroundColor = "";
}
let MetalCell = Temp[i].cells[0];
let Metal = document.createElement("span");
Metal.innerText = MetalCell.innerText;
Metal.className = "badge text-bg-primary";
MetalCell.innerText = "";
MetalCell.appendChild(Metal);
GetUsernameHTML(Temp[i].cells[1], Temp[i].cells[1].innerText);
Temp[i].cells[2].innerHTML = Temp[i].cells[2].innerText;
Temp[i].cells[3].innerHTML = Temp[i].cells[3].innerText;
if (UtilityEnabled("MonochromeUI")) {
for (let j = 0; j < 5 && j < Temp[i].cells.length; j++) {
Temp[i].cells[j].style.backgroundColor = "";
Temp[i].cells[j].style.color = "";
}
}
for (let j = 5; j < Temp[i].cells.length; j++) {
let InnerText = Temp[i].cells[j].innerText;
let BackgroundColor = Temp[i].cells[j].style.backgroundColor;
let Red = BackgroundColor.substring(4, BackgroundColor.indexOf(","));
let Green = BackgroundColor.substring(BackgroundColor.indexOf(",") + 2, BackgroundColor.lastIndexOf(","));
let Blue = BackgroundColor.substring(BackgroundColor.lastIndexOf(",") + 2, BackgroundColor.lastIndexOf(")"));
let NoData = (Red == 238 && Green == 238 && Blue == 238);
let FirstBlood = (Red == 170 && Green == 170 && Blue == 255);
let Solved = (Green == 255);
let ErrorCount = "";
if (Solved) {
ErrorCount = (Blue == 170 ? 5 : (Blue - 51) / 32);
} else {
ErrorCount = (Blue == 22 ? 15 : (170 - Blue) / 10);
}
if (NoData) {
BackgroundColor = "";
} else if (FirstBlood) {
BackgroundColor = "rgb(127, 127, 255)";
} else if (Solved) {
BackgroundColor = "rgba(0, 255, 0, " + Math.max(1 / 10 * (10 - ErrorCount), 0.2) + ")";
if (ErrorCount != 0) {
InnerText += " (" + (ErrorCount == 5 ? "4+" : ErrorCount) + ")";
}
} else {
BackgroundColor = "rgba(255, 0, 0, " + Math.min(ErrorCount / 10 + 0.2, 1) + ")";
if (ErrorCount != 0) {
InnerText += " (" + (ErrorCount == 15 ? "14+" : ErrorCount) + ")";
}
}
Temp[i].cells[j].innerHTML = InnerText;
Temp[i].cells[j].style.backgroundColor = BackgroundColor;
Temp[i].cells[j].style.color = (UtilityEnabled("DarkMode") ? "white" : "black");
}
}
if (document.querySelector("#rank > tbody") != null && ParsedDocument.querySelector("#rank > tbody") != null) document.querySelector("#rank > tbody").innerHTML = ParsedDocument.querySelector("#rank > tbody").innerHTML;
});
};
RefreshOIRank();
if (document.querySelector("body > div.container > div > center > h3") != null) document.title = document.querySelector("body > div.container > div > center > h3").innerText;
if (UtilityEnabled("AutoRefresh")) {
addEventListener("focus", RefreshOIRank);
}
}
}
Style.innerHTML += "td {";
Style.innerHTML += " white-space: nowrap;";
Style.innerHTML += "}";
if (document.querySelector("body > div.container > div > center") != null) document.querySelector("body > div.container > div > center").style.paddingBottom = "10px";
if (document.querySelector("body > div.container > div > center > a") != null) document.querySelector("body > div.container > div > center > a").style.display = "none";
if (document.querySelector("body > div.container > div > center > h3") != null) document.title = document.querySelector("body > div.container > div > center > h3").innerText;
} else if (location.pathname == "/submitpage.php" && document.querySelector("body > div > div.mt-3") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/submitpage.php") {
document.title = "提交代码: " + (SearchParams.get("id") != null ? "题目" + Number(SearchParams.get("id")) : "比赛" + Number(SearchParams.get("cid")));
// submitpage.php only renders the vcode field while the judge queue is busy, and the
// custom page below throws the server markup away, so read it before that happens.
// The queue is idle almost all of the time, so the only other way to reach this state
// is to flood the judge; localStorage UserScript-ForceCaptcha=true forces it instead.
const NativeCaptchaShown = document.querySelector("input[name='vcode']") != null ||
document.querySelector("img#vcode") != null ||
localStorage.getItem("UserScript-ForceCaptcha") === "true";
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = `
打开O2开关
`;
(function() {
const _header = document.getElementById('_submitPageHeader');
const _h3 = document.createElement('h3');
_h3.textContent = '提交代码';
_header.appendChild(_h3);
if (SearchParams.get("id") != null) {
_header.appendChild(document.createTextNode('题目'));
const _idSpan = document.createElement('span');
_idSpan.className = 'blue';
_idSpan.textContent = String(Number(SearchParams.get("id")));
_header.appendChild(_idSpan);
} else {
_header.appendChild(document.createTextNode('比赛'));
const _cidSpan = document.createElement('span');
_cidSpan.className = 'blue';
_cidSpan.textContent = String(Number(SearchParams.get("cid")));
_header.appendChild(_cidSpan);
_header.appendChild(document.createTextNode('\u2003题目')); // \u2003 = em space
const _pidSpan = document.createElement('span');
_pidSpan.className = 'blue';
_pidSpan.textContent = String.fromCharCode(65 + parseInt(SearchParams.get("pid")));
_header.appendChild(_pidSpan);
}
})();
if (UtilityEnabled("AutoO2")) {
if (document.querySelector("#enable_O2") != null) document.querySelector("#enable_O2").checked = true;
}
const getSubmitStorageKey = () => (SearchParams.get("id") != null ? ('XMOJ-Submit-id-' + SearchParams.get("id")) : ('XMOJ-Submit-cid-' + SearchParams.get("cid") + '-pid-' + SearchParams.get("pid")));
let CodeMirrorElement;
const editorOptions = {
language: 'cpp',
value: '',
automaticLayout: true,
theme: (UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'),
minimap: { enabled: false },
lineNumbers: 'on',
tabSize: 4,
localStorageKey: getSubmitStorageKey(),
// enable filling from header bottom to window bottom by default
fitToViewport: true,
bottomOffset: 8
};
try {
CodeMirrorElement = await createMonacoEditor('MonacoEditor', editorOptions);
try {
CodeMirrorElement._monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function() { Submit.click(); });
CodeMirrorElement._monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Space, function() { CodeMirrorElement._monacoEditor.trigger('keyboard', 'editor.action.triggerSuggest', {}); });
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
if (!editorOptions.fitToViewport) CodeMirrorElement.setSize("100%", "550px");
CodeMirrorElement.getWrapperElement().style.border = UtilityEnabled("MonochromeUI") ? "2px solid var(--mono-black)" : "1px solid #ddd";
document.getElementById("loadEditor").remove();
} catch (e) {
const _fallbackTA = document.getElementById('CodeInput');
document.getElementById('MonacoEditor').style.display = 'none';
_fallbackTA.style.display = '';
_fallbackTA.style.width = '100%';
if (editorOptions.fitToViewport) {
try {
const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header');
const top = header ? header.getBoundingClientRect().bottom : 0;
const bottomOffset = Number(editorOptions.bottomOffset || 0);
const available = Math.max(80, window.innerHeight - top - bottomOffset);
_fallbackTA.style.height = available + 'px';
} catch (err) { _fallbackTA.style.height = '550px'; }
} else {
_fallbackTA.style.height = '550px';
}
const _fallbackKey = getSubmitStorageKey();
try { const _saved = localStorage.getItem(_fallbackKey); if (_saved !== null && _saved !== 'null') _fallbackTA.value = _saved; } catch (_e) {}
let _fallbackTimer = null;
const _fallbackSave = () => { try { localStorage.setItem(_fallbackKey, _fallbackTA.value); } catch (_e) {} };
_fallbackTA.addEventListener('input', () => { if (_fallbackTimer) clearTimeout(_fallbackTimer); _fallbackTimer = setTimeout(_fallbackSave, 500); });
_fallbackTA.addEventListener('keydown', (e) => {
const key = e.key || e.keyCode;
const isEnter = key === 'Enter' || key === 13;
if (isEnter && e.ctrlKey) {
e.preventDefault();
if (typeof Submit !== 'undefined' && Submit && typeof Submit.click === 'function') {
Submit.click();
}
}
});
CodeMirrorElement = {
getValue: () => _fallbackTA.value,
setValue: (v) => { _fallbackTA.value = v; },
setSize: (w, h) => { if (w) _fallbackTA.style.width = w; if (h) _fallbackTA.style.height = h; },
getWrapperElement: () => _fallbackTA,
focus: () => _fallbackTA.focus(),
showFind: () => {},
goToLine: () => {},
selectRange: () => {},
saveToLocal: _fallbackSave,
localStorageKey: _fallbackKey,
_monacoEditor: null
};
}
if (SearchParams.get("sid") !== null) {
await fetch("https://www.xmoj.tech/getsource.php?id=" + SearchParams.get("sid"))
.then((Response) => {
return Response.text()
})
.then((Response) => {
CodeMirrorElement.setValue(Response.substring(0, Response.indexOf("/**************************************************************")).trim());
});
}
// vcode.php writes the expected answer into the PHP session every time it is
// requested, so the challenge must be downloaded exactly once and that same copy
// shown to the user: letting the load it separately would leave the picture on
// screen one challenge behind whatever the session actually expects.
let CaptchaObjectURL = null;
let CaptchaRequestID = 0;
const SetCaptchaStatus = (Message) => {
if (document.querySelector("#CaptchaStatus") != null) document.querySelector("#CaptchaStatus").innerText = Message;
};
// Byte 6-7 of a GIF header is the little endian width. vcode.php sizes the image as
// 15px per character, so 60px means the easy 4 digit challenge while a wider image is
// the 8 character alphanumeric one the server switches to after a failed attempt.
const GetCaptchaLength = async (ImageBlob) => {
const Header = new Uint8Array(await ImageBlob.slice(0, 8).arrayBuffer());
return Math.round((Header[6] | (Header[7] << 8)) / 15);
};
const ParsedCaptchaGlyphs = Object.entries(CaptchaGlyphs).map(([Digit, Bitmap]) => {
const Rows = Bitmap.split("|");
return {Digit: Digit, Rows: Rows, Width: Rows[0].length, Height: Rows.length};
});
// vcode.php fills the background with one random colour and draws the text in its exact
// inverse, so the most common pixel identifies the background and 255 minus it is the
// ink. Noise dots are a third random colour and mostly fall outside that tolerance. The
// 1px black border is skipped because a near white background makes it match the ink.
const BuildCaptchaMask = (Pixels, Width, Height) => {
const Counts = new Map();
for (let Index = 0; Index < Pixels.length; Index += 4) {
const Key = (Pixels[Index] << 16) | (Pixels[Index + 1] << 8) | Pixels[Index + 2];
Counts.set(Key, (Counts.get(Key) || 0) + 1);
}
let Background = 0, BestCount = -1;
Counts.forEach((Count, Key) => {
if (Count > BestCount) { BestCount = Count; Background = Key; }
});
const InkRed = 255 - ((Background >> 16) & 255);
const InkGreen = 255 - ((Background >> 8) & 255);
const InkBlue = 255 - (Background & 255);
const Mask = [];
for (let Row = 0; Row < Height; Row++) {
const Line = new Uint8Array(Width);
for (let Column = 0; Column < Width; Column++) {
if (Row === 0 || Column === 0 || Row === Height - 1 || Column === Width - 1) continue;
const Index = (Row * Width + Column) * 4;
const Distance = Math.abs(Pixels[Index] - InkRed) +
Math.abs(Pixels[Index + 1] - InkGreen) +
Math.abs(Pixels[Index + 2] - InkBlue);
if (Distance < 90) Line[Column] = 1;
}
Mask.push(Line);
}
return Mask;
};
// Digits never touch in this font, so inked columns split cleanly into one run each.
const SplitCaptchaColumns = (Mask, Width, Height) => {
const Groups = [];
let Current = null;
for (let Column = 0; Column < Width; Column++) {
let Inked = false;
for (let Row = 0; Row < Height && !Inked; Row++) if (Mask[Row][Column]) Inked = true;
if (Inked) {
if (Current !== null && Column - Current[Current.length - 1] <= 1) Current.push(Column);
else { if (Current !== null) Groups.push(Current); Current = [Column]; }
}
}
if (Current !== null) Groups.push(Current);
return Groups;
};
// Rewarding covered ink alone lets a noisy 0 score as well as a 9, so ink the glyph does
// not explain is penalised too. The window shifts by a couple of pixels either way to
// absorb noise that has stuck to the edge of a digit and moved its bounding box.
const MatchCaptchaGlyph = (Mask, Group, Width, Height) => {
let Top = Height;
for (let Row = 0; Row < Height; Row++) {
for (const Column of Group) if (Mask[Row][Column]) { Top = Math.min(Top, Row); break; }
}
const Left = Group[0];
const Scores = ParsedCaptchaGlyphs.map((Glyph) => {
let Best = -Infinity;
for (let OffsetY = -2; OffsetY <= 2; OffsetY++) {
for (let OffsetX = -2; OffsetX <= 2; OffsetX++) {
let Score = 0;
for (let Row = 0; Row < Glyph.Height; Row++) {
for (let Column = 0; Column < Glyph.Width; Column++) {
const SampleRow = Top + OffsetY + Row;
const SampleColumn = Left + OffsetX + Column;
const Inked = SampleRow >= 0 && SampleRow < Height && SampleColumn >= 0 &&
SampleColumn < Width && Mask[SampleRow][SampleColumn] === 1;
if (Glyph.Rows[Row][Column] === "1") Score += Inked ? 1 : -2;
else if (Inked) Score -= 1;
}
}
Best = Math.max(Best, Score);
}
}
return {Digit: Glyph.Digit, Score: Best};
}).sort((Left, Right) => Right.Score - Left.Score);
return {Digit: Scores[0].Digit, Margin: Scores[0].Score - Scores[1].Score};
};
// Returns null rather than a guess whenever the image does not split into exactly four
// digits or any one of them is a close call, so a wrong answer never reaches submit.php.
const SolveCaptcha = async (ImageBlob) => {
try {
const Bitmap = await createImageBitmap(ImageBlob);
const Canvas = document.createElement("canvas");
Canvas.width = Bitmap.width;
Canvas.height = Bitmap.height;
const Context = Canvas.getContext("2d", {willReadFrequently: true});
Context.drawImage(Bitmap, 0, 0);
const Pixels = Context.getImageData(0, 0, Bitmap.width, Bitmap.height).data;
const Mask = BuildCaptchaMask(Pixels, Bitmap.width, Bitmap.height);
const Groups = SplitCaptchaColumns(Mask, Bitmap.width, Bitmap.height);
if (Groups.length !== 4) {
if (UtilityEnabled("DebugMode")) {
console.log("Captcha split into", Groups.length, "glyphs, not reading it");
}
return null;
}
let Answer = "";
for (const Group of Groups) {
const Match = MatchCaptchaGlyph(Mask, Group, Bitmap.width, Bitmap.height);
if (Match.Margin < CaptchaMinMargin) {
if (UtilityEnabled("DebugMode")) {
console.log("Captcha glyph too close to call, margin", Match.Margin);
}
return null;
}
Answer += Match.Digit;
}
if (UtilityEnabled("DebugMode")) {
console.log("Captcha read locally as", Answer);
}
return Answer;
} catch (e) {
console.error(e);
return null;
}
};
const RefreshCaptcha = async (StatusMessage) => {
const RequestID = ++CaptchaRequestID;
const CaptchaInput = document.querySelector("#vcode");
if (CaptchaInput == null) return;
if (document.querySelector("#CaptchaElement") != null) document.querySelector("#CaptchaElement").style.display = "block";
CaptchaInput.value = "";
SetCaptchaStatus(StatusMessage || "");
// Only a submitted answer counts against the session, so fetching another image is
// free: when the solver declines one it is cheaper to ask for a fresh challenge than
// to make the user type it. Each fetch replaces the answer the session expects, so
// this stops if the user starts typing against the image on screen before the next one.
for (let Attempt = 1; Attempt <= CaptchaMaxAttempts; Attempt++) {
if (Attempt > 1) {
await new Promise((Resolve) => setTimeout(Resolve, 300));
if (RequestID !== CaptchaRequestID || CaptchaInput.value !== "") return;
SetCaptchaStatus("这张看不太准,正在自动换一张(" + Attempt + "/" + CaptchaMaxAttempts + ")");
}
// From the moment vcode.php is requested the image on screen no longer matches the
// session, so anything typed from it would be a guaranteed wrong answer. The box is
// locked until the new image replaces it. A newer call that takes over leaves the
// lock to be released by that call rather than by this one.
CaptchaInput.readOnly = true;
let ImageBlob;
try {
const CaptchaResponse = await fetch("https://www.xmoj.tech/vcode.php?" + Math.random(), {cache: "no-store"});
ImageBlob = await CaptchaResponse.blob();
} catch (e) {
console.error(e);
if (RequestID === CaptchaRequestID) {
CaptchaInput.readOnly = false;
SetCaptchaStatus("验证码加载失败,请点击图片重试");
}
return;
}
if (RequestID !== CaptchaRequestID) return;
if (CaptchaObjectURL !== null) URL.revokeObjectURL(CaptchaObjectURL);
CaptchaObjectURL = URL.createObjectURL(ImageBlob);
if (document.querySelector("#CaptchaImage") != null) document.querySelector("#CaptchaImage").src = CaptchaObjectURL;
CaptchaInput.value = "";
CaptchaInput.readOnly = false;
if (!UtilityEnabled("AutoCaptcha")) return;
let CaptchaLength = 4;
try {
CaptchaLength = await GetCaptchaLength(ImageBlob);
} catch (e) {
console.error(e);
}
if (CaptchaLength !== 4) {
SetCaptchaStatus("本次为 " + CaptchaLength + " 位字母验证码,请手动输入");
return;
}
const Answer = await SolveCaptcha(ImageBlob);
if (RequestID !== CaptchaRequestID) return;
// Never overwrite what the user has already started typing.
if (CaptchaInput.value !== "") return;
if (Answer !== null) {
CaptchaInput.value = Answer;
SetCaptchaStatus("已自动识别,若与图片不符请手动修改");
return;
}
}
SetCaptchaStatus("连续几张都看不太准,请手动输入,或点击图片换一张");
};
if (document.querySelector("#CaptchaImage") != null) document.querySelector("#CaptchaImage").addEventListener("click", () => {
RefreshCaptcha("");
});
if (document.querySelector("#vcode") != null) document.querySelector("#vcode").addEventListener("keydown", (KeyEvent) => {
if (KeyEvent.key === "Enter") {
KeyEvent.preventDefault();
Submit.click();
}
});
// submit.php ignores an unexpected vcode field, so sending it whenever the user has
// one costs nothing and covers the case where the queue grew past the enforcement
// threshold after this page was rendered.
const GetCaptchaParameter = () => {
const CaptchaValue = (document.querySelector("#vcode") != null) ? document.querySelector("#vcode").value.trim() : "";
return CaptchaValue === "" ? "" : "&vcode=" + encodeURIComponent(CaptchaValue);
};
// Submitting a blank answer makes the server mark the session as having failed the
// check, which swaps the 4 digit challenge for an 8 character one until the session
// ends. This has to be re-checked immediately before the POST rather than only when
// 提交 is pressed: a warning leaves 强制提交 on screen, and the captcha can be cleared
// in between by refreshing the image or emptying the box by hand.
const CaptchaIsMissing = () => {
if (document.querySelector("#CaptchaElement") != null && document.querySelector("#CaptchaElement").style.display === "none") return false;
if (document.querySelector("#vcode") != null && document.querySelector("#vcode").value.trim() !== "") return false;
PassCheck.style.display = "none";
ErrorElement.style.display = "block";
ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = "当前评测队列繁忙,请先填写上方的验证码。";
Submit.disabled = false;
Submit.value = "提交";
if (document.querySelector("#vcode") != null) document.querySelector("#vcode").focus();
return true;
};
if (NativeCaptchaShown) {
RefreshCaptcha("");
}
const ShowSubmitStatus = (Message) => {
ErrorElement.style.display = "block";
ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = Message;
console.log(Message);
};
// Credit: https://github.com/boomzero/quicksubmit/blob/main/index.ts
// Also licensed under GPL-3.0
// The contest is over, so submit.php refuses the cid+pid submission. Look up the
// real problem number on the contest page and submit to that problem instead.
// Returns {Success, Message}; Success means a submission record was created.
async function SubmitToEndedContestProblem(Source, O2Switch, ReportStatus) {
const ContestID = new URL(location.href).searchParams.get("cid");
const ProblemNumber = new URL(location.href).searchParams.get("pid");
let RealPID;
try {
const problem = await GetContestProblemData(ContestID, ProblemNumber);
RealPID = String(problem.problemId);
} catch (e) {
console.error(e);
return {Success: false, Message: "无法读取比赛题目,未能找到原题题号!"};
}
if (RealPID === undefined || RealPID === "") {
return {Success: false, Message: "无法确定原题题号,请手动前往原题提交!"};
}
// XMOJ rejects anything submitted within a few seconds of the previous submission
// with 请勿重复提交, so wait the cooldown out instead of silently dropping the code.
for (let Attempt = 0; Attempt < 5; Attempt++) {
// The captcha field stays editable while we fetch the contest API and while we wait
// out a cooldown, so re-check it before every POST rather than trusting the
// check the 提交 handler did. Sending a blank answer would burn the session.
// CaptchaIsMissing() already shows its own message and restores the button.
if (CaptchaIsMissing()) return {Success: false, Handled: true, Message: ""};
ReportStatus("比赛已结束, 正在尝试向题目 " + RealPID + " 提交");
let SubmitPage = "";
try {
const SubmitResponse = await fetch("https://www.xmoj.tech/submit.php", {
"headers": {
"content-type": "application/x-www-form-urlencoded"
},
"referrer": location.href,
"method": "POST",
"body": "id=" + RealPID + "&language=1&" + "source=" + encodeURIComponent(Source) + O2Switch + GetCaptchaParameter()
});
if (SubmitResponse.redirected) {
location.href = SubmitResponse.url;
return {Success: true, Message: ""};
}
SubmitPage = await SubmitResponse.text();
} catch (e) {
console.error(e);
return {Success: false, Message: "向题目 " + RealPID + " 提交失败!网络错误,请稍后重试!"};
}
if (UtilityEnabled("DebugMode")) {
console.log("Direct submission response:", SubmitPage);
}
// Retrying cannot help here: the answer that was sent has already been spent.
if (SubmitPage.indexOf("验证码错误") !== -1) {
await RefreshCaptcha("");
if (document.querySelector("#vcode") != null) document.querySelector("#vcode").focus();
return {Success: false, Message: "验证码错误!请填写上方的验证码后重新提交。"};
}
if (SubmitPage.indexOf("请勿重复提交") === -1) {
let ServerMessage = "";
try {
const MessageElement = new DOMParser().parseFromString(SubmitPage, "text/html").querySelector(".jumbotron");
if (MessageElement !== null) ServerMessage = MessageElement.textContent.trim();
} catch (e) {
console.error(e);
}
return {Success: false, Message: "向题目 " + RealPID + " 提交失败!" + (ServerMessage === "" ? "请关闭脚本后重试!" : ServerMessage)};
}
ReportStatus("提交过于频繁, 3 秒后重新尝试向题目 " + RealPID + " 提交");
await new Promise((Resolve) => setTimeout(Resolve, 3000));
}
return {Success: false, Message: "向题目 " + RealPID + " 提交失败!提交过于频繁,请稍后手动重试!"};
}
PassCheck.addEventListener("click", async () => {
// This is the request that actually reaches submit.php, so the captcha is checked
// here as well as in the 提交 handler above.
if (CaptchaIsMissing()) return;
ErrorElement.style.display = "none";
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").disabled = true;
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").value = "正在提交...";
let o2Switch = "&enable_O2=on";
if (document.querySelector("#enable_O2") != null && !document.querySelector("#enable_O2").checked) o2Switch = "";
await fetch("https://www.xmoj.tech/submit.php", {
"headers": {
"content-type": "application/x-www-form-urlencoded"
},
"referrer": location.href,
"method": "POST",
"body": (SearchParams.get("id") != null ? "id=" + SearchParams.get("id") : "cid=" + SearchParams.get("cid") + "&pid=" + SearchParams.get("pid")) + "&language=1&" + "source=" + encodeURIComponent(CodeMirrorElement.getValue()) + o2Switch + GetCaptchaParameter()
}).then(async (Response) => {
if (Response.redirected) {
location.href = Response.url;
} else {
const text = await Response.text();
// The queue can cross submit.php's enforcement threshold after this page
// was rendered, so the field may not have been on screen at all yet.
if (text.indexOf("验证码错误") !== -1) {
if (UtilityEnabled("DebugMode")) {
console.log("Submission rejected by captcha check.");
}
await RefreshCaptcha("");
ErrorElement.style.display = "block";
ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = "验证码错误!请填写上方的验证码后重新提交。";
Submit.disabled = false;
Submit.value = "提交";
if (document.querySelector("#vcode") != null) document.querySelector("#vcode").focus();
return;
}
if (UtilityEnabled("DebugMode")) {
console.log("Submission failed! Response:", text);
}
let FailMessage = "提交失败!请关闭脚本后重试!";
if (text.indexOf("没有这个比赛!") !== -1 && SearchParams.get("pid") !== null) {
const FallbackResult = await SubmitToEndedContestProblem(CodeMirrorElement.getValue(), o2Switch, ShowSubmitStatus);
if (FallbackResult.Success || FallbackResult.Handled) {
return;
}
FailMessage = FallbackResult.Message;
}
ShowSubmitStatus(FailMessage);
Submit.disabled = false;
Submit.value = "提交";
}
})
});
Submit.addEventListener("click", async () => {
PassCheck.style.display = "none";
ErrorElement.style.display = "none";
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").disabled = true;
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").value = "正在检查...";
if (CaptchaIsMissing()) return;
let Source = CodeMirrorElement.getValue();
let PID = 0;
let IOFilename = "";
if (SearchParams.get("cid") != null && SearchParams.get("pid") != null) {
PID = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-Problem-" + SearchParams.get("pid") + "-PID")
} else {
PID = SearchParams.get("id");
}
IOFilename = localStorage.getItem("UserScript-Problem-" + PID + "-IOFilename");
if (UtilityEnabled("IOFile") && IOFilename != null) {
if (Source.indexOf(IOFilename) == -1) {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = "此题输入输出文件名为" + IOFilename + ",请检查是否填错";
let freopenText = document.createElement('small');
if (UtilityEnabled("DarkMode")) freopenText.style.color = "white"; else freopenText.style.color = "black";
freopenText.textContent = '\n您也可以复制freopen语句。\n';
document.getElementById('ErrorMessage').appendChild(freopenText);
let copyFreopenButton = document.createElement("button");
copyFreopenButton.className = "btn btn-sm btn-outline-secondary copy-btn";
copyFreopenButton.innerText = "复制代码";
copyFreopenButton.style.marginLeft = "10px";
copyFreopenButton.style.marginTop = "10px";
copyFreopenButton.style.marginBottom = "10px";
copyFreopenButton.type = "button";
copyFreopenButton.addEventListener("click", () => {
navigator.clipboard.writeText('\n freopen("' + IOFilename + '.in", "r", stdin);\n freopen("' + IOFilename + '.out", "w", stdout);');
copyFreopenButton.innerText = "复制成功";
setTimeout(() => {
copyFreopenButton.innerText = "复制代码";
}, 1500);
});
document.getElementById('ErrorMessage').appendChild(copyFreopenButton);
// create a small read-only Monaco editor or fallback text for the freopen snippet
let codeHost = document.createElement('div');
codeHost.style.width = '100%';
codeHost.style.height = '38px';
codeHost.style.marginTop = '10px';
document.getElementById('ErrorMessage').appendChild(codeHost);
if (typeof monaco !== 'undefined') {
try {
const _tmpErrEditor = monaco.editor.create(codeHost, {
value: 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);',
language: 'cpp',
readOnly: true,
theme: (UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'),
automaticLayout: true,
minimap: { enabled: false },
lineNumbers: 'on'
});
window._xmoj_temp_error_editors = window._xmoj_temp_error_editors || [];
window._xmoj_temp_error_editors.push(_tmpErrEditor);
try { codeHost._monacoEditor = _tmpErrEditor; codeHost.setAttribute('data-xmoj-error-editor', '1'); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
} catch (e) {
const pre = document.createElement('pre');
pre.textContent = 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);';
codeHost.appendChild(pre);
}
} else {
const pre = document.createElement('pre');
pre.textContent = 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);';
codeHost.appendChild(pre);
}
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").disabled = false;
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").value = "提交";
return false;
} else if (RegExp("//.*freopen").test(Source)) {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = "请不要注释freopen语句";
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").disabled = false;
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").value = "提交";
return false;
}
}
if (Source == "") {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = "源代码为空";
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").disabled = false;
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").value = "提交";
return false;
}
if (UtilityEnabled("CompileError")) {
let ResponseData = await new Promise((Resolve) => {
GM_xmlhttpRequest({
method: "POST", url: "https://cppinsights.io/api/v1/transform", headers: {
"content-type": "application/json;charset=UTF-8"
}, referrer: "https://cppinsights.io/", data: JSON.stringify({
"insightsOptions": ["cpp14"], "code": Source
}), onload: (Response) => {
Resolve(Response);
}
});
});
let Response = JSON.parse(ResponseData.responseText);
if (Response.returncode) {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
ErrorMessage.innerText = "编译错误:\n" + Response.stderr.trim();
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").disabled = false;
if (document.querySelector("#Submit") != null) document.querySelector("#Submit").value = "提交";
return false;
} else {
PassCheck.click();
}
} else {
PassCheck.click();
}
});
} else if (location.pathname == "/modifypage.php" && document.querySelector("body > div > div") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/modifypage.php") {
if (SearchParams.get("ByUserScript") != null) {
document.title = "XMOJ-Script 更新日志";
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = "";
await fetch(ServerURL + "/Update.json", {cache: "no-cache"})
.then((Response) => {
return Response.json();
})
.then((Response) => {
for (let i = Object.keys(Response.UpdateHistory).length - 1; i >= 0; i--) {
let Version = Object.keys(Response.UpdateHistory)[i];
let Data = Response.UpdateHistory[Version];
let UpdateDataCard = document.createElement("div");
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").appendChild(UpdateDataCard);
UpdateDataCard.className = "card mb-3";
if (Data.Prerelease) UpdateDataCard.classList.add("text-secondary");
let UpdateDataCardBody = document.createElement("div");
UpdateDataCard.appendChild(UpdateDataCardBody);
UpdateDataCardBody.className = "card-body";
let UpdateDataCardTitle = document.createElement("h5");
UpdateDataCardBody.appendChild(UpdateDataCardTitle);
UpdateDataCardTitle.className = "card-title";
UpdateDataCardTitle.innerText = Version;
if (Data.Prerelease) {
UpdateDataCardTitle.innerHTML += "(预览版)";
}
let UpdateDataCardSubtitle = document.createElement("h6");
UpdateDataCardBody.appendChild(UpdateDataCardSubtitle);
UpdateDataCardSubtitle.className = "card-subtitle mb-2 text-muted";
UpdateDataCardSubtitle.innerHTML = GetRelativeTime(Data.UpdateDate);
let UpdateDataCardText = document.createElement("p");
UpdateDataCardBody.appendChild(UpdateDataCardText);
UpdateDataCardText.className = "card-text";
//release notes
if (Data.Notes != undefined) {
UpdateDataCardText.innerHTML = Data.Notes;
}
let UpdateDataCardList = document.createElement("ul");
UpdateDataCardText.appendChild(UpdateDataCardList);
UpdateDataCardList.className = "list-group list-group-flush";
for (let j = 0; j < Data.UpdateContents.length; j++) {
let UpdateDataCardListItem = document.createElement("li");
UpdateDataCardList.appendChild(UpdateDataCardListItem);
UpdateDataCardListItem.className = "list-group-item";
UpdateDataCardListItem.innerHTML = "(" + "#" + Data.UpdateContents[j].PR + " ) " + escapeHTML(Data.UpdateContents[j].Description);
}
let UpdateDataCardLink = document.createElement("a");
UpdateDataCardBody.appendChild(UpdateDataCardLink);
UpdateDataCardLink.className = "card-link";
UpdateDataCardLink.href = "https://github.com/XMOJ-Script-dev/XMOJ-Script/releases/tag/" + Version;
UpdateDataCardLink.target = "_blank";
UpdateDataCardLink.innerText = "查看该版本";
}
});
} else {
document.title = "修改账号";
let Nickname = document.getElementsByName("nick")[0].value;
let School = document.getElementsByName("school")[0].value;
let EmailAddress = document.getElementsByName("email")[0].value;
let CodeforcesAccount = document.getElementsByName("acc_cf")[0].value;
let AtcoderAccount = document.getElementsByName("acc_atc")[0].value;
let USACOAccount = document.getElementsByName("acc_usaco")[0].value;
let LuoguAccount = document.getElementsByName("acc_luogu")[0].value;
if (document.querySelector("body > div > div") != null) document.querySelector("body > div > div").innerHTML = `
修改
修改成功
`;
document.getElementById("Nickname").value = Nickname;
document.getElementById("School").value = School;
document.getElementById("EmailAddress").value = EmailAddress;
document.getElementById("CodeforcesAccount").value = CodeforcesAccount;
document.getElementById("AtcoderAccount").value = AtcoderAccount;
document.getElementById("USACOAccount").value = USACOAccount;
document.getElementById("LuoguAccount").value = LuoguAccount;
RequestAPI("GetBadge", {
"UserID": String(CurrentUsername)
}, (Response) => {
if (Response.Success) {
BadgeRow.style.display = "";
BadgeContent.value = Response.Data.Content;
BadgeBackgroundColor.value = Response.Data.BackgroundColor;
BadgeColor.value = Response.Data.Color;
let Temp = [];
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i).startsWith("UserScript-User-" + CurrentUsername + "-Badge-")) {
Temp.push(localStorage.key(i));
}
}
for (let i = 0; i < Temp.length; i++) {
localStorage.removeItem(Temp[i]);
}
}
});
ModifyInfo.addEventListener("click", async () => {
ModifyInfo.disabled = true;
if (ModifyInfo.querySelector("span") != null) ModifyInfo.querySelector("span").style.display = "";
ErrorElement.style.display = "none";
SuccessElement.style.display = "none";
//表单元素缺失时不提交,避免用空值覆盖用户信息
if (document.querySelector("#BadgeContent") == null || document.querySelector("#BadgeBackgroundColor") == null || document.querySelector("#BadgeColor") == null || document.querySelector("#Nickname") == null || document.querySelector("#OldPassword") == null || document.querySelector("#NewPassword") == null || document.querySelector("#NewPasswordAgain") == null || document.querySelector("#School") == null || document.querySelector("#EmailAddress") == null || document.querySelector("#CodeforcesAccount") == null || document.querySelector("#AtcoderAccount") == null || document.querySelector("#USACOAccount") == null || document.querySelector("#LuoguAccount") == null) {
ModifyInfo.disabled = false;
if (ModifyInfo.querySelector("span") != null) ModifyInfo.querySelector("span").style.display = "none";
ErrorElement.style.display = "block";
ErrorElement.innerText = "页面加载异常,请刷新后重试";
return;
}
let BadgeContent = document.querySelector("#BadgeContent").value;
let BadgeBackgroundColor = document.querySelector("#BadgeBackgroundColor").value;
let BadgeColor = document.querySelector("#BadgeColor").value;
await new Promise((Resolve) => {
RequestAPI("EditBadge", {
"UserID": String(CurrentUsername),
"Content": String(BadgeContent),
"BackgroundColor": String(BadgeBackgroundColor),
"Color": String(BadgeColor)
}, (Response) => {
if (Response.Success) {
Resolve();
} else {
ModifyInfo.disabled = false;
if (ModifyInfo.querySelector("span") != null) ModifyInfo.querySelector("span").style.display = "none";
ErrorElement.style.display = "block";
ErrorElement.innerText = Response.Message;
}
});
});
let Nickname = document.querySelector("#Nickname").value;
let OldPassword = document.querySelector("#OldPassword").value;
let NewPassword = document.querySelector("#NewPassword").value;
let NewPasswordAgain = document.querySelector("#NewPasswordAgain").value;
let School = document.querySelector("#School").value;
let EmailAddress = document.querySelector("#EmailAddress").value;
let CodeforcesAccount = document.querySelector("#CodeforcesAccount").value;
let AtcoderAccount = document.querySelector("#AtcoderAccount").value;
let USACOAccount = document.querySelector("#USACOAccount").value;
let LuoguAccount = document.querySelector("#LuoguAccount").value;
await fetch("https://www.xmoj.tech/modify.php", {
"headers": {
"content-type": "application/x-www-form-urlencoded"
},
"referrer": location.href,
"method": "POST",
"body": "nick=" + encodeURIComponent(Nickname) + "&" + "opassword=" + encodeURIComponent(OldPassword) + "&" + "npassword=" + encodeURIComponent(NewPassword) + "&" + "rptpassword=" + encodeURIComponent(NewPasswordAgain) + "&" + "school=" + encodeURIComponent(School) + "&" + "email=" + encodeURIComponent(EmailAddress) + "&" + "acc_cf=" + encodeURIComponent(CodeforcesAccount) + "&" + "acc_atc=" + encodeURIComponent(AtcoderAccount) + "&" + "acc_usaco=" + encodeURIComponent(USACOAccount) + "&" + "acc_luogu=" + encodeURIComponent(LuoguAccount)
});
ModifyInfo.disabled = false;
if (ModifyInfo.querySelector("span") != null) ModifyInfo.querySelector("span").style.display = "none";
SuccessElement.style.display = "block";
});
if (UtilityEnabled("ExportACCode")) {
let ExportACCode = document.createElement("button");
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").appendChild(ExportACCode);
ExportACCode.innerText = "导出AC代码";
ExportACCode.className = "btn btn-outline-secondary";
ExportACCode.addEventListener("click", () => {
ExportACCode.disabled = true;
ExportACCode.innerText = "正在导出...";
let Request = new XMLHttpRequest();
Request.addEventListener("readystatechange", () => {
if (Request.readyState == 4) {
if (Request.status == 200) {
let Response = Request.responseText;
let ACCode = Response.split("------------------------------------------------------\r\n");
let ScriptElement = document.createElement("script");
ScriptElement.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js";
document.head.appendChild(ScriptElement);
ScriptElement.onload = () => {
var Zip = new JSZip();
for (let i = 0; i < ACCode.length; i++) {
let CurrentCode = ACCode[i];
if (CurrentCode != "") {
let lineBreakPos = CurrentCode.search(/[\r\n]/);
if (lineBreakPos === -1) continue;
let headerLine = CurrentCode.slice(0, lineBreakPos);
let digitMatch = headerLine.match(/\d+/);
if (!digitMatch) continue;
let CurrentQuestionID = digitMatch[0];
let bodyStart = lineBreakPos + 1;
if (CurrentCode[lineBreakPos] === '\r' && CurrentCode[lineBreakPos + 1] === '\n') {
bodyStart = lineBreakPos + 2;
}
CurrentCode = CurrentCode.slice(bodyStart);
CurrentCode = CurrentCode.replaceAll("\r", "");
Zip.file(CurrentQuestionID + ".cpp", CurrentCode);
}
}
ExportACCode.innerText = "正在生成压缩包……";
Zip.generateAsync({type: "blob"})
.then(function (Content) {
saveAs(Content, "ACCodes.zip");
ExportACCode.innerText = "AC代码导出成功";
ExportACCode.disabled = false;
setTimeout(() => {
ExportACCode.innerText = "导出AC代码";
}, 1000);
});
};
} else {
ExportACCode.disabled = false;
ExportACCode.innerText = "AC代码导出失败";
setTimeout(() => {
ExportACCode.innerText = "导出AC代码";
}, 1000);
}
}
});
Request.open("GET", "https://www.xmoj.tech/export_ac_code.php", true);
Request.send();
});
}
}
} else if (location.pathname == "/userinfo.php" && document.querySelector("body > div > div") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/userinfo.php") {
if (SearchParams.get("ByUserScript") === null) {
if (UtilityEnabled("RemoveUseless")) {
let Temp = document.getElementById("submission").childNodes;
for (let i = 0; i < Temp.length; i++) {
Temp[i].remove();
}
}
if (document.querySelector("body > script:nth-child(5)") != null) eval(document.querySelector("body > script:nth-child(5)").innerHTML);
if (document.querySelector("#statics > tbody > tr:nth-child(1)") != null) document.querySelector("#statics > tbody > tr:nth-child(1)").remove();
let Temp = (document.querySelector("#statics > tbody") != null) ? document.querySelector("#statics > tbody").children : [];
for (let i = 0; i < Temp.length; i++) {
if (Temp[i].children[0] != undefined) {
if (Temp[i].children[0].innerText == "Statistics") {
Temp[i].children[0].innerText = "统计";
} else if (Temp[i].children[0].innerText == "Email:") {
Temp[i].children[0].innerText = "电子邮箱";
}
Temp[i].children[1].removeAttribute("align");
}
}
Temp = (document.querySelector("#statics > tbody > tr:nth-child(1) > td:nth-child(3)") != null) ? document.querySelector("#statics > tbody > tr:nth-child(1) > td:nth-child(3)").childNodes : [];
let ACProblems = [];
for (let i = 0; i < Temp.length; i++) {
if (Temp[i].tagName == "A" && Temp[i].href.indexOf("problem.php?id=") != -1) {
ACProblems.push(Number(Temp[i].innerText.trim()));
}
}
if (document.querySelector("#statics > tbody > tr:nth-child(1) > td:nth-child(3)") != null) document.querySelector("#statics > tbody > tr:nth-child(1) > td:nth-child(3)").remove();
let UserID, UserNick;
if (document.querySelector("#statics > caption") != null) [UserID, UserNick] = document.querySelector("#statics > caption").childNodes[0].data.trim().split("--");
if (document.querySelector("#statics > caption") != null) document.querySelector("#statics > caption").remove();
document.title = "用户 " + UserID + " 的个人中心";
let Row = document.createElement("div");
Row.className = "row";
let LeftDiv = document.createElement("div");
LeftDiv.className = "col-md-5";
Row.appendChild(LeftDiv);
let LeftTopDiv = document.createElement("div");
LeftTopDiv.className = "row mb-2";
LeftDiv.appendChild(LeftTopDiv);
let AvatarContainer = document.createElement("div");
AvatarContainer.classList.add("col-auto");
let AvatarElement = document.createElement("img");
let UserEmailHash = (await GetUserInfo(UserID)).EmailHash;
if (UserEmailHash == undefined) {
AvatarElement.src = `https://cravatar.cn/avatar/00000000000000000000000000000000?d=mp&f=y`;
} else {
AvatarElement.src = `https://cravatar.cn/avatar/${UserEmailHash}?d=retro`;
}
AvatarElement.classList.add("rounded", "me-2");
AvatarElement.style.height = "120px";
AvatarContainer.appendChild(AvatarElement);
LeftTopDiv.appendChild(AvatarContainer);
let UserInfoElement = document.createElement("div");
UserInfoElement.classList.add("col-auto");
UserInfoElement.style.lineHeight = "40px";
UserInfoElement.innerHTML += "用户名:" + escapeHTML(UserID) + " ";
UserInfoElement.innerHTML += "昵称:" + escapeHTML(UserNick) + " ";
if (UtilityEnabled("Rating")) {
UserInfoElement.innerHTML += "评分:" + ((await GetUserInfo(UserID)).Rating) + " ";
}
// Create a placeholder for the last online time
let lastOnlineElement = document.createElement('div');
lastOnlineElement.innerHTML = "最后在线:加载中... ";
UserInfoElement.appendChild(lastOnlineElement);
let BadgeInfo = await GetUserBadge(UserID);
if (IsAdmin) {
if (BadgeInfo.Content !== "") {
let DeleteBadgeButton = document.createElement("button");
DeleteBadgeButton.className = "btn btn-outline-danger btn-sm";
DeleteBadgeButton.innerText = "删除标签";
DeleteBadgeButton.addEventListener("click", async () => {
if (confirm("您确定要删除此标签吗?")) {
RequestAPI("DeleteBadge", {
"UserID": UserID
}, (Response) => {
if (UtilityEnabled("DebugMode")) console.log(Response);
if (Response.Success) {
let Temp = [];
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i).startsWith("UserScript-User-" + UserID + "-Badge-")) {
Temp.push(localStorage.key(i));
}
}
for (let i = 0; i < Temp.length; i++) {
localStorage.removeItem(Temp[i]);
}
window.location.reload();
} else {
SmartAlert(Response.Message);
}
});
}
});
UserInfoElement.appendChild(DeleteBadgeButton);
} else {
let AddBadgeButton = document.createElement("button");
AddBadgeButton.className = "btn btn-outline-primary btn-sm";
AddBadgeButton.innerText = "添加标签";
AddBadgeButton.addEventListener("click", async () => {
RequestAPI("NewBadge", {
"UserID": UserID
}, (Response) => {
if (Response.Success) {
let Temp = [];
for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i).startsWith("UserScript-User-" + UserID + "-Badge-")) {
Temp.push(localStorage.key(i));
}
}
for (let i = 0; i < Temp.length; i++) {
localStorage.removeItem(Temp[i]);
}
window.location.reload();
} else {
SmartAlert(Response.Message);
}
});
});
UserInfoElement.appendChild(AddBadgeButton);
}
}
RequestAPI("LastOnline", {"Username": UserID}, (result) => {
if (result.Success) {
if (UtilityEnabled("DebugMode")) {
console.log('lastOnline:' + result.Data.logintime);
}
lastOnlineElement.innerHTML = "最后在线:" + GetRelativeTime(result.Data.logintime) + " ";
} else {
lastOnlineElement.innerHTML = "最后在线:近三个月内从未 ";
}
});
LeftTopDiv.appendChild(UserInfoElement);
LeftDiv.appendChild(LeftTopDiv);
let LeftTable = document.querySelector("body > div > div > center > table");
LeftDiv.appendChild(LeftTable);
let RightDiv = document.createElement("div");
RightDiv.className = "col-md-7";
Row.appendChild(RightDiv);
RightDiv.innerHTML = "已解决题目 ";
for (let i = 0; i < ACProblems.length; i++) {
RightDiv.innerHTML += "" + ACProblems[i] + " ";
}
if (document.querySelector("body > div > div") != null) document.querySelector("body > div > div").innerHTML = "";
if (document.querySelector("body > div > div") != null) document.querySelector("body > div > div").appendChild(Row);
} else {
document.title = "上传标程";
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = `上传标程
您必须要上传标程以后才能使用“查看标程”功能。点击“上传标程”按钮以后,系统会自动上传标程,请您耐心等待。
首次上传标程可能会比较慢,请耐心等待。后续将可以自动上传AC代码。
系统每过30天会自动提醒您上传标程,您必须要上传标程,否则将会被禁止使用“查看标程”功能。
`;
UploadStd.addEventListener("click", async () => {
UploadStd.disabled = true;
ErrorElement.style.display = "none";
ErrorElement.innerText = "";
UploadProgress.classList.remove("bg-success");
UploadProgress.classList.remove("bg-warning");
UploadProgress.classList.remove("bg-danger");
UploadProgress.classList.add("progress-bar-animated");
UploadProgress.style.width = "0%";
UploadProgress.innerText = "0%";
let ACList = [];
await fetch("https://www.xmoj.tech/userinfo.php?user=" + CurrentUsername)
.then((Response) => {
return Response.text();
}).then((Response) => {
let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
let ScriptData = (ParsedDocument.querySelector("#statics > tbody > tr:nth-child(2) > td:nth-child(3) > script") != null) ? ParsedDocument.querySelector("#statics > tbody > tr:nth-child(2) > td:nth-child(3) > script").innerText : "";
ScriptData = ScriptData.substr(ScriptData.indexOf("}") + 1).trim();
ScriptData = ScriptData.split(";");
for (let i = 0; i < ScriptData.length; i++) {
ACList.push(Number(ScriptData[i].substring(2, ScriptData[i].indexOf(","))));
}
});
RequestAPI("GetStdList", {}, async (Result) => {
if (Result.Success) {
let StdList = Result.Data.StdList;
for (let i = 0; i < ACList.length; i++) {
if (StdList.indexOf(ACList[i]) === -1 && ACList[i] !== 0) {
await new Promise((Resolve) => {
RequestAPI("UploadStd", {
"ProblemID": Number(ACList[i])
}, (Result) => {
if (!Result.Success) {
ErrorElement.style.display = "block";
ErrorElement.innerText += Result.Message + "\n";
UploadProgress.classList.add("bg-warning");
}
UploadProgress.innerText = (i / ACList.length * 100).toFixed(1) + "% (" + ACList[i] + ")";
UploadProgress.style.width = (i / ACList.length * 100) + "%";
Resolve();
});
});
}
}
UploadProgress.classList.add("bg-success");
UploadProgress.classList.remove("progress-bar-animated");
UploadProgress.innerText = "100%";
UploadProgress.style.width = "100%";
UploadStd.disabled = false;
localStorage.setItem("UserScript-LastUploadedStdTime", new Date().getTime());
} else {
ErrorElement.style.display = "block";
ErrorElement.innerText = Result.Message;
UploadStd.disabled = false;
}
});
});
}
} else if (location.pathname == "/comparesource.php" && document.querySelector("body > div.container > div") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/comparesource.php") {
if (UtilityEnabled("CompareSource")) {
if (location.search == "") {
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").innerHTML = "";
let LeftCodeText = document.createElement("span");
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").appendChild(LeftCodeText);
LeftCodeText.innerText = "左侧代码的运行编号:";
let LeftCode = document.createElement("input");
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").appendChild(LeftCode);
LeftCode.classList.add("form-control");
LeftCode.style.width = "40%";
LeftCode.style.marginBottom = "5px";
let RightCodeText = document.createElement("span");
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").appendChild(RightCodeText);
RightCodeText.innerText = "右侧代码的运行编号:";
let RightCode = document.createElement("input");
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").appendChild(RightCode);
RightCode.classList.add("form-control");
RightCode.style.width = "40%";
RightCode.style.marginBottom = "5px";
let CompareButton = document.createElement("button");
if (document.querySelector("body > div.container > div") != null) document.querySelector("body > div.container > div").appendChild(CompareButton);
CompareButton.innerText = "比较";
CompareButton.className = "btn btn-primary";
CompareButton.addEventListener("click", () => {
location.href = "https://www.xmoj.tech/comparesource.php?left=" + Number(LeftCode.value) + "&right=" + Number(RightCode.value);
});
} else {
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = `
忽略空白
`;
let compareElementStyle = document.createElement("style");
compareElementStyle.textContent = `#CompareElement .monaco-merge-host { height: 95%; }`;
document.head.appendChild(compareElementStyle);
let LeftCode = "";
await fetch("https://www.xmoj.tech/getsource.php?id=" + SearchParams.get("left"))
.then((Response) => {
return Response.text();
}).then((Response) => {
LeftCode = Response.substring(0, Response.indexOf("/**************************************************************")).trim();
});
let RightCode = "";
await fetch("https://www.xmoj.tech/getsource.php?id=" + SearchParams.get("right"))
.then((Response) => {
return Response.text();
}).then((Response) => {
RightCode = Response.substring(0, Response.indexOf("/**************************************************************")).trim();
});
let MergeViewElement = CodeMirror.MergeView(CompareElement, {
value: LeftCode,
origLeft: null,
orig: RightCode,
lineNumbers: true,
mode: "text/x-c++src",
collapseIdentical: "true",
readOnly: true,
theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default"),
revertButtons: false,
ignoreWhitespace: true
});
IgnoreWhitespace.addEventListener("change", () => {
MergeViewElement.ignoreWhitespace = ignorews.checked;
});
}
}
} else if (location.pathname == "/loginpage.php" && document.querySelector("#login") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/loginpage.php") {
if (UtilityEnabled("NewBootstrap")) {
if (document.querySelector("#login") != null) document.querySelector("#login").innerHTML = ` `;
}
let ErrorText = document.createElement("div");
ErrorText.style.color = "red";
ErrorText.style.marginBottom = "5px";
if (document.querySelector("#login") != null) document.querySelector("#login").appendChild(ErrorText);
let LoginButton = document.getElementsByName("submit")[0];
LoginButton.addEventListener("click", async () => {
let Username = document.getElementsByName("user_id")[0].value;
let Password = document.getElementsByName("password")[0].value;
if (Username == "" || Password == "") {
ErrorText.innerText = "用户名或密码不能为空";
} else {
await fetch("https://www.xmoj.tech/login.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "user_id=" + encodeURIComponent(Username) + "&password=" + hex_md5(Password)
})
.then((Response) => {
return Response.text();
})
.then(async (Response) => {
if (UtilityEnabled("LoginFailed")) {
if (Response.indexOf("history.go(-2);") != -1) {
if (UtilityEnabled("SavePassword")) {
await storeCredential(Username, Password);
}
let NewPage = localStorage.getItem("UserScript-LastPage");
if (NewPage == null) {
NewPage = "https://www.xmoj.tech/index.php";
}
location.href = NewPage;
} else {
if (UtilityEnabled("SavePassword")) {
clearCredential();
}
Response = Response.substring(Response.indexOf("alert('") + 7);
Response = Response.substring(0, Response.indexOf("');"));
if (Response == "UserName or Password Wrong!") {
ErrorText.innerText = "用户名或密码错误!";
} else {
ErrorText.innerText = Response;
}
}
} else {
document.innerHTML = Response;
}
});
}
});
if (UtilityEnabled("SavePassword")) {
(async () => {
let Credential = await getCredential();
if (Credential) {
if (document.querySelector("#login > div:nth-child(1) > div > input") != null) document.querySelector("#login > div:nth-child(1) > div > input").value = Credential.id;
if (document.querySelector("#login > div:nth-child(2) > div > input") != null) document.querySelector("#login > div:nth-child(2) > div > input").value = Credential.password;
LoginButton.click();
}
})();
}
} else if (location.pathname == "/contest_video.php" || location.pathname == "/problem_video.php") {
let ScriptData = (document.querySelector("body > div > div.mt-3 > center > script") != null) ? document.querySelector("body > div > div.mt-3 > center > script").innerHTML : "";
if (document.getElementById("J_prismPlayer0").innerHTML != "") {
document.getElementById("J_prismPlayer0").innerHTML = "";
if (player) {
player.dispose();
}
eval(ScriptData);
}
if (UtilityEnabled("DownloadPlayback")) {
ScriptData = ScriptData.substring(ScriptData.indexOf("{"));
ScriptData = ScriptData.substring(0, ScriptData.indexOf("}") + 1);
ScriptData = ScriptData.replace(/([a-zA-Z0-9]+) ?:/g, "\"$1\":");
ScriptData = ScriptData.replace(/'/g, "\"");
let VideoData = JSON.parse(ScriptData);
let RandomUUID = () => {
let t = "0123456789abcdef";
let e = [];
for (let r = 0; r < 36; r++) e[r] = t.substr(Math.floor(16 * Math.random()), 1);
e[14] = "4";
e[19] = t.substr(3 & e[19] | 8, 1);
e[8] = e[13] = e[18] = e[23] = "-";
return e.join("");
};
let URLParams = new URLSearchParams({
"AccessKeyId": VideoData.accessKeyId,
"Action": "GetPlayInfo",
"VideoId": VideoData.vid,
"Formats": "",
"AuthTimeout": 7200,
"Rand": RandomUUID(),
"SecurityToken": VideoData.securityToken,
"StreamType": "video",
"Format": "JSON",
"Version": "2017-03-21",
"SignatureMethod": "HMAC-SHA1",
"SignatureVersion": "1.0",
"SignatureNonce": RandomUUID(),
"PlayerVersion": "2.9.3",
"Channel": "HTML5"
});
URLParams.sort();
await fetch("https://vod." + VideoData.region + ".aliyuncs.com/?" + URLParams.toString() + "&Signature=" + encodeURIComponent(CryptoJS.HmacSHA1("GET&%2F&" + encodeURIComponent(URLParams.toString()), VideoData.accessKeySecret + "&").toString(CryptoJS.enc.Base64)))
.then((Response) => {
return Response.json();
})
.then((Response) => {
let DownloadButton = document.createElement("a");
DownloadButton.className = "btn btn-outline-secondary";
DownloadButton.innerText = "下载";
DownloadButton.href = Response.PlayInfoList.PlayInfo[0].PlayURL;
DownloadButton.download = Response.VideoBase.Title;
if (document.querySelector("body > div > div.mt-3 > center") != null) document.querySelector("body > div > div.mt-3 > center").appendChild(DownloadButton);
});
}
} else if (location.pathname == "/reinfo.php") {
document.title = "测试点信息: " + SearchParams.get("sid");
if (document.querySelector("#results > div") == undefined) {
if (document.querySelector("#results") != null) document.querySelector("#results").parentElement.innerHTML = "没有测试点信息";
} else {
// With several subtasks there are several result groups, and group
// headers do not have the nested structure of a test point.
let ResultGroups = document.querySelectorAll("#results > div");
for (let j = 0; j < ResultGroups.length; j++) {
for (let i = 0; i < ResultGroups[j].children.length; i++) {
let CurrentElement = ResultGroups[j].children[i];
for (let Depth = 0; Depth < 3 && CurrentElement != undefined; Depth++) {
CurrentElement = CurrentElement.children[0];
}
if (CurrentElement == undefined || CurrentElement.innerText.indexOf("/") == -1) {
continue;
}
let Temp = CurrentElement.innerText.substring(0, CurrentElement.innerText.length - 2).split("/");
CurrentElement.innerText = TimeToStringTime(Temp[0]) + "/" + SizeToStringSize(Temp[1]);
}
}
{
let ApplyDataElement = document.getElementById("apply_data");
let ApplyDiv = ApplyDataElement ? ApplyDataElement.parentElement : document.getElementById("results").parentElement;
console.log("启动!!!");
if (UtilityEnabled("ApplyData")) {
let base93Alphabet = (() => {
let result = "";
for (let code = 32; code < 127; code++) {
if (code !== 91 && code !== 93) {
result += String.fromCharCode(code);
}
}
return result;
})();
let base93Map = Object.fromEntries([...base93Alphabet].map((character, index) => [character, index]));
function Base93Decode(input) {
let output = [];
let bitBuffer = 0;
let bitCount = 0;
let value = -1;
for (let character of input) {
let decoded = base93Map[character];
if (decoded === undefined) {
throw new Error("Invalid Base93 payload");
}
if (value < 0) {
value = decoded;
continue;
}
value += decoded * 93;
bitBuffer |= value << bitCount;
bitCount += (value & 8191) > 456 ? 13 : 14;
while (bitCount >= 8) {
output.push(bitBuffer & 255);
bitBuffer >>>= 8;
bitCount -= 8;
}
value = -1;
}
if (value >= 0) {
output.push((bitBuffer | value << bitCount) & 255);
}
return new Uint8Array(output);
}
function DecodeBytesForDisplay(input) {
try {
// Keep a leading UTF-8 BOM instead of silently discarding it.
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input);
} catch {
// Invalid UTF-8 is shown reversibly: every original byte becomes one \xNN escape.
let output = "";
for (let byte of input) {
output += "\\x" + byte.toString(16).padStart(2, "0");
}
return output;
}
}
function NumberStreamDecodeV1(input) {
let position = 4;
if (input.length < 5 || input[0] !== 78 || input[1] !== 83 || input[2] !== 67 || input[3] !== 49) {
throw new Error("Invalid number-stream payload");
}
function ReadByte() {
if (position >= input.length) {
throw new Error("Truncated number-stream payload");
}
return input[position++];
}
function ReadVarint() {
let value = 0n;
let shift = 0n;
while (true) {
let byte = ReadByte();
value |= BigInt(byte & 127) << shift;
if (!(byte & 128)) {
return value;
}
shift += 7n;
if (shift > 63n) {
throw new Error("Invalid number-stream varint");
}
}
}
function ReadSize() {
let value = ReadVarint();
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
throw new Error("Number-stream payload is too large");
}
return Number(value);
}
function Unzig(value) {
return (value >> 1n) ^ -(value & 1n);
}
let flags = ReadByte();
if (flags & 1) {
let length = ReadSize();
if (position + length > input.length) {
throw new Error("Truncated number-stream payload");
}
return DecodeBytesForDisplay(input.subarray(position, position + length));
}
let numberCount = ReadSize();
let lineCount = ReadSize();
let lineLengths = [];
let mode = ReadByte();
if (mode === 1) {
let length = ReadSize();
let lastLength = ReadSize();
for (let i = 0; i + 1 < lineCount; i++) {
lineLengths.push(length);
}
if (lineCount) {
lineLengths.push(lastLength);
}
} else if (mode === 2) {
for (let i = 0; i < lineCount; i++) {
lineLengths.push(ReadSize());
}
} else {
throw new Error("Invalid number-stream line mode");
}
let values = [];
let previous = 0n;
while (values.length < numberCount) {
let count = Math.min(128, numberCount - values.length);
let useDelta = ReadByte() !== 0;
let width = ReadByte();
if (width > 64) {
throw new Error("Invalid number-stream bit width");
}
if (width === 0) {
for (let i = 0; i < count; i++) {
let value = useDelta ? previous : 0n;
values.push(value);
previous = value;
}
continue;
}
let byteCount = Math.ceil(count * width / 8);
if (position + byteCount > input.length) {
throw new Error("Truncated number-stream payload");
}
let end = position + byteCount;
let buffer = 0n;
let bitCount = 0;
function ReadBits(bits) {
while (bitCount < bits) {
buffer |= BigInt(input[position++]) << BigInt(bitCount);
bitCount += 8;
}
let value = buffer & ((1n << BigInt(bits)) - 1n);
buffer >>= BigInt(bits);
bitCount -= bits;
return value;
}
for (let i = 0; i < count; i++) {
let decoded = Unzig(ReadBits(width));
let value = useDelta ? BigInt.asIntN(64, previous + decoded) : decoded;
values.push(value);
previous = value;
}
position = end;
}
if (lineLengths.reduce((sum, length) => sum + length, 0) !== numberCount) {
throw new Error("Invalid number-stream structure");
}
let output = "";
let valueIndex = 0;
for (let i = 0; i < lineLengths.length; i++) {
if (i) {
output += "\n";
}
let line = [];
for (let j = 0; j < lineLengths[i]; j++) {
line.push(values[valueIndex++].toString());
}
output += line.join(" ");
}
if (flags & 2) {
output += "\n";
}
return output;
}
function NumberStreamDecode(input) {
if (input[3] === 49) {
return NumberStreamDecodeV1(input);
}
let position = 4;
if (input.length < 5 || input[0] !== 78 || input[1] !== 83 || input[2] !== 67 || input[3] !== 51) {
throw new Error("Invalid number-stream payload");
}
function ReadByte() {
if (position >= input.length) {
throw new Error("Truncated number-stream payload");
}
return input[position++];
}
function ReadVarint() {
let value = 0n;
let shift = 0n;
while (true) {
let byte = ReadByte();
value |= BigInt(byte & 127) << shift;
if (!(byte & 128)) {
return value;
}
shift += 7n;
if (shift > 63n) {
throw new Error("Invalid number-stream varint");
}
}
}
function ReadSize() {
let value = ReadVarint();
if (value > 4294967296n) {
throw new Error("Number-stream payload is too large");
}
return Number(value);
}
function Unzig(value) {
return (value >> 1n) ^ -(value & 1n);
}
function DecodeStream(length) {
let output = [];
let definitions = [];
let sequence = [];
let previousDefinition = -1;
let previous = 0n;
function Emit(definition, count) {
if (definition.count !== count) {
throw new Error("Invalid number-stream block size");
}
let buffer = 0n;
let bitCount = 0;
let payloadPosition = 0;
function ReadBits(bits) {
while (bitCount < bits) {
buffer |= BigInt(definition.payload[payloadPosition++]) << BigInt(bitCount);
bitCount += 8;
}
let value = buffer & ((1n << BigInt(bits)) - 1n);
buffer >>= BigInt(bits);
bitCount -= bits;
return value;
}
for (let i = 0; i < count; i++) {
let packed = definition.width ? ReadBits(definition.width) : 0n;
let value;
if (definition.mode === 4) {
value = BigInt.asIntN(64, definition.base + packed);
} else if (definition.mode === 1) {
value = BigInt.asIntN(64, previous + Unzig(packed));
} else {
value = Unzig(packed);
}
output.push(value);
previous = value;
}
}
while (output.length < length) {
let count = Math.min(128, length - output.length);
let operation = ReadByte();
if (operation === 3) {
let repeats = ReadSize();
if (previousDefinition < 0 || repeats === 0) {
throw new Error("Invalid number-stream run");
}
for (let i = 0; i < repeats; i++) {
if (output.length >= length) {
throw new Error("Number-stream run is too long");
}
count = Math.min(128, length - output.length);
Emit(definitions[previousDefinition], count);
sequence.push(previousDefinition);
}
continue;
}
if (operation === 2) {
let distance = ReadSize();
if (!distance || distance > sequence.length) {
throw new Error("Invalid number-stream back-reference");
}
previousDefinition = sequence[sequence.length - distance];
Emit(definitions[previousDefinition], count);
sequence.push(previousDefinition);
continue;
}
if (operation !== 0 && operation !== 1 && operation !== 4) {
throw new Error("Invalid number-stream opcode");
}
let width = ReadByte();
if (width > 64) {
throw new Error("Invalid number-stream bit width");
}
let base = operation === 4 ? Unzig(ReadVarint()) : 0n;
let byteCount = Math.ceil(count * width / 8);
if (position + byteCount > input.length) {
throw new Error("Truncated number-stream payload");
}
let definition = {
mode: operation,
width,
count,
base,
payload: input.subarray(position, position + byteCount)
};
position += byteCount;
Emit(definition, count);
definitions.push(definition);
previousDefinition = definitions.length - 1;
sequence.push(previousDefinition);
}
return output;
}
let flags = ReadByte();
if (flags & 1) {
let length = ReadSize();
if (position + length > input.length) {
throw new Error("Truncated number-stream payload");
}
return DecodeBytesForDisplay(input.subarray(position, position + length));
}
let numberCount = ReadSize();
let lineCount = ReadSize();
let lineValues = DecodeStream(lineCount);
let values = DecodeStream(numberCount);
let lineLengths = lineValues.map((value) => {
if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
throw new Error("Invalid number-stream line length");
}
return Number(value);
});
if (lineLengths.reduce((sum, length) => sum + length, 0) !== numberCount) {
throw new Error("Invalid number-stream structure");
}
let output = "";
let valueIndex = 0;
for (let i = 0; i < lineLengths.length; i++) {
if (i) {
output += "\n";
}
let line = [];
for (let j = 0; j < lineLengths[i]; j++) {
line.push(values[valueIndex++].toString());
}
output += line.join(" ");
}
if (flags & 2) {
output += "\n";
}
return output;
}
async function GzipDecode(input) {
let stream = new Blob([input]).stream().pipeThrough(new DecompressionStream("gzip"));
return new Uint8Array(await new Response(stream).arrayBuffer());
}
async function DecodePayload(payload) {
let rawData = Base93Decode(payload);
if (rawData.length >= 4 && rawData[0] === 78 && rawData[1] === 83 && rawData[2] === 67 && (rawData[3] === 49 || rawData[3] === 51)) {
return NumberStreamDecode(rawData);
}
return DecodeBytesForDisplay(await GzipDecode(rawData));
}
async function ExtractData(text) {
let result = [];
let pattern = /^(?:what\(\): )?\[([^\]\r\n]*)\]\r?$/gm;
let match;
while ((match = pattern.exec(text))) {
try {
result.push(await DecodePayload(match[1]));
} catch {
}
}
return result;
}
let NumberStreamEnabled = false;
let NumberStreamButton = document.createElement("button");
NumberStreamButton.className = "ms-2 btn btn-outline-secondary";
NumberStreamButton.title = "适合大型、主要由数值组成的输入;基准压缩速度约 310 MB/s(数值范围以 long long 为限,其他内容会原样回退)";
function UpdateNumberStreamButton() {
NumberStreamButton.innerText = "高速数值模式:" + (NumberStreamEnabled ? "开启" : "关闭");
NumberStreamButton.classList.toggle("btn-outline-secondary", !NumberStreamEnabled);
NumberStreamButton.classList.toggle("btn-outline-success", NumberStreamEnabled);
NumberStreamButton.setAttribute("aria-pressed", String(NumberStreamEnabled));
}
NumberStreamButton.addEventListener("click", () => {
NumberStreamEnabled = !NumberStreamEnabled;
UpdateNumberStreamButton();
UpdateLineBreakButton();
UpdateModeDescription();
});
ApplyDiv.appendChild(NumberStreamButton);
let PreserveLineBreaks = true;
let LineBreakButton = document.createElement("button");
LineBreakButton.className = "ms-2 btn btn-outline-secondary";
LineBreakButton.title = "使用 cin >> 等空白不敏感的读取方式时可关闭;关闭后不保留原输入换行,可避免大量随机行长超过评测输出限制";
function UpdateLineBreakButton() {
LineBreakButton.innerText = "保留换行:" + (PreserveLineBreaks ? "是" : "否");
LineBreakButton.disabled = !NumberStreamEnabled;
LineBreakButton.classList.toggle("btn-outline-secondary", PreserveLineBreaks || !NumberStreamEnabled);
LineBreakButton.classList.toggle("btn-outline-warning", !PreserveLineBreaks && NumberStreamEnabled);
LineBreakButton.setAttribute("aria-pressed", String(PreserveLineBreaks));
}
LineBreakButton.addEventListener("click", () => {
PreserveLineBreaks = !PreserveLineBreaks;
UpdateLineBreakButton();
UpdateModeDescription();
});
ApplyDiv.appendChild(LineBreakButton);
let ModeDescription = document.createElement("div");
ModeDescription.className = "small text-secondary mt-2";
function UpdateModeDescription() {
if (!NumberStreamEnabled) {
ModeDescription.innerText = "默认模式(gzip + Base93):逐字节压缩并精确保留任意输入,适合文本、Unicode、混合内容,以及使用 getline 或按字符读取的程序。无效 UTF-8 会以可逆的逐字节 \\xNN 形式显示。";
} else if (PreserveLineBreaks) {
ModeDescription.innerText = "高速数值模式(保留换行):NSC3 同时压缩 long long 数值和每行数值个数,精确恢复规范数值输入的空格、空行、换行和末尾换行;非规范或混合内容会原样回退。大量随机行长仍可能超过评测输出限制。";
} else {
ModeDescription.innerText = "高速数值模式(不保留换行):丢弃规范数值输入的行边界,恢复为单行空格分隔的数据,可避开随机行长开销。仅适合 cin >> 或 scanf 等空白不敏感读取;不要用于 getline、按行解析或依赖末尾换行的程序。非规范或混合内容仍会原样回退。";
}
}
UpdateNumberStreamButton();
UpdateLineBreakButton();
UpdateModeDescription();
ApplyDiv.appendChild(ModeDescription);
let GetDataButton = document.createElement("button");
GetDataButton.className = "ms-2 btn btn-outline-secondary";
GetDataButton.innerText = "获取数据";
console.log("按钮创建成功");
ApplyDiv.appendChild(GetDataButton);
GetDataButton.addEventListener("click", async () => {
GetDataButton.disabled = true;
GetDataButton.innerText = "正在获取数据...";
let PID = localStorage.getItem("UserScript-Solution-" + SearchParams.get("sid") + "-Problem");
if (PID == null) {
GetDataButton.innerText = "失败! 无法获取PID";
GetDataButton.disabled = false;
await new Promise((resolve) => {
setTimeout(resolve, 800);
});
GetDataButton.innerText = "获取数据";
return;
}
let Code = "";
if (localStorage.getItem(`UserScript-Problem-${PID}-IOFilename`) !== null) {
Code = `#define IOFile "${localStorage.getItem(`UserScript-Problem-${PID}-IOFilename`)}"\n`;
}
if (NumberStreamEnabled && !PreserveLineBreaks) {
Code += "#define NSC_IGNORE_LINES\n";
}
let NumberStreamCode = String.raw`// XMOJ-Script 获取数值数据代码 (NSC3)
#include
using namespace std;
typedef uint8_t U8;typedef uint64_t U64;typedef int64_t I64;const size_t NB=128,DC=1u<<16;
U64 zz(I64 v){return(U64(v)<<1)^U64(v>>63);}int bf(U64 v){int n=0;while(v)n++,v>>=1;return n;}void pv(vector&o,U64 v){while(v>=128)o.push_back(U8(v)|128),v>>=7;o.push_back(U8(v));}
struct NP{vectorv,l;bool nl,ok;NP():nl(0),ok(1){}};
NP pn(const string&s){NP r;size_t n=s.size();if(!n)return r;r.nl=s[n-1]=='\n';size_t e=r.nl?n-1:n;const char*p=s.data();r.v.reserve(e/3+8);size_t i=0;I64 z=0;bool a=1;while(i=e||p[i]==' '||p[i]=='\n'){r.ok=0;return r;}c=p[i];}bool neg=0;if(c=='-'){neg=1;if(++i>=e){r.ok=0;return r;}c=p[i];}if(c<'0'||c>'9'){r.ok=0;return r;}if(c=='0'&&(neg||(i+1='0'&&p[i+1]<='9'))){r.ok=0;return r;}U64 m=0;size_t b=i;while(i9)break;m=m*10+d;i++;}if(i-b>19){r.ok=0;return r;}if(i-b==19){U64 q=0;for(size_t j=b;j(~U64(0)-d)/10){r.ok=0;return r;}q=q*10+d;}m=q;}U64 lim=neg?U64(1)<<63:(U64(1)<<63)-1;if(m>lim){r.ok=0;return r;}r.v.push_back(neg?I64(~m+1):I64(m));z++;a=0;}r.l.push_back(z);return r;}
struct BW{vector&o;U64 a;int n;BW(vector&O):o(O),a(0),n(0){}void p(U64 v,int k){while(k){int t=min(k,64-n);U64 m=t==64?~U64(0):(U64(1)<>t;k-=t;if(n==64){for(int i=0;i<8;i++)o.push_back(U8(a>>(8*i)));a=0;n=0;}}}void f(){if(n)for(int i=0;i<(n+7)/8;i++)o.push_back(U8(a>>(8*i)));}};
void bp(vector&q,U8&m,U8&k,I64&base,const I64*v,size_t n,I64 prev){U64 mr=0,md=0;I64 lo=v[0],hi=v[0],p=prev;for(size_t i=0;i>8);for(int b=0;b<8;b++)d+=char(U8(U64(base)>>(8*b)));}
void es(vector&o,const vector&v){unordered_mapdict;vectordefs;vectorseq;string cur;vectorq;q.reserve(NB*8+8);size_t pending=0,prevDef=size_t(-1);I64 prev=0;for(size_t i=0;i::iterator it=dict.find(cur);if(it!=dict.end()){size_t at=it->second;o.push_back(2);pv(o,seq.size()-at);prevDef=seq[at];seq.push_back(prevDef);it->second=seq.size()-1;continue;}o.push_back(m);o.push_back(k);if(m==4)pv(o,zz(base));o.insert(o.end(),q.begin(),q.end());defs.push_back(cur);prevDef=defs.size()-1;seq.push_back(prevDef);if(dict.size()o;o.push_back('N');o.push_back('S');o.push_back('C');o.push_back('3');NP p=pn(s);bool raw=!p.ok;
#ifdef NSC_IGNORE_LINES
if(!raw){p.nl=0;p.l.clear();if(!p.v.empty())p.l.push_back(I64(p.v.size()));}
#endif
o.push_back(U8(raw|(p.nl?2:0)));if(raw){pv(o,s.size());o.insert(o.end(),s.begin(),s.end());return string((char*)&o[0],o.size());}pv(o,p.v.size());pv(o,p.l.size());es(o,p.l);es(o,p.v);return string((char*)&o[0],o.size());}
string b93(const string&s){static string A=[](){string a;for(int c=32;c<127;c++)if(c!=91&&c!=93)a+=char(c);return a;}();string o="[";o.reserve(s.size()*5/4+3);uint32_t b=0;int n=0;for(size_t i=0;i13){uint32_t v=b&8191;if(v>456)b>>=13,n-=13;else v=b&16383,b>>=14,n-=14;o+=A[v%93];o+=A[v/93];}}if(n){o+=A[b%93];if(n>7||b>92)o+=A[b/93];}o+=']';return o;}
string rd(){string s;
#ifdef IOFile
if(!fseek(stdin,0,SEEK_END)){long n=ftell(stdin);rewind(stdin);if(n>=0){s.resize((size_t)n);if(n){size_t q=fread(&s[0],1,(size_t)n,stdin);s.resize(q);}return s;}}
#endif
char b[65536];size_t q;while((q=fread(b,1,sizeof b,stdin))!=0)s.append(b,q);return s;}
int main(){
#ifdef IOFile
if(!freopen(IOFile ".in","rb",stdin))return 0;
#endif
cerr<
using namespace std;
struct W{string o;uint64_t b;int n;W():b(0),n(0){}void p(uint32_t v,int k){b|=(uint64_t)v<=8)o+=char(b),b>>=8,n-=8;}void a(){if(n)o+=char(b),b=0,n=0;}string f(){a();return o;}};uint32_t R(uint32_t x,int n){uint32_t y=0;while(n--)y=y*2+(x&1),x>>=1;return y;}struct T{uint16_t l,d;T(int L=0,int D=0):l(L),d(D){}};
int Lb[]={3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258},Le[]={0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0},Db[]={1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577},De[]={0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
int li(int x){int i=0;while(i<28&&x>=Lb[i+1])i++;return i;}int di(int x){int i=0;while(i<29&&x>=Db[i+1])i++;return i;}
vector tk(const string&s){int N=s.size(),Z=1<<16;vectorh(Z,-1),p(1<<15,-1);vectorv;v.reserve(N/5+1);auto H=[&](int i){return ((uint32_t)(uint8_t)s[i]*63001u+(uint32_t)(uint8_t)s[i+1]*251u+(uint8_t)s[i+2])&(Z-1);};auto I=[&](int i){if(i+2=N)return make_pair(0,0);int c=h[H(i)],m=min(258,N-i),q=0;const char*a=s.data()+i;while(c>=0&&i-c<=32768){int lim=l>=8?64:128;if(q++>=lim)break;const char*b=s.data()+c;if(b[0]!=a[0]||b[1]!=a[1]||b[2]!=a[2]){c=p[c&32767];continue;}if(l>=3&&(b[l]!=a[l]||memcmp(a,b,l))){c=p[c&32767];continue;}int z=l>=3?l+1:3;while(z+8<=m){uint64_t x,y;memcpy(&x,a+z,8);memcpy(&y,b+z,8);if(x!=y)break;z+=8;}while(zl)l=z,d=i-c;if(z>=128||z==m)break;c=p[c&32767];}return make_pair(l,d);};for(int i=0;iu=M(i);int l=u.first,d=u.second;I(i);if(l>=3&&l<16&&i+1q=M(i+1);if(q.first>l){v.push_back(T((uint8_t)s[i++],0));continue;}}if(l>=3){v.push_back(T(l,d));for(int j=1;jf,int M,vector&L){int n=f.size();L.assign(n,0);priority_queue,vector >,greater > >q;vectora(2*n,-1),b(2*n,-1);for(int i=0;i1){pairx=q.top();q.pop();pairy=q.top();q.pop();a[z]=x.second;b[z]=y.second;q.push(make_pair(x.first+y.first,z++));}functionF=[&](int x,int d){if(x cd(const vector&L,int M){vectorc(M+1),n(M+1);for(size_t i=0;ir(L.size());for(size_t i=0;i&L,const vector&C){w.p(C[s],L[s]);}
void pm(W&w,int l,int d,const vector&L,const vector&C,const vector&D,const vector&E){int a=li(l),q=di(d);sy(w,257+a,L,C);if(Le[a])w.p(l-Lb[a],Le[a]);sy(w,q,D,E);if(De[q])w.p(d-Db[q],De[q]);}
string fx(const vector&v){vectorL(288),D(32,5);for(int i=0;i<144;i++)L[i]=8;for(int i=144;i<256;i++)L[i]=9;for(int i=256;i<280;i++)L[i]=7;for(int i=280;i<288;i++)L[i]=8;vectorC=cd(L,15),E=cd(D,15);W w;w.o.reserve(v.size());w.p(1,1);w.p(1,2);for(size_t i=0;i rl(const vector&x){vectorr;for(int i=0,n=x.size();i=11){int q=min(k,138);r.push_back(Q(18,7,q-11));k-=q;}if(k>=3){int q=min(k,10);r.push_back(Q(17,3,q-3));k-=q;}while(k--)r.push_back(Q(0,0,0));}else{r.push_back(Q(z,0,0));k--;while(k>=3){int q=min(k,6);r.push_back(Q(16,2,q-3));k-=q;}while(k--)r.push_back(Q(z,0,0));}i=j;}return r;}
string dy(const vector&v){vectorf(286),g(30);f[256]=1;for(size_t i=0;iL,D;if(!ln(f,15,L)||!ln(g,15,D))return string();int nl=286,nd=30;while(nl>257&&!L[nl-1])nl--;while(nd>1&&!D[nd-1])nd--;vectorx(L.begin(),L.begin()+nl);x.insert(x.end(),D.begin(),D.begin()+nd);vectorr=rl(x);vectorf2(19);for(size_t i=0;iK;if(!ln(f2,7,K))return string();static int O[]={16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};int nk=19;while(nk>4&&!K[O[nk-1]])nk--;vectorC=cd(L,15),E=cd(D,15),J=cd(K,7);W w;w.o.reserve(v.size());w.p(1,1);w.p(2,2);w.p(nl-257,5);w.p(nd-1,5);w.p(nk-4,4);for(int i=0;i>8);w.o+=char(q);w.o+=char(q>>8);w.o.append(s.data()+p,k);p+=k;}return w.o;}
uint32_t cr(const string&s){static uint32_t t[256];static int z=0;if(!z){for(int i=0;i<256;i++){uint32_t c=i;for(int j=0;j<8;j++)c=c>>1^(0xedb88320u&-(int)(c&1));t[i]=c;}z=1;}uint32_t c=~0u;for(size_t i=0;i>8);return ~c;}
string gz(const string&s){vectorv=tk(s);string b=fx(v),c=dy(v),d;c.size()&&c.size()(1,(s.size()+65534)/65535);if(z>8*i);for(int i=0;i<4;i++)o+=char(n>>8*i);return o;}
string b93(const string&s){static string A=[](){string a;for(int c=32;c<127;c++)if(c!=91&&c!=93)a+=char(c);return a;}();string o="[";o.reserve(s.size()*5/4+3);uint32_t b=0;int n=0;for(size_t i=0;i13){uint32_t v=b&8191;if(v>456)b>>=13,n-=13;else v=b&16383,b>>=14,n-=14;o+=A[v%93];o+=A[v/93];}}if(n){o+=A[b%93];if(n>7||b>92)o+=A[b/93];}o+=']';return o;}
string rd(){string s;
#ifdef IOFile
if(!fseek(stdin,0,SEEK_END)){long n=ftell(stdin);rewind(stdin);if(n>=0){s.resize((size_t)n);if(n){size_t q=fread(&s[0],1,(size_t)n,stdin);s.resize(q);}return s;}}
#endif
char b[65536];size_t q;while((q=fread(b,1,sizeof b,stdin))!=0)s.append(b,q);return s;}
int main(){
#ifdef IOFile
if(!freopen(IOFile ".in","rb",stdin))return 0;
#endif
cerr< {
return Response.text();
}).then((Response) => {
let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
return (ParsedDocument.querySelector("#result-tab > tbody > tr:nth-child(1) > td:nth-child(2)") != null) ? ParsedDocument.querySelector("#result-tab > tbody > tr:nth-child(1) > td:nth-child(2)").innerText : "";
});
//没有找到提交记录时不再轮询,避免无限等待
if (SID == "") {
GetDataButton.innerText = "获取数据失败";
GetDataButton.disabled = false;
return;
}
await new Promise((Resolve) => {
let Interval = setInterval(async () => {
await fetch("status-ajax.php?solution_id=" + SID).then((Response) => {
return Response.text();
}).then((Response) => {
if (Response.split(",")[0] >= 4) {
clearInterval(Interval);
Resolve();
}
});
}, 500);
});
await fetch(`https://www.xmoj.tech/reinfo.php?sid=${SID}`).then((Response) => {
return Response.text();
}).then(async (Response) => {
let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
let ErrorData = ParsedDocument.getElementById("errtxt").innerText;
let dataList = await ExtractData(ErrorData);
if (dataList.length === 0) {
GetDataButton.innerText = "获取数据失败";
GetDataButton.disabled = false;
return;
}
for (let i = 0; i < dataList.length; i++) {
let Data = dataList[i];
ApplyDiv.appendChild(document.createElement("hr"));
ApplyDiv.appendChild(document.createTextNode("数据" + (i + 1) + ":"));
let CodeElement = document.createElement("div");
ApplyDiv.appendChild(CodeElement);
CodeMirror(CodeElement, {
value: Data,
theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default"),
lineNumbers: true,
readOnly: true
}).setSize("100%", "auto");
}
GetDataButton.innerText = "获取数据成功";
GetDataButton.disabled = false;
});
});
}
if (ApplyDataElement) {
ApplyDataElement.addEventListener("click", () => {
let ApplyElements = document.getElementsByClassName("data");
for (let i = 0; i < ApplyElements.length; i++) {
ApplyElements[i].style.display = (ApplyElements[i].style.display == "block" ? "" : "block");
}
});
}
}
let ApplyElements = document.getElementsByClassName("data");
for (let i = 0; i < ApplyElements.length; i++) {
ApplyElements[i].addEventListener("click", async () => {
await fetch("https://www.xmoj.tech/data_distribute_ajax_apply.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "user_id=" + CurrentUsername + "&" + "solution_id=" + SearchParams.get("sid") + "&" + "name=" + ApplyElements[i].getAttribute("name")
}).then((Response) => {
return Response.json();
}).then((Response) => {
ApplyElements[i].innerText = Response.msg;
setTimeout(() => {
ApplyElements[i].innerText = "申请数据";
}, 1000);
});
});
}
}
} else if (location.pathname == "/downloads.php") {
let SoftwareList = document.querySelector("body > div > ul");
SoftwareList.remove();
SoftwareList = document.createElement("ul");
SoftwareList.className = "software_list";
let Container = document.createElement("div");
if (document.querySelector("body > div") != null) document.querySelector("body > div").appendChild(Container);
Container.className = "mt-3";
Container.appendChild(SoftwareList);
if (UtilityEnabled("NewDownload")) {
let Softwares = [{
"Name": "Bloodshed Dev-C++",
"Image": "https://a.fsdn.com/allura/p/dev-cpp/icon",
"URL": "https://sourceforge.net/projects/dev-cpp/"
}, {
"Name": "DevC++ 5.11 TDM-GCC 4.9.2",
"Image": "https://www.xmoj.tech/image/devcpp.png",
"URL": "https://www.xmoj.tech/downloads/Dev-Cpp+5.11+TDM-GCC+4.9.2+Setup.exe"
}, {
"Name": "Orwell Dev-C++",
"Image": "https://a.fsdn.com/allura/p/orwelldevcpp/icon",
"URL": "https://sourceforge.net/projects/orwelldevcpp/"
}, {
"Name": "Embarcadero Dev-C++",
"Image": "https://a.fsdn.com/allura/s/embarcadero-dev-cpp/icon",
"URL": "https://sourceforge.net/software/product/Embarcadero-Dev-Cpp/"
}, {
"Name": "RedPanda C++",
"Image": "https://a.fsdn.com/allura/p/redpanda-cpp/icon",
"URL": "https://sourceforge.net/projects/redpanda-cpp/"
}, {
"Name": "CP Editor",
"Image": "https://a.fsdn.com/allura/mirror/cp-editor/icon?c35437565079e4135a985ba557ef2fdbe97de6bafb27aceafd76bc54490c26e3?&w=90",
"URL": "https://cpeditor.org/zh/download/"
}, {
"Name": "CLion",
"Image": "https://resources.jetbrains.com/storage/products/company/brand/logos/CLion_icon.png",
"URL": "https://www.jetbrains.com/clion/download"
}, {
"Name": "CP Editor",
"Image": "https://a.fsdn.com/allura/mirror/cp-editor/icon",
"URL": "https://sourceforge.net/projects/cp-editor.mirror/"
}, {
"Name": "Code::Blocks",
"Image": "https://a.fsdn.com/allura/p/codeblocks/icon",
"URL": "https://sourceforge.net/projects/codeblocks/"
}, {
"Name": "Visual Studio Code",
"Image": "https://code.visualstudio.com/favicon.ico",
"URL": "https://code.visualstudio.com/Download"
}, {
"Name": "Lazarus",
"Image": "https://a.fsdn.com/allura/p/lazarus/icon",
"URL": "https://sourceforge.net/projects/lazarus/"
}, {
"Name": "Geany",
"Image": "https://www.geany.org/static/img/geany.svg",
"URL": "https://www.geany.org/download/releases/"
}, {
"Name": "NOI Linux",
"Image": "https://www.noi.cn/upload/resources/image/2021/07/16/163780.jpg",
"URL": "https://www.noi.cn/gynoi/jsgz/2021-07-16/732450.shtml"
}, {
"Name": "VirtualBox",
"Image": "https://www.virtualbox.org/graphics/vbox_logo2_gradient.png",
"URL": "https://www.virtualbox.org/wiki/Downloads"
}, {
"Name": "MinGW",
"Image": "https://www.mingw-w64.org/logo.svg",
"URL": "https://sourceforge.net/projects/mingw/"
}];
for (let i = 0; i < Softwares.length; i++) {
SoftwareList.innerHTML += "" + "" + "" + "
" + "
" + "
" + "
" + Softwares[i].Name + "
" + "
" + " " + " ";
}
}
} else if (location.pathname == "/problemstatus.php") {
if (document.querySelector("body > div > div.mt-3 > center") != null) document.querySelector("body > div > div.mt-3 > center").insertBefore(document.querySelector("#statics"), document.querySelector("body > div > div.mt-3 > center > table"));
if (document.querySelector("body > div > div.mt-3 > center") != null) document.querySelector("body > div > div.mt-3 > center").insertBefore(document.querySelector("#problemstatus"), document.querySelector("body > div > div.mt-3 > center > table"));
if (document.querySelector("body > div > div.mt-3 > center > table:nth-child(3)") != null) document.querySelector("body > div > div.mt-3 > center > table:nth-child(3)").remove();
let Temp = (document.querySelector("#statics") != null) ? document.querySelector("#statics").rows : [];
for (let i = 0; i < Temp.length; i++) {
Temp[i].removeAttribute("class");
}
if (document.querySelector("#problemstatus > thead > tr") != null) document.querySelector("#problemstatus > thead > tr").innerHTML = document.querySelector("#problemstatus > thead > tr").innerHTML.replaceAll("td", "th");
if (document.querySelector("#problemstatus > thead > tr > th:nth-child(2)") != null) document.querySelector("#problemstatus > thead > tr > th:nth-child(2)").innerText = "运行编号";
if (document.querySelector("#problemstatus > thead > tr > th:nth-child(4)") != null) document.querySelector("#problemstatus > thead > tr > th:nth-child(4)").remove();
if (document.querySelector("#problemstatus > thead > tr > th:nth-child(4)") != null) document.querySelector("#problemstatus > thead > tr > th:nth-child(4)").remove();
if (document.querySelector("#problemstatus > thead > tr > th:nth-child(4)") != null) document.querySelector("#problemstatus > thead > tr > th:nth-child(4)").remove();
if (document.querySelector("#problemstatus > thead > tr > th:nth-child(4)") != null) document.querySelector("#problemstatus > thead > tr > th:nth-child(4)").remove();
Temp = (document.querySelector("#problemstatus > thead > tr") != null) ? document.querySelector("#problemstatus > thead > tr").children : [];
for (let i = 0; i < Temp.length; i++) {
Temp[i].removeAttribute("class");
}
Temp = (document.querySelector("#problemstatus > tbody") != null) ? document.querySelector("#problemstatus > tbody").children : [];
for (let i = 0; i < Temp.length; i++) {
if (Temp[i].children[5].children[0] != null) {
Temp[i].children[1].innerHTML = `${escapeHTML(Temp[i].children[1].innerText.trim())} `;
}
GetUsernameHTML(Temp[i].children[2], Temp[i].children[2].innerText);
Temp[i].children[3].remove();
Temp[i].children[3].remove();
Temp[i].children[3].remove();
Temp[i].children[3].remove();
}
let CurrentPage = parseInt(SearchParams.get("page") || 0);
let PID = Number(SearchParams.get("id"));
document.title = "问题 " + PID + " 状态";
let Pagination = ` `;
if (document.querySelector("body > div > div.mt-3 > center") != null) document.querySelector("body > div > div.mt-3 > center").innerHTML += Pagination;
} else if (location.pathname == "/open_contest.php" && document.querySelector("body > div > div.mt-3 > div > div.col-md-4") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/open_contest.php") {
let Temp = (document.querySelector("body > div > div.mt-3 > div > div.col-md-8") != null) ? document.querySelector("body > div > div.mt-3 > div > div.col-md-8").children : [];
let NewsData = [];
for (let i = 0; i < Temp.length; i += 2) {
let Title = Temp[i].children[0].innerText;
let Time = 0;
if (Temp[i].children[1] != null) {
Time = Temp[i].children[1].innerText;
}
let Body = Temp[i + 1].innerHTML;
NewsData.push({"Title": Title, "Time": new Date(Time), "Body": Body});
}
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-8") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-8").innerHTML = "";
for (let i = 0; i < NewsData.length; i++) {
let NewsRow = document.createElement("div");
NewsRow.className = "cnt-row";
let NewsRowHead = document.createElement("div");
NewsRowHead.className = "cnt-row-head title";
NewsRowHead.innerText = NewsData[i].Title;
if (NewsData[i].Time.getTime() != 0) {
NewsRowHead.innerHTML += "" + NewsData[i].Time.toLocaleDateString() + " ";
}
NewsRow.appendChild(NewsRowHead);
let NewsRowBody = document.createElement("div");
NewsRowBody.className = "cnt-row-body";
NewsRowBody.innerHTML = NewsData[i].Body;
NewsRow.appendChild(NewsRowBody);
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-8") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-8").appendChild(NewsRow);
}
let MyContestData = (document.querySelector("body > div > div.mt-3 > div > div.col-md-4 > div:nth-child(2)") != null) ? document.querySelector("body > div > div.mt-3 > div > div.col-md-4 > div:nth-child(2)").innerHTML : "";
let CountDownData = (document.querySelector("#countdown_list") != null) ? document.querySelector("#countdown_list").innerHTML : "";
if (document.querySelector("body > div > div.mt-3 > div > div.col-md-4") != null) document.querySelector("body > div > div.mt-3 > div > div.col-md-4").innerHTML = `
`;
} else if (location.pathname == "/showsource.php" && document.querySelector("body > div > div.mt-3") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/showsource.php") {
let Code = "";
if (SearchParams.get("ByUserScript") == null) {
document.title = "查看代码: " + SearchParams.get("id");
await fetch("https://www.xmoj.tech/getsource.php?id=" + SearchParams.get("id"))
.then((Response) => {
return Response.text();
}).then((Response) => {
Code = Response.replace("\n\n", "");
});
} else {
document.title = "查看标程: " + SearchParams.get("pid");
if (localStorage.getItem("UserScript-LastUploadedStdTime") === undefined || new Date().getTime() - localStorage.getItem("UserScript-LastUploadedStdTime") > 1000 * 60 * 60 * 24 * 30) {
location.href = "https://www.xmoj.tech/userinfo.php?ByUserScript=1";
}
await new Promise((Resolve) => {
RequestAPI("GetStd", {
"ProblemID": Number(SearchParams.get("pid"))
}, (Response) => {
if (Response.Success) {
Code = Response.Data.StdCode;
} else {
Code = Response.Message;
}
Resolve();
});
});
}
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = ``;
CodeMirror.fromTextArea(document.querySelector("body > div > div.mt-3 > textarea"), {
lineNumbers: true,
mode: "text/x-c++src",
readOnly: true,
theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default")
}).setSize("100%", "auto");
} else if (location.pathname == "/ceinfo.php") {
await fetch(location.href)
.then((Result) => {
return Result.text();
}).then((Result) => {
let ParsedDocument = new DOMParser().parseFromString(Result, "text/html");
if (!ParsedDocument.getElementsByClassName("jumbotron")[0].innerHTML.includes('I am sorry, You could not view this message!')) {
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = "";
let CodeElement = document.createElement("div");
CodeElement.className = "mb-3";
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").appendChild(CodeElement);
CodeMirror(CodeElement, {
value: ParsedDocument.getElementById("errtxt").innerHTML.replaceAll("<", "<").replaceAll(">", ">"),
lineNumbers: true,
mode: "text/x-c++src",
readOnly: true,
theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default")
}).setSize("100%", "auto");
}
});
} else if (location.pathname == "/mail.php" && document.querySelector("body > div > div.mt-3") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname == "/mail.php") {
if (SearchParams.get("to_user") == null) {
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = `
搜索新用户
`;
let RefreshMessageList = (Silent = true) => {
if (!Silent) {
ReceiveTable.children[1].innerHTML = "";
for (let i = 0; i < 10; i++) {
let Row = document.createElement("tr");
ReceiveTable.children[1].appendChild(Row);
for (let j = 0; j < 3; j++) {
let Cell = document.createElement("td");
Row.appendChild(Cell);
Cell.innerHTML = `
`;
}
}
}
RequestAPI("GetMailList", {}, async (ResponseData) => {
if (ResponseData.Success) {
ErrorElement.style.display = "none";
let Data = ResponseData.Data.MailList;
ReceiveTable.children[1].innerHTML = "";
for (let i = 0; i < Data.length; i++) {
let Row = document.createElement("tr");
ReceiveTable.children[1].appendChild(Row);
let UsernameCell = document.createElement("td");
Row.appendChild(UsernameCell);
let UsernameSpan = document.createElement("span");
UsernameCell.appendChild(UsernameSpan);
GetUsernameHTML(UsernameSpan, Data[i].OtherUser, false, "https://www.xmoj.tech/mail.php?to_user=");
if (Data[i].UnreadCount != 0) {
let UnreadCountSpan = document.createElement("span");
UsernameCell.appendChild(UnreadCountSpan);
UnreadCountSpan.className = "ms-1 badge text-bg-danger";
UnreadCountSpan.innerText = Data[i].UnreadCount;
}
let LastsMessageCell = document.createElement("td");
Row.appendChild(LastsMessageCell);
LastsMessageCell.innerText = replaceMarkdownImages(Data[i].LastsMessage, '[image]');
let SendTimeCell = document.createElement("td");
Row.appendChild(SendTimeCell);
SendTimeCell.innerHTML = GetRelativeTime(Data[i].SendTime);
}
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "";
}
});
};
Username.addEventListener("input", () => {
Username.classList.remove("is-invalid");
});
AddUser.addEventListener("click", () => {
let UsernameData = Username.value;
if (UsernameData == "") {
Username.classList.add("is-invalid");
return;
}
AddUser.children[0].style.display = "";
AddUser.disabled = true;
RequestAPI("SendMail", {
"ToUser": String(UsernameData),
"Content": String("您好,我是" + CurrentUsername)
}, (ResponseData) => {
AddUser.children[0].style.display = "none";
AddUser.disabled = false;
if (ResponseData.Success) {
ErrorElement.style.display = "none";
RefreshMessageList();
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "";
}
});
});
RefreshMessageList(false);
addEventListener("focus", RefreshMessageList);
} else {
if (document.querySelector("body > div > div.mt-3") != null) document.querySelector("body > div > div.mt-3").innerHTML = `
发送
`;
GetUsernameHTML(ToUser, SearchParams.get("to_user"));
let RefreshMessage = (Silent = true) => {
if (!Silent) {
MessageTable.children[1].innerHTML = "";
for (let i = 0; i < 10; i++) {
let Row = document.createElement("tr");
MessageTable.children[1].appendChild(Row);
for (let j = 0; j < 4; j++) {
let Cell = document.createElement("td");
Row.appendChild(Cell);
Cell.innerHTML = `
`;
}
}
}
RequestAPI("ReadUserMailMention", {
"UserID": String(SearchParams.get("to_user"))
});
RequestAPI("GetMail", {
"OtherUser": String(SearchParams.get("to_user"))
}, async (ResponseData) => {
if (ResponseData.Success) {
ErrorElement.style.display = "none";
let Data = ResponseData.Data.Mail;
MessageTable.children[1].innerHTML = "";
for (let i = 0; i < Data.length; i++) {
let Row = document.createElement("tr");
MessageTable.children[1].appendChild(Row);
if (!Data[i].IsRead && Data[i].FromUser != CurrentUsername) {
Row.className = "table-info";
}
let UsernameCell = document.createElement("td");
Row.appendChild(UsernameCell);
GetUsernameHTML(UsernameCell, Data[i].FromUser);
let ContentCell = document.createElement("td");
let ContentDiv = document.createElement("div");
ContentDiv.style.display = "flex";
ContentDiv.style.maxWidth = window.innerWidth - 300 + "px";
ContentDiv.style.maxHeight = "500px";
ContentDiv.style.overflowX = "auto";
ContentDiv.style.overflowY = "auto";
ContentDiv.innerHTML = PurifyHTML(marked.parse(Data[i].Content));
let mediaElements = ContentDiv.querySelectorAll('img, video');
for (let media of mediaElements) {
media.style.objectFit = 'contain';
media.style.maxWidth = '100%';
media.style.maxHeight = '100%';
}
ContentCell.appendChild(ContentDiv);
Row.appendChild(ContentCell);
let SendTimeCell = document.createElement("td");
Row.appendChild(SendTimeCell);
SendTimeCell.innerHTML = GetRelativeTime(Data[i].SendTime);
let IsReadCell = document.createElement("td");
Row.appendChild(IsReadCell);
IsReadCell.innerHTML = (Data[i].IsRead ? "已读" : "未读");
}
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "";
}
});
};
Content.addEventListener("input", () => {
Content.classList.remove("is-invalid");
});
Content.addEventListener("paste", (EventData) => {
let Items = EventData.clipboardData.items;
if (Items.length !== 0) {
for (let i = 0; i < Items.length; i++) {
if (Items[i].type.indexOf("image") != -1) {
let Reader = new FileReader();
Reader.readAsDataURL(Items[i].getAsFile());
Reader.onload = () => {
let Before = Content.value.substring(0, Content.selectionStart);
let After = Content.value.substring(Content.selectionEnd, Content.value.length);
const UploadMessage = "![正在上传图片...]()";
Content.value = Before + UploadMessage + After;
Content.dispatchEvent(new Event("input"));
RequestAPI("UploadImage", {
"Image": Reader.result
}, (ResponseData) => {
if (ResponseData.Success) {
Content.value = Before + `` + After;
Content.dispatchEvent(new Event("input"));
} else {
Content.value = Before + `![上传失败!` + ResponseData.Message + `]()` + After;
Content.dispatchEvent(new Event("input"));
}
});
};
}
}
}
});
Content.addEventListener("keydown", (Event) => {
if (Event.keyCode == 13) {
Send.click();
}
});
Send.addEventListener("click", () => {
if (Content.value == "") {
Content.classList.add("is-invalid");
return;
}
Send.disabled = true;
Send.children[0].style.display = "";
let ContentData = Content.value;
RequestAPI("SendMail", {
"ToUser": String(SearchParams.get("to_user")), "Content": String(ContentData)
}, (ResponseData) => {
Send.disabled = false;
Send.children[0].style.display = "none";
if (ResponseData.Success) {
ErrorElement.style.display = "none";
Content.value = "";
RefreshMessage();
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "";
}
});
});
RefreshMessage(false);
addEventListener("focus", RefreshMessage);
}
} else if (location.pathname.indexOf("/discuss3") != -1 && document.querySelector("body > div > div") == null) {
//页面结构异常(如403/404页面),跳过处理
} else if (location.pathname.indexOf("/discuss3") != -1) {
if (UtilityEnabled("Discussion")) {
Discussion.classList.add("active");
if (location.pathname == "/discuss3/discuss.php") {
document.title = "讨论列表";
let ProblemID = parseInt(SearchParams.get("pid"));
let BoardID = parseInt(SearchParams.get("bid"));
let Page = Number(SearchParams.get("page")) || 1;
if (document.querySelector("body > div > div") != null) document.querySelector("body > div > div").innerHTML = `
讨论列表${(isNaN(ProblemID) ? "" : ` - 题目` + ProblemID)}
发布新讨论
编号
标题
作者
题目编号
发布时间
回复数
最后回复
`;
NewPost.addEventListener("click", () => {
if (!isNaN(ProblemID)) {
location.href = "https://www.xmoj.tech/discuss3/newpost.php?pid=" + ProblemID;
} else if (SearchParams.get("bid") != null) {
location.href = "https://www.xmoj.tech/discuss3/newpost.php?bid=" + SearchParams.get("bid");
} else {
location.href = "https://www.xmoj.tech/discuss3/newpost.php";
}
});
const RefreshPostList = (Silent = true) => {
if (!Silent) {
PostList.children[1].innerHTML = "";
for (let i = 0; i < 10; i++) {
let Row = document.createElement("tr");
PostList.children[1].appendChild(Row);
for (let j = 0; j < 7; j++) {
let Cell = document.createElement("td");
Row.appendChild(Cell);
Cell.innerHTML = `
`;
}
}
}
RequestAPI("GetPosts", {
"ProblemID": Number(ProblemID || 0),
"Page": Number(Page),
"BoardID": Number(SearchParams.get("bid") || -1)
}, async (ResponseData) => {
if (ResponseData.Success == true) {
ErrorElement.style.display = "none";
if (!Silent) {
DiscussPagination.children[0].children[0].href = "https://www.xmoj.tech/discuss3/discuss.php?" + (isNaN(ProblemID) ? "" : "pid=" + ProblemID + "&") + (isNaN(BoardID) ? "" : "bid=" + BoardID + "&") + "page=1";
DiscussPagination.children[1].children[0].href = "https://www.xmoj.tech/discuss3/discuss.php?" + (isNaN(ProblemID) ? "" : "pid=" + ProblemID + "&") + (isNaN(BoardID) ? "" : "bid=" + BoardID + "&") + "page=" + (Page - 1);
DiscussPagination.children[2].children[0].href = "https://www.xmoj.tech/discuss3/discuss.php?" + (isNaN(ProblemID) ? "" : "pid=" + ProblemID + "&") + (isNaN(BoardID) ? "" : "bid=" + BoardID + "&") + "page=" + Page;
DiscussPagination.children[3].children[0].href = "https://www.xmoj.tech/discuss3/discuss.php?" + (isNaN(ProblemID) ? "" : "pid=" + ProblemID + "&") + (isNaN(BoardID) ? "" : "bid=" + BoardID + "&") + "page=" + (Page + 1);
DiscussPagination.children[4].children[0].href = "https://www.xmoj.tech/discuss3/discuss.php?" + (isNaN(ProblemID) ? "" : "pid=" + ProblemID + "&") + (isNaN(BoardID) ? "" : "bid=" + BoardID + "&") + "page=" + ResponseData.Data.PageCount;
if (Page <= 1) {
DiscussPagination.children[0].classList.add("disabled");
DiscussPagination.children[1].remove();
}
if (Page >= ResponseData.Data.PageCount) {
DiscussPagination.children[DiscussPagination.children.length - 1].classList.add("disabled");
DiscussPagination.children[DiscussPagination.children.length - 2].remove();
}
}
let Posts = ResponseData.Data.Posts;
PostList.children[1].innerHTML = "";
if (Posts.length == 0) {
PostList.children[1].innerHTML = `
暂无数据 `;
}
for (let i = 0; i < Posts.length; i++) {
let Row = document.createElement("tr");
PostList.children[1].appendChild(Row);
let IDCell = document.createElement("td");
Row.appendChild(IDCell);
IDCell.innerText = Posts[i].PostID + " " + Posts[i].BoardName;
let TitleCell = document.createElement("td");
Row.appendChild(TitleCell);
let TitleLink = document.createElement("a");
TitleCell.appendChild(TitleLink);
TitleLink.href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + Posts[i].PostID;
if (Posts[i].Lock.Locked) {
TitleLink.classList.add("link-secondary");
TitleLink.innerHTML = "🔒 ";
}
TitleLink.innerHTML += escapeHTML(Posts[i].Title);
let AuthorCell = document.createElement("td");
Row.appendChild(AuthorCell);
GetUsernameHTML(AuthorCell, Posts[i].UserID);
let ProblemIDCell = document.createElement("td");
Row.appendChild(ProblemIDCell);
if (Posts[i].ProblemID != 0) {
let ProblemIDLink = document.createElement("a");
ProblemIDCell.appendChild(ProblemIDLink);
ProblemIDLink.href = "https://www.xmoj.tech/problem.php?id=" + Posts[i].ProblemID;
ProblemIDLink.innerText = Posts[i].ProblemID;
}
let PostTimeCell = document.createElement("td");
Row.appendChild(PostTimeCell);
PostTimeCell.innerHTML = GetRelativeTime(Posts[i].PostTime);
let ReplyCountCell = document.createElement("td");
Row.appendChild(ReplyCountCell);
ReplyCountCell.innerText = Posts[i].ReplyCount;
let LastReplyTimeCell = document.createElement("td");
Row.appendChild(LastReplyTimeCell);
LastReplyTimeCell.innerHTML = GetRelativeTime(Posts[i].LastReplyTime);
}
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "block";
}
});
};
RefreshPostList(false);
addEventListener("focus", RefreshPostList);
RequestAPI("GetBoards", {}, (ResponseData) => {
if (ResponseData.Success === true) {
let LinkElement = document.createElement("a");
LinkElement.href = "https://www.xmoj.tech/discuss3/discuss.php";
LinkElement.classList.add("me-2");
LinkElement.innerText = "全部";
GotoBoard.appendChild(LinkElement);
for (let i = 0; i < ResponseData.Data.Boards.length; i++) {
let LinkElement = document.createElement("a");
LinkElement.href = "https://www.xmoj.tech/discuss3/discuss.php?bid=" + ResponseData.Data.Boards[i].BoardID;
LinkElement.classList.add("me-2");
LinkElement.innerText = ResponseData.Data.Boards[i].BoardName;
GotoBoard.appendChild(LinkElement);
}
}
});
} else if (location.pathname == "/discuss3/newpost.php") {
let ProblemID = parseInt(SearchParams.get("pid"));
if (document.querySelector("body > div > div") != null) document.querySelector("body > div > div").innerHTML = `
发布新讨论` + (!isNaN(ProblemID) ? ` - 题目` + ProblemID : ``) + `
标题
回复
发布
`;
let CaptchaSecretKey = "";
unsafeWindow.CaptchaLoadedCallback = () => {
turnstile.render("#CaptchaContainer", {
sitekey: CaptchaSiteKey, callback: function (CaptchaSecretKeyValue) {
CaptchaSecretKey = CaptchaSecretKeyValue;
SubmitElement.disabled = false;
},
});
};
let TurnstileScript = document.createElement("script");
TurnstileScript.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=CaptchaLoadedCallback";
document.body.appendChild(TurnstileScript);
ContentElement.addEventListener("keydown", (Event) => {
if ((Event.metaKey || Event.ctrlKey) && Event.keyCode == 13) {
SubmitElement.click();
}
});
ContentElement.addEventListener("input", () => {
ContentElement.classList.remove("is-invalid");
PreviewTab.innerHTML = PurifyHTML(marked.parse(ContentElement.value));
RenderMathJax();
});
TitleElement.addEventListener("input", () => {
TitleElement.classList.remove("is-invalid");
});
ContentElement.addEventListener("paste", (EventData) => {
let Items = EventData.clipboardData.items;
if (Items.length !== 0) {
for (let i = 0; i < Items.length; i++) {
if (Items[i].type.indexOf("image") != -1) {
let Reader = new FileReader();
Reader.readAsDataURL(Items[i].getAsFile());
Reader.onload = () => {
let Before = ContentElement.value.substring(0, ContentElement.selectionStart);
let After = ContentElement.value.substring(ContentElement.selectionEnd, ContentElement.value.length);
const UploadMessage = "![正在上传图片...]()";
ContentElement.value = Before + UploadMessage + After;
ContentElement.dispatchEvent(new Event("input"));
RequestAPI("UploadImage", {
"Image": Reader.result
}, (ResponseData) => {
if (ResponseData.Success) {
ContentElement.value = Before + `` + After;
ContentElement.dispatchEvent(new Event("input"));
} else {
ContentElement.value = Before + `![上传失败!]()` + After;
ContentElement.dispatchEvent(new Event("input"));
}
});
};
}
}
}
});
SubmitElement.addEventListener("click", async () => {
ErrorElement.style.display = "none";
let Title = TitleElement.value;
let Content = ContentElement.value;
let ProblemID = parseInt(SearchParams.get("pid"));
if (Title === "") {
TitleElement.classList.add("is-invalid");
return;
}
if (Content === "") {
ContentElement.classList.add("is-invalid");
return;
}
if (document.querySelector("#Board input:checked") === null) {
ErrorElement.innerText = "请选择要发布的板块";
ErrorElement.style.display = "block";
return;
}
SubmitElement.disabled = true;
SubmitElement.children[0].style.display = "inline-block";
RequestAPI("NewPost", {
"Title": String(Title),
"Content": String(Content),
"ProblemID": Number(isNaN(ProblemID) ? 0 : ProblemID),
"CaptchaSecretKey": String(CaptchaSecretKey),
"BoardID": Number(document.querySelector("#Board input:checked").value)
}, (ResponseData) => {
SubmitElement.disabled = false;
SubmitElement.children[0].style.display = "none";
if (ResponseData.Success == true) {
location.href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + ResponseData.Data.PostID;
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "block";
}
});
});
RequestAPI("GetBoards", {}, (ResponseData) => {
if (ResponseData.Success === true) {
let Data = ResponseData.Data.Boards;
for (let i = 0; i < Data.length; i++) {
let RadioElement = document.createElement("div");
RadioElement.className = "col-auto form-check form-check-inline";
let RadioInput = document.createElement("input");
RadioInput.className = "form-check-input";
RadioInput.type = "radio";
RadioInput.name = "Board";
RadioInput.id = "Board" + Data[i].BoardID;
RadioInput.value = Data[i].BoardID;
RadioElement.appendChild(RadioInput);
if (SearchParams.get("bid") !== null && SearchParams.get("bid") == Data[i].BoardID) {
RadioInput.checked = true;
}
if (!isNaN(ProblemID)) {
RadioInput.disabled = true;
}
if (Data[i].BoardID == 4) {
if (!isNaN(ProblemID)) RadioInput.checked = true;
RadioInput.disabled = true;
}
let RadioLabel = document.createElement("label");
RadioLabel.className = "form-check-label";
RadioLabel.htmlFor = "Board" + Data[i].BoardID;
RadioLabel.innerText = Data[i].BoardName;
RadioElement.appendChild(RadioLabel);
Board.appendChild(RadioElement);
}
}
});
} else if (location.pathname == "/discuss3/thread.php") {
if (SearchParams.get("tid") == null) {
location.href = "https://www.xmoj.tech/discuss3/discuss.php";
} else {
let ThreadID = SearchParams.get("tid");
let Page = Number(SearchParams.get("page")) || 1;
if (document.querySelector("body > div > div") != null) document.querySelector("body > div > div").innerHTML = `
作者:
发布时间:
板块:
删除
发布
`;
let CaptchaSecretKey = "";
unsafeWindow.CaptchaLoadedCallback = () => {
turnstile.render("#CaptchaContainer", {
theme: UtilityEnabled("DarkMode") ? "dark" : "light", language: "zh-cn",
sitekey: CaptchaSiteKey, callback: function (CaptchaSecretKeyValue) {
CaptchaSecretKey = CaptchaSecretKeyValue;
SubmitElement.disabled = false;
},
});
};
let TurnstileScript = document.createElement("script");
TurnstileScript.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=CaptchaLoadedCallback";
document.body.appendChild(TurnstileScript);
ContentElement.addEventListener("keydown", (Event) => {
if ((Event.metaKey || Event.ctrlKey) && Event.keyCode == 13) {
SubmitElement.click();
}
});
ContentElement.addEventListener("input", () => {
PreviewTab.innerHTML = PurifyHTML(marked.parse(ContentElement.value));
RenderMathJax();
});
ContentElement.addEventListener("paste", (EventData) => {
let Items = EventData.clipboardData.items;
if (Items.length !== 0) {
for (let i = 0; i < Items.length; i++) {
if (Items[i].type.indexOf("image") != -1) {
let Reader = new FileReader();
Reader.readAsDataURL(Items[i].getAsFile());
Reader.onload = () => {
let Before = ContentElement.value.substring(0, ContentElement.selectionStart);
let After = ContentElement.value.substring(ContentElement.selectionEnd, ContentElement.value.length);
const UploadMessage = "![正在上传图片...]()";
ContentElement.value = Before + UploadMessage + After;
ContentElement.dispatchEvent(new Event("input"));
RequestAPI("UploadImage", {
"Image": Reader.result
}, (ResponseData) => {
if (ResponseData.Success) {
ContentElement.value = Before + `` + After;
ContentElement.dispatchEvent(new Event("input"));
} else {
ContentElement.value = Before + `![上传失败!]()` + After;
ContentElement.dispatchEvent(new Event("input"));
}
});
};
}
}
}
});
let RefreshReply = (Silent = true) => {
if (!Silent) {
PostTitle.innerHTML = ` `;
PostAuthor.innerHTML = ` `;
PostTime.innerHTML = ` `;
PostBoard.innerHTML = ` `;
PostReplies.innerHTML = "";
for (let i = 0; i < 10; i++) {
PostReplies.innerHTML += ``;
}
}
RequestAPI("GetPost", {
"PostID": Number(ThreadID), "Page": Number(Page)
}, async (ResponseData) => {
if (ResponseData.Success == true) {
let OldScrollTop = document.documentElement.scrollTop;
let LockButtons = !IsAdmin && ResponseData.Data.Lock.Locked;
if (!Silent) {
DiscussPagination.children[0].children[0].href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + ThreadID + "&page=1";
DiscussPagination.children[1].children[0].href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + ThreadID + "&page=" + (Page - 1);
DiscussPagination.children[2].children[0].href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + ThreadID + "&page=" + Page;
DiscussPagination.children[3].children[0].href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + ThreadID + "&page=" + (Page + 1);
DiscussPagination.children[4].children[0].href = "https://www.xmoj.tech/discuss3/thread.php?tid=" + ThreadID + "&page=" + ResponseData.Data.PageCount;
if (Page <= 1) {
DiscussPagination.children[0].classList.add("disabled");
DiscussPagination.children[1].remove();
}
if (Page >= ResponseData.Data.PageCount) {
DiscussPagination.children[DiscussPagination.children.length - 1].classList.add("disabled");
DiscussPagination.children[DiscussPagination.children.length - 2].remove();
}
if (IsAdmin || ResponseData.Data.UserID == CurrentUsername) {
Delete.style.display = "";
}
}
PostTitle.innerHTML = escapeHTML(ResponseData.Data.Title) + (ResponseData.Data.ProblemID == 0 ? "" : ` - 题目` + ` ` + ResponseData.Data.ProblemID + ` `);
document.title = "讨论" + ThreadID + ": " + ResponseData.Data.Title;
PostAuthor.innerHTML = " ";
GetUsernameHTML(PostAuthor.children[0], ResponseData.Data.UserID);
PostTime.innerHTML = GetRelativeTime(ResponseData.Data.PostTime);
PostBoard.innerHTML = escapeHTML(ResponseData.Data.BoardName);
let Replies = ResponseData.Data.Reply;
PostReplies.innerHTML = "";
for (let i = 0; i < Replies.length; i++) {
let CardElement = document.createElement("div");
PostReplies.appendChild(CardElement);
CardElement.className = "card mb-3";
let CardBodyElement = document.createElement("div");
CardElement.appendChild(CardBodyElement);
CardBodyElement.className = "card-body row";
let CardBodyRowElement = document.createElement("div");
CardBodyElement.appendChild(CardBodyRowElement);
CardBodyRowElement.className = "row mb-3";
let AuthorElement = document.createElement("span");
CardBodyRowElement.appendChild(AuthorElement);
AuthorElement.className = "col-4 text-muted";
let AuthorSpanElement = document.createElement("span");
AuthorElement.appendChild(AuthorSpanElement);
AuthorSpanElement.innerText = "作者:";
let AuthorUsernameElement = document.createElement("span");
AuthorElement.appendChild(AuthorUsernameElement);
GetUsernameHTML(AuthorUsernameElement, Replies[i].UserID);
let SendTimeElement = document.createElement("span");
CardBodyRowElement.appendChild(SendTimeElement);
SendTimeElement.className = "col-4 text-muted";
SendTimeElement.innerHTML = "发布时间:" + GetRelativeTime(Replies[i].ReplyTime);
let OKButton;
if (!LockButtons) {
let ButtonsElement = document.createElement("span");
CardBodyRowElement.appendChild(ButtonsElement);
ButtonsElement.className = "col-4";
let ReplyButton = document.createElement("button");
ButtonsElement.appendChild(ReplyButton);
ReplyButton.type = "button";
ReplyButton.className = "btn btn-sm btn-info";
ReplyButton.innerText = "回复";
ReplyButton.addEventListener("click", () => {
let Content = Replies[i].Content;
Content = Content.split("\n").map((Line) => {
// Count the number of '>' characters at the beginning of the line
let nestingLevel = 0;
while (Line.startsWith(">")) {
nestingLevel++;
Line = Line.substring(1).trim();
}
// If the line is nested more than 2 levels deep, skip it
if (nestingLevel > 2) {
return null;
}
// Reconstruct the line with the appropriate number of '>' characters
return "> ".repeat(nestingLevel + 1) + Line;
}).filter(Line => Line !== null) // Remove null entries
.join("\n");
ContentElement.value += Content + `\n\n@${Replies[i].UserID} `;
ContentElement.focus();
});
let DeleteButton = document.createElement("button");
ButtonsElement.appendChild(DeleteButton);
DeleteButton.type = "button";
DeleteButton.className = "btn btn-sm btn-danger ms-1";
DeleteButton.innerText = "删除";
DeleteButton.style.display = (IsAdmin || Replies[i].UserID == CurrentUsername ? "" : "none");
DeleteButton.addEventListener("click", () => {
DeleteButton.disabled = true;
DeleteButton.lastChild.style.display = "";
RequestAPI("DeleteReply", {
"ReplyID": Number(Replies[i].ReplyID)
}, (ResponseData) => {
if (ResponseData.Success == true) {
RefreshReply();
} else {
DeleteButton.disabled = false;
DeleteButton.lastChild.style.display = "none";
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "";
}
});
});
let DeleteSpin = document.createElement("div");
DeleteButton.appendChild(DeleteSpin);
DeleteSpin.className = "spinner-border spinner-border-sm";
DeleteSpin.role = "status";
DeleteSpin.style.display = "none";
OKButton = document.createElement("button");
ButtonsElement.appendChild(OKButton);
OKButton.type = "button";
OKButton.style.display = "none";
OKButton.className = "btn btn-sm btn-success ms-1";
OKButton.innerText = "确认";
let OKSpin = document.createElement("div");
OKButton.appendChild(OKSpin);
OKSpin.className = "spinner-border spinner-border-sm";
OKSpin.role = "status";
OKSpin.style.display = "none";
OKButton.addEventListener("click", () => {
OKButton.disabled = true;
OKButton.lastChild.style.display = "";
RequestAPI("EditReply", {
ReplyID: Number(Replies[i].ReplyID),
Content: String(ContentEditor.value)
}, (ResponseData) => {
if (ResponseData.Success == true) {
RefreshReply();
} else {
OKButton.disabled = false;
OKButton.lastChild.style.display = "none";
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "";
}
});
});
let CancelButton = document.createElement("button");
ButtonsElement.appendChild(CancelButton);
CancelButton.type = "button";
CancelButton.style.display = "none";
CancelButton.className = "btn btn-sm btn-secondary ms-1";
CancelButton.innerText = "取消";
CancelButton.addEventListener("click", () => {
CardBodyElement.children[2].style.display = "";
CardBodyElement.children[3].style.display = "none";
EditButton.style.display = "";
OKButton.style.display = "none";
CancelButton.style.display = "none";
});
let EditButton = document.createElement("button");
ButtonsElement.appendChild(EditButton);
EditButton.type = "button";
EditButton.className = "btn btn-sm btn-warning ms-1";
EditButton.innerText = "编辑";
EditButton.style.display = (IsAdmin || Replies[i].UserID == CurrentUsername ? "" : "none");
EditButton.addEventListener("click", () => {
CardBodyElement.children[2].style.display = "none";
CardBodyElement.children[3].style.display = "";
EditButton.style.display = "none";
OKButton.style.display = "";
CancelButton.style.display = "";
});
}
let CardBodyHRElement = document.createElement("hr");
CardBodyElement.appendChild(CardBodyHRElement);
let ReplyContentElement = document.createElement("div");
CardBodyElement.appendChild(ReplyContentElement);
ReplyContentElement.innerHTML = PurifyHTML(marked.parse(Replies[i].Content)).replaceAll(/@([a-zA-Z0-9]+)/g, `@ $1 `);
if (Replies[i].EditTime != null) {
if (Replies[i].EditPerson == Replies[i].UserID) {
ReplyContentElement.innerHTML += `最后编辑于${GetRelativeTime(Replies[i].EditTime)} `;
} else {
ReplyContentElement.innerHTML += `最后被${escapeHTML(Replies[i].EditPerson)} 编辑于${GetRelativeTime(Replies[i].EditTime)} `;
}
}
let ContentEditElement = document.createElement("div");
CardBodyElement.appendChild(ContentEditElement);
ContentEditElement.classList.add("input-group");
ContentEditElement.style.display = "none";
let ContentEditor = document.createElement("textarea");
ContentEditElement.appendChild(ContentEditor);
ContentEditor.className = "form-control col-6";
ContentEditor.rows = 3;
ContentEditor.value = Replies[i].Content;
if (ContentEditor.value.indexOf(" ") != -1) {
ContentEditor.value = ContentEditor.value.substring(0, ContentEditor.value.indexOf(" "));
}
ContentEditor.addEventListener("keydown", (Event) => {
if ((Event.metaKey || Event.ctrlKey) && Event.keyCode == 13) {
OKButton.click();
}
});
let PreviewTab = document.createElement("div");
ContentEditElement.appendChild(PreviewTab);
PreviewTab.className = "form-control col-6";
PreviewTab.innerHTML = PurifyHTML(marked.parse(ContentEditor.value));
ContentEditor.addEventListener("input", () => {
PreviewTab.innerHTML = PurifyHTML(marked.parse(ContentEditor.value));
RenderMathJax();
});
ContentEditor.addEventListener("paste", (EventData) => {
let Items = EventData.clipboardData.items;
if (Items.length !== 0) {
for (let i = 0; i < Items.length; i++) {
if (Items[i].type.indexOf("image") != -1) {
let Reader = new FileReader();
Reader.readAsDataURL(Items[i].getAsFile());
Reader.onload = () => {
let Before = ContentEditor.value.substring(0, ContentEditor.selectionStart);
let After = ContentEditor.value.substring(ContentEditor.selectionEnd, ContentEditor.value.length);
const UploadMessage = "![正在上传图片...]()";
ContentEditor.value = Before + UploadMessage + After;
ContentEditor.dispatchEvent(new Event("input"));
RequestAPI("UploadImage", {
"Image": Reader.result
}, (ResponseData) => {
if (ResponseData.Success) {
ContentEditor.value = Before + `` + After;
ContentEditor.dispatchEvent(new Event("input"));
} else {
ContentEditor.value = Before + `![上传失败!]()` + After;
ContentEditor.dispatchEvent(new Event("input"));
}
});
};
}
}
}
});
}
let UsernameElements = document.getElementsByClassName("Usernames");
for (let i = 0; i < UsernameElements.length; i++) {
GetUsernameHTML(UsernameElements[i], UsernameElements[i].innerText, true);
}
let CodeElements = document.querySelectorAll("#PostReplies > div > div > div:nth-child(3) > pre > code");
for (let i = 0; i < CodeElements.length; i++) {
let ModeName = "text/x-c++src";
if (CodeElements[i].className == "language-c") {
ModeName = "text/x-csrc";
} else if (CodeElements[i].className == "language-cpp") {
ModeName = "text/x-c++src";
}
CodeMirror(CodeElements[i].parentElement, {
value: CodeElements[i].innerText,
mode: ModeName,
theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default"),
lineNumbers: true,
readOnly: true
}).setSize("100%", "auto");
CodeElements[i].remove();
}
if (LockButtons) {
let LockElement = ContentElement.parentElement.parentElement;
LockElement.innerHTML = "讨论已于 " + await GetRelativeTime(ResponseData.Data.Lock.LockTime) + " 被 ";
let LockUsernameSpan = document.createElement("span");
LockElement.appendChild(LockUsernameSpan);
GetUsernameHTML(LockUsernameSpan, ResponseData.Data.Lock.LockPerson);
LockElement.innerHTML += " 锁定";
LockElement.classList.add("mb-5");
}
if (IsAdmin) {
ToggleLock.style.display = "inline-block";
ToggleLockButton.checked = ResponseData.Data.Lock.Locked;
ToggleLockButton.onclick = () => {
ToggleLockButton.disabled = true;
ErrorElement.style.display = "none";
RequestAPI((ToggleLockButton.checked ? "LockPost" : "UnlockPost"), {
"PostID": Number(ThreadID)
}, (LockResponseData) => {
ToggleLockButton.disabled = false;
if (LockResponseData.Success) {
RefreshReply();
} else {
ErrorElement.style.display = "";
ErrorElement.innerText = "错误:" + LockResponseData.Message;
ToggleLockButton.checked = !ToggleLockButton.checked;
}
});
};
}
Style.innerHTML += "img {";
Style.innerHTML += " width: 50%;";
Style.innerHTML += "}";
RenderMathJax();
if (Silent) {
scrollTo({
top: OldScrollTop, behavior: "instant"
});
}
} else {
PostTitle.innerText = "错误:" + ResponseData.Message;
}
});
};
Delete.addEventListener("click", () => {
Delete.disabled = true;
Delete.children[0].style.display = "inline-block";
RequestAPI("DeletePost", {
"PostID": Number(SearchParams.get("tid"))
}, (ResponseData) => {
Delete.disabled = false;
Delete.children[0].style.display = "none";
if (ResponseData.Success == true) {
location.href = "https://www.xmoj.tech/discuss3/discuss.php";
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "block";
}
});
});
SubmitElement.addEventListener("click", async () => {
ErrorElement.style.display = "none";
SubmitElement.disabled = true;
SubmitElement.children[0].style.display = "inline-block";
RequestAPI("NewReply", {
"PostID": Number(SearchParams.get("tid")),
"Content": String(ContentElement.value),
"CaptchaSecretKey": String(CaptchaSecretKey)
}, async (ResponseData) => {
SubmitElement.disabled = false;
SubmitElement.children[0].style.display = "none";
if (ResponseData.Success == true) {
RefreshReply();
ContentElement.value = "";
PreviewTab.innerHTML = "";
while (PostReplies.innerHTML.indexOf("placeholder") != -1) {
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
}
ContentElement.focus();
ContentElement.scrollIntoView();
turnstile.reset();
} else {
ErrorElement.innerText = ResponseData.Message;
ErrorElement.style.display = "block";
}
});
});
RefreshReply(false);
addEventListener("focus", RefreshReply);
}
}
}
}
}
}
InitializeImageEnlarger();
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
setTimeout(RevealPage, 1500);
await main();
RevealPage();
console.log("XMOJ-Script loaded successfully!");
})().catch(e => {
console.error("[XMOJ-Script] Initialization error:", e);
});
function GetMDText(element) {
let result = '';
const blockTags = new Set([
'P', 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV',
'UL', 'OL', 'LI', 'PRE', 'BLOCKQUOTE',
'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'
]);
const cellTags = new Set(['TD', 'TH']);
const mathItems = new Map();
const mathDocument = (typeof unsafeWindow === 'undefined' ? window : unsafeWindow).MathJax?.startup?.document;
// XMOJ also serves MathJax 3.0, before getMathItemsWithin was available.
const items = typeof mathDocument?.getMathItemsWithin === 'function' ?
mathDocument.getMathItemsWithin([element]) : mathDocument?.math || [];
for (const math of items) {
if (math.typesetRoot && element.contains(math.typesetRoot) && typeof math.math === 'string') {
mathItems.set(math.typesetRoot, math);
}
}
function AppendBreak(count) {
if (!result) return;
const trailing = result.match(/\n*$/)[0].length;
if (trailing < count) result += '\n'.repeat(count - trailing);
}
function AppendContent(content, cell) {
if (cell?.breakPending) {
AppendBreak(1);
cell.breakPending = false;
}
result += content;
}
function ReadMath(node) {
const math = mathItems.get(node);
if (math) return math;
if (node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('katex') ||
node.classList.contains('katex-display') || node.nodeName.toUpperCase() === 'MATH')) {
const annotation = node.querySelector('annotation[encoding="application/x-tex"]');
if (annotation) return {math: annotation.textContent, display: node.classList.contains('katex-display') ||
node.parentElement?.classList.contains('katex-display') || node.getAttribute('display') === 'block'};
}
return null;
}
function AppendTable(table) {
const rows = [...table.rows];
const first = rows[0];
const hasHeader = first && first.cells.length > 0 && (first.parentElement.nodeName === 'THEAD' ||
[...first.cells].every(cell => cell.nodeName === 'TH'));
// Pipe tables require a header and cannot represent merged cells, nested tables or code blocks.
// Also retain HTML for links so their destinations are preserved.
// Replace rendered math with its source.
if (!hasHeader || table.querySelector('table, pre, a') || rows.some(row => [...row.cells].some(cell => cell.colSpan !== 1 || cell.rowSpan !== 1)) ||
table.tHead?.rows.length > 1) {
const copy = table.cloneNode(true);
function RestoreMath(original, clone) {
if (original.nodeName === 'PRE') return;
const math = ReadMath(original);
if (math) {
clone.replaceWith(document.createTextNode((math.display ? '$$' : '$') + math.math + (math.display ? '$$' : '$')));
return;
}
for (const attribute of ['src', 'href']) {
const value = original.getAttribute?.(attribute);
if (value) {
try {
clone.setAttribute(attribute, new URL(value, location.href).href);
} catch (e) {
// Keep the source value when it is not a valid URL.
}
}
}
const children = [...clone.childNodes];
[...original.childNodes].forEach((child, index) => RestoreMath(child, children[index]));
}
RestoreMath(table, copy);
AppendBreak(2);
// Encode literal line endings so blank lines inside HTML do not end
// the Markdown HTML block. PRE contents retain them after rendering.
AppendContent(copy.outerHTML.replace(/\r\n|\r|\n/g, '
'));
AppendBreak(2);
return;
}
if (table.caption) {
AppendBreak(2);
Traverse(table.caption);
AppendBreak(2);
}
if (!rows.length || !rows.some(row => row.cells.length)) return;
const values = rows.map(row => [...row.cells].map(cell => {
const previous = result;
result = '';
Traverse(cell);
const value = result.replace(/\t$/, '').trim().replace(/(\\*)\|/g, (_, slashes) => slashes + slashes + '\\|')
.replace(/ *\r?\n/g, ' ');
result = previous;
return value;
}));
const width = Math.max(...values.map(row => row.length));
const header = values.shift();
const Line = row => '| ' + Array.from({length: width}, (_, index) => row[index] || '').join(' | ') + ' |';
const separators = Array.from({length: width}, (_, index) => {
const cell = first.cells[index];
const alignment = cell?.style.textAlign || cell?.getAttribute('align');
return alignment === 'center' ? ':---:' : alignment === 'right' ? '---:' : alignment === 'left' ? ':---' : '---';
});
AppendBreak(2);
AppendContent([Line(header), Line(separators), ...values.map(Line)].join('\n'));
AppendBreak(2);
}
function AppendMath(tex, display, inCell) {
// Keep a table cell on its row, including its trailing tab separator.
if (display && !inCell) AppendBreak(2);
AppendContent(display ? (inCell ? '$$' + tex + '$$' : '$$\n' + tex + '\n$$') : '$' + tex + '$', inCell);
if (display && !inCell) AppendBreak(2);
}
function Traverse(node, inPre = false, inCell = false) {
if (node.nodeType === Node.TEXT_NODE) {
// Serialized HTML often has indentation between blocks and cells.
// Keep inline spaces and all preformatted text.
const boundary = node => blockTags.has(node?.nodeName) || cellTags.has(node?.nodeName);
const before = boundary(node.previousSibling) || (boundary(node.parentElement) && !node.previousSibling);
const after = boundary(node.nextSibling) || (boundary(node.parentElement) && !node.nextSibling);
let text = node.textContent;
if (!inPre && /^[ \t\r\n]*$/.test(text) && (before || after)) return;
if (!inPre) {
text = text.replace(/[ \t\r\n]+/g, ' ');
if (before) text = text.replace(/^ +/, '');
if (after) text = text.replace(/ +$/, '');
}
AppendContent(text, inCell);
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) {
return;
}
const tag = node.nodeName.toUpperCase();
if (tag === 'PRE' && !inPre) {
// Read literal code (including highlighted spans) without converting math
// or collapsing its indentation. BR is also used by older editors.
function ReadCode(child) {
if (child.nodeType === Node.TEXT_NODE) return child.textContent;
if (child.nodeName === 'BR') return '\n';
return [...child.childNodes].map(ReadCode).join('');
}
const code = ReadCode(node);
if (inCell) {
inCell.breakPending = true;
AppendContent(code, inCell);
inCell.breakPending = true;
} else {
const classes = [node.querySelector('code')?.className, node.className].filter(Boolean).join(' ');
const language = classes.match(/(?:^|\s)(?:language|lang)-([\w+-]+)(?=\s|$)/)?.[1] || 'plain';
AppendBreak(2);
AppendContent(GetMarkdownCodeBlock(code, language));
AppendBreak(2);
}
return;
}
if (tag === 'TABLE') {
AppendTable(node);
return;
}
// Copy the source once, rather than the visual and accessibility trees.
const math = ReadMath(node);
if (math) {
AppendMath(math.math, math.display, inCell);
return;
}
// Preserve line breaks for
if (tag === 'BR') {
AppendContent(inPre ? '\n' : ' \n', inCell);
return;
}
// Convert images to Markdown
if (tag === 'IMG') {
const src = node.getAttribute('src');
if (src) {
let resolvedSrc = src;
try {
resolvedSrc = new URL(src, location.href).href;
} catch (e) {
// Fallback to the raw src if URL construction fails
}
AppendContent(``, inCell);
}
return;
}
const isBlock = blockTags.has(tag);
const isCell = cellTags.has(tag);
const breaks = tag === 'TR' ? 1 : 2;
// Each cell has its own buffer. Defer block breaks until its next content
// so nested paragraphs separate without leading/trailing row breaks.
if (isCell) {
const previous = result;
result = '';
const cell = {breakPending: false};
for (const child of node.childNodes) Traverse(child, inPre, cell);
result = previous + result + '\t';
return;
}
if (isBlock && !inPre) {
if (inCell) inCell.breakPending = true;
else AppendBreak(breaks);
}
for (let child of node.childNodes) {
Traverse(child, inPre || tag === 'PRE', inCell);
}
if (isBlock && !inPre) {
if (inCell) inCell.breakPending = true;
else AppendBreak(breaks);
}
}
Traverse(element);
return result;
}
function GetMarkdownCodeBlock(code, language = "plain") {
// A backtick run in the source must not terminate the copied code block.
const runs = [...code.matchAll(/`+/g)].map(match => match[0].length + 1);
const fence = "`".repeat(Math.max(3, ...runs));
return fence + language + "\n" + code + (code.endsWith("\n") ? "" : "\n") + fence;
}
// Use the displayed section labels, excluding copy controls and alternate languages.
function GetProblemCopyHeading(element, language = "zh") {
if (!element) return "";
const copy = element.cloneNode(true);
const otherLanguage = language === "en" ? ".lang_cn" : ".lang_en";
for (const node of copy.querySelectorAll("button, .copy-btn, [data-xmoj-script], " + otherLanguage)) node.remove();
return copy.textContent.replace(/\s+/g, " ").trim();
}
function GetProblemCopyLanguage(root) {
const english = root.querySelector(".content.lang_en");
return english?.isConnected && !english.closest("[hidden], .hidden, .d-none") &&
getComputedStyle(english).display !== "none" ? "en" : "zh";
}
function GetProblemSectionMarkdown(section, language = "zh") {
const heading = GetProblemCopyHeading(section.querySelector(".cnt-row-head"), language);
const body = section.querySelector(".cnt-row-body");
if (!heading || !body) return "";
let content;
if (body.querySelector(".sampledata, .data-sample, .in-out-item")) {
const parts = [];
const samples = [...body.querySelectorAll(".data-sample")];
const groups = samples.length ? samples : [...body.querySelectorAll(".in-out")];
function Visit(node) {
if (node.nodeType !== Node.ELEMENT_NODE ||
node.classList.contains(language === "en" ? "lang_cn" : "lang_en")) return;
if (node.classList.contains("in-out-item")) {
const sample = node.querySelector(".sampledata");
if (!sample || sample.textContent === "") return;
let label = GetProblemCopyHeading(node.querySelector(".title"), language);
const group = node.closest(samples.length ? ".data-sample" : ".in-out");
if (!/#\s*\d+/.test(label)) label += " #" + (Math.max(0, groups.indexOf(group)) + 1);
const code = sample.textContent;
parts.push("### " + label + "\n\n" + GetMarkdownCodeBlock(code));
return;
}
if (node.classList.contains("title")) {
const label = GetProblemCopyHeading(node, language);
if (label) parts.push("### " + label);
return;
}
// Legacy pages wrap the complete sample section in .content.
if (node.classList.contains("content") && !node.querySelector(".sampledata, .data-sample, .in-out-item")) {
const text = GetMDText(node).trim();
if (text) parts.push(text);
return;
}
for (const child of node.children) Visit(child);
}
for (const child of body.children) Visit(child);
content = parts.join("\n\n");
} else {
const translated = body.querySelector(language === "en" ? ".content.lang_en" : ".content.lang_cn");
const fallback = body.querySelector(".content.lang_cn") || body.querySelector(".content");
content = GetMDText(translated || fallback || body).trim();
}
return content ? "## " + heading + "\n\n" + content : "";
}
function GetProblemStatementMarkdown(root, language = "zh") {
const title = root.querySelector(".xmoj-problem-head h2") ||
root.querySelector(language === "en" ? "h2.lang_en" : "h2.lang_cn") || root.querySelector("h2");
const name = GetProblemCopyHeading(title, language)
.replace(/^(?:(?:问题|Problem)\s+[^::]+|\d+)\s*[::]\s*/i, "");
const body = root.querySelector(".xmoj-problem-body") || root;
const sections = [...body.querySelectorAll(".cnt-row")]
.map(section => GetProblemSectionMarkdown(section, language)).filter(Boolean);
return (name ? ["# " + name, ...sections] : sections).join("\n\n");
}
function InitializeProblemMarkdownCopy(root, sourceRoot = root, addCopy = null) {
if (!addCopy) addCopy = (parent, name, readText) => {
if (parent.querySelector('[data-xmoj-script="' + name + '"]')) return;
const button = document.createElement("button");
button.type = "button";
button.className = "btn btn-sm btn-outline-secondary copy-btn";
button.style.marginLeft = "10px";
button.setAttribute("data-xmoj-script", name);
button.textContent = "复制";
button.addEventListener("click", () => {
GM_setClipboard(readText());
button.textContent = "复制成功";
setTimeout(() => { button.textContent = "复制"; }, 1000);
});
parent.appendChild(button);
};
const titles = [...root.querySelectorAll(".xmoj-problem-head h2, h2.lang_cn, h2.lang_en")];
if (!titles.length && root.querySelector("h2")) titles.push(root.querySelector("h2"));
for (const title of titles) {
addCopy(title, "copy-problem", () => GetProblemStatementMarkdown(sourceRoot, GetProblemCopyLanguage(root)));
}
const body = root.querySelector(".xmoj-problem-body") || root;
const sourceBody = sourceRoot.querySelector(".xmoj-problem-body") || sourceRoot;
const sourceSections = [...sourceBody.querySelectorAll(".cnt-row")];
for (const [index, section] of [...body.querySelectorAll(".cnt-row")].entries()) {
const heading = section.querySelector(".cnt-row-head");
if (heading && section.querySelector(".cnt-row-body")) {
addCopy(heading, "copy-section", () =>
GetProblemSectionMarkdown(sourceSections[index] || section, GetProblemCopyLanguage(root)));
}
}
}
function InitializeImageEnlarger() {
// Image Enlargement Feature
if (UtilityEnabled("ImageEnlarger")) {
try {
// Add CSS styles for the enlarger
let EnlargerStyle = document.createElement("style");
EnlargerStyle.textContent = `
.xmoj-image-preview {
cursor: pointer;
}
.xmoj-image-preview:hover {
opacity: 0.8;
transition: opacity 0.2s ease;
}
.xmoj-image-modal {
display: none;
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
}
.xmoj-image-modal.show {
display: flex;
flex-direction: column;
}
.xmoj-image-modal-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.xmoj-image-modal-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.xmoj-image-modal-toolbar {
display: flex;
justify-content: center;
gap: 10px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.5);
flex-wrap: wrap;
}
.xmoj-image-modal-toolbar button {
padding: 8px 16px;
background-color: #0d6efd;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s ease;
}
.xmoj-image-modal-toolbar button:hover {
background-color: #0b5ed7;
}
.xmoj-image-modal-toolbar button:active {
background-color: #0a58ca;
}
.xmoj-image-modal-close {
position: absolute;
top: 20px;
right: 30px;
color: white;
background: none;
border: none;
padding: 0;
line-height: 1;
font-size: 40px;
font-weight: bold;
cursor: pointer;
transition: color 0.2s ease;
z-index: 1;
}
.xmoj-image-modal-close:hover {
color: #ccc;
}
.xmoj-image-modal-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.5);
color: white;
border: none;
padding: 20px 12px;
cursor: pointer;
font-size: 28px;
transition: background-color 0.2s ease;
user-select: none;
-webkit-user-select: none;
}
.xmoj-image-modal-nav:hover {
background: rgba(0, 0, 0, 0.8);
}
.xmoj-image-modal-nav:disabled {
opacity: 0.3;
cursor: default;
}
.xmoj-image-modal-nav-prev {
left: 0;
border-radius: 0 4px 4px 0;
}
.xmoj-image-modal-nav-next {
right: 0;
border-radius: 4px 0 0 4px;
}
`;
document.head.appendChild(EnlargerStyle);
// Create modal element
let ImageModal = document.createElement("div");
ImageModal.className = "xmoj-image-modal";
ImageModal.id = "xmoj-image-modal";
let CloseButton = document.createElement("button");
CloseButton.className = "xmoj-image-modal-close";
CloseButton.type = "button";
CloseButton.setAttribute("aria-label", "关闭图片");
CloseButton.title = "关闭图片";
CloseButton.innerHTML = "×";
ImageModal.appendChild(CloseButton);
let ModalContent = document.createElement("div");
ModalContent.className = "xmoj-image-modal-content";
let PrevBtn = document.createElement("button");
PrevBtn.className = "xmoj-image-modal-nav xmoj-image-modal-nav-prev";
PrevBtn.type = "button";
PrevBtn.setAttribute("aria-label", "上一张");
PrevBtn.innerHTML = "❮";
ModalContent.appendChild(PrevBtn);
let NextBtn = document.createElement("button");
NextBtn.className = "xmoj-image-modal-nav xmoj-image-modal-nav-next";
NextBtn.type = "button";
NextBtn.setAttribute("aria-label", "下一张");
NextBtn.innerHTML = "❯";
ModalContent.appendChild(NextBtn);
let ModalImage = document.createElement("img");
ModalImage.className = "xmoj-image-modal-image";
ModalContent.appendChild(ModalImage);
ImageModal.appendChild(ModalContent);
let Toolbar = document.createElement("div");
Toolbar.className = "xmoj-image-modal-toolbar";
let ZoomInBtn = document.createElement("button");
ZoomInBtn.innerHTML = "放大 (+)";
ZoomInBtn.type = "button";
Toolbar.appendChild(ZoomInBtn);
let ZoomOutBtn = document.createElement("button");
ZoomOutBtn.innerHTML = "缩小 (-)";
ZoomOutBtn.type = "button";
Toolbar.appendChild(ZoomOutBtn);
let ResetZoomBtn = document.createElement("button");
ResetZoomBtn.innerHTML = "重置大小";
ResetZoomBtn.type = "button";
Toolbar.appendChild(ResetZoomBtn);
let SaveBtn = document.createElement("button");
SaveBtn.innerHTML = "保存图片";
SaveBtn.type = "button";
Toolbar.appendChild(SaveBtn);
ImageModal.appendChild(Toolbar);
document.body.appendChild(ImageModal);
// Zoom level and navigation state
let CurrentZoom = 1;
const ZoomStep = 0.1;
const MinZoom = 0.1;
const MaxZoom = 5;
let ImageList = [];
let CurrentImageIndex = -1;
let PanX = 0;
let PanY = 0;
let IsDragging = false;
let DragStartX = 0;
let DragStartY = 0;
let DragStartPanX = 0;
let DragStartPanY = 0;
let IsTouchPanning = false;
let TouchStartX = 0;
let TouchStartY = 0;
let TouchPanStartPanX = 0;
let TouchPanStartPanY = 0;
// Function to update image transform (zoom + pan)
let UpdateImageSize = () => {
ModalImage.style.transform = `translate(${PanX}px, ${PanY}px) scale(${CurrentZoom})`;
ModalImage.style.transition = IsDragging ? "none" : "transform 0.2s ease";
let CursorStyle = CurrentZoom > 1 ? "grab" : "";
ModalImage.style.cursor = CursorStyle;
ModalContent.style.cursor = CursorStyle;
};
// Function to update prev/next button state
let UpdateNavButtons = () => {
let HasMultiple = ImageList.length > 1;
PrevBtn.style.display = HasMultiple ? "" : "none";
NextBtn.style.display = HasMultiple ? "" : "none";
PrevBtn.disabled = CurrentImageIndex <= 0;
NextBtn.disabled = CurrentImageIndex >= ImageList.length - 1;
};
// Function to navigate to a specific image by index
let NavigateTo = (index) => {
if (index < 0 || index >= ImageList.length) return;
CurrentImageIndex = index;
CurrentZoom = 1;
PanX = 0;
PanY = 0;
ModalImage.src = ImageList[CurrentImageIndex];
UpdateNavButtons();
UpdateImageSize();
};
// Function to open modal
let OpenImageModal = (imgElement) => {
let PreviewImages = [...document.querySelectorAll("img.xmoj-image-preview")];
ImageList = PreviewImages.map(img => img.currentSrc || img.src).filter(src => src);
CurrentImageIndex = PreviewImages.indexOf(imgElement);
if (CurrentImageIndex === -1) {
ImageList = [(imgElement.currentSrc || imgElement.src)];
CurrentImageIndex = 0;
}
CurrentZoom = 1;
PanX = 0;
PanY = 0;
ModalImage.src = ImageList[CurrentImageIndex];
ImageModal.classList.add("show");
UpdateNavButtons();
UpdateImageSize();
};
// Function to close modal
let CloseImageModal = () => {
ImageModal.classList.remove("show");
};
// Close button click
CloseButton.addEventListener("click", CloseImageModal);
// Close when clicking outside the image
ImageModal.addEventListener("click", (e) => {
if (e.target === ImageModal || e.target === ModalContent) {
CloseImageModal();
}
});
// Keyboard shortcuts
document.addEventListener("keydown", (e) => {
if (ImageModal.classList.contains("show")) {
if (e.key === "Escape") {
CloseImageModal();
} else if (e.key === "+") {
ZoomInBtn.click();
} else if (e.key === "-") {
ZoomOutBtn.click();
} else if (e.key === "ArrowLeft") {
NavigateTo(CurrentImageIndex - 1);
} else if (e.key === "ArrowRight") {
NavigateTo(CurrentImageIndex + 1);
}
}
});
// Touch events: pan when zoomed, swipe to navigate when at zoom level 1
ModalContent.addEventListener("touchstart", (e) => {
if (e.touches.length !== 1) return;
TouchStartX = e.touches[0].clientX;
TouchStartY = e.touches[0].clientY;
if (CurrentZoom > 1) {
IsTouchPanning = true;
TouchPanStartPanX = PanX;
TouchPanStartPanY = PanY;
} else {
IsTouchPanning = false;
}
}, { passive: true });
ModalContent.addEventListener("touchmove", (e) => {
if (!IsTouchPanning || e.touches.length !== 1) return;
PanX = TouchPanStartPanX + (e.touches[0].clientX - TouchStartX);
PanY = TouchPanStartPanY + (e.touches[0].clientY - TouchStartY);
UpdateImageSize();
e.preventDefault();
}, { passive: false });
ModalContent.addEventListener("touchend", (e) => {
if (IsTouchPanning) {
IsTouchPanning = false;
return;
}
let TouchEndX = e.changedTouches[0].clientX;
let TouchEndY = e.changedTouches[0].clientY;
let DeltaX = TouchEndX - TouchStartX;
let DeltaY = TouchEndY - TouchStartY;
const SwipeThreshold = 50;
if (Math.abs(DeltaX) > SwipeThreshold && Math.abs(DeltaX) > Math.abs(DeltaY)) {
if (DeltaX < 0) {
NavigateTo(CurrentImageIndex + 1);
} else {
NavigateTo(CurrentImageIndex - 1);
}
}
}, { passive: true });
// Mouse drag to pan when zoomed
ModalContent.addEventListener("mousedown", (e) => {
if (CurrentZoom <= 1) return;
if (e.target.tagName.toUpperCase() === "BUTTON") return;
IsDragging = true;
DragStartX = e.clientX;
DragStartY = e.clientY;
DragStartPanX = PanX;
DragStartPanY = PanY;
ModalImage.style.cursor = "grabbing";
ModalContent.style.cursor = "grabbing";
e.preventDefault();
});
document.addEventListener("mousemove", (e) => {
if (!IsDragging) return;
PanX = DragStartPanX + (e.clientX - DragStartX);
PanY = DragStartPanY + (e.clientY - DragStartY);
UpdateImageSize();
});
document.addEventListener("mouseup", () => {
if (IsDragging) {
IsDragging = false;
let CursorStyle = CurrentZoom > 1 ? "grab" : "";
ModalImage.style.cursor = CursorStyle;
ModalContent.style.cursor = CursorStyle;
}
});
// Mouse wheel to zoom in/out
ModalContent.addEventListener("wheel", (e) => {
e.preventDefault();
let ZoomDelta = e.deltaY > 0 ? -ZoomStep : ZoomStep;
CurrentZoom = Math.max(MinZoom, Math.min(MaxZoom, CurrentZoom + ZoomDelta));
UpdateImageSize();
}, { passive: false });
// Navigation button clicks
PrevBtn.addEventListener("click", (e) => {
e.stopPropagation();
NavigateTo(CurrentImageIndex - 1);
});
NextBtn.addEventListener("click", (e) => {
e.stopPropagation();
NavigateTo(CurrentImageIndex + 1);
});
// Zoom controls
ZoomInBtn.addEventListener("click", () => {
CurrentZoom = Math.min(CurrentZoom + ZoomStep, MaxZoom);
UpdateImageSize();
});
ZoomOutBtn.addEventListener("click", () => {
CurrentZoom = Math.max(CurrentZoom - ZoomStep, MinZoom);
UpdateImageSize();
});
ResetZoomBtn.addEventListener("click", () => {
CurrentZoom = 1;
PanX = 0;
PanY = 0;
UpdateImageSize();
});
// Save/Download image: fetch via GM_xmlhttpRequest to bypass CORS, then use blob URL for reliable download
SaveBtn.addEventListener("click", () => {
let src = ModalImage.src;
let urlPath = src.split("?")[0];
let filename = urlPath.split("/").pop() || "image.png";
GM_xmlhttpRequest({
method: "GET",
url: src,
responseType: "blob",
onload: (resp) => {
let BlobUrl = URL.createObjectURL(resp.response);
let Link = document.createElement("a");
Link.href = BlobUrl;
Link.download = filename;
document.body.appendChild(Link);
Link.click();
document.body.removeChild(Link);
setTimeout(() => URL.revokeObjectURL(BlobUrl), 100);
},
onerror: () => {
let Link = document.createElement("a");
Link.href = src;
Link.download = filename;
Link.target = "_blank";
document.body.appendChild(Link);
Link.click();
document.body.removeChild(Link);
}
});
});
// Apply to all images on the page
let ApplyEnlargerToImage = (img) => {
const effectiveSrc = img.currentSrc || img.src;
if (!img.classList.contains("xmoj-image-preview") &&
!img.closest(".xmoj-image-modal") &&
effectiveSrc &&
!effectiveSrc.includes("gravatar") &&
!effectiveSrc.includes("cravatar")) {
img.classList.add("xmoj-image-preview");
if (!img.title) {
img.title = "点击放大";
}
img.addEventListener("click", (e) => {
e.stopPropagation();
OpenImageModal(img);
});
}
};
let ApplyEnlargerToImages = () => {
document.querySelectorAll("img").forEach(ApplyEnlargerToImage);
};
// Apply to existing images
ApplyEnlargerToImages();
// Apply to dynamically added images
let Observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.tagName === "IMG") {
ApplyEnlargerToImage(node);
} else {
node.querySelectorAll("img").forEach(ApplyEnlargerToImage);
}
});
});
});
Observer.observe(document.body, {
childList: true,
subtree: true
});
} catch (e) {
console.error(e);
if (UtilityEnabled("DebugMode")) {
SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
}
}