diff --git a/Update.json b/Update.json index 4f2f2901..3cc9edb0 100644 --- a/Update.json +++ b/Update.json @@ -3638,6 +3638,17 @@ } ], "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." + }, + "3.5.3": { + "UpdateDate": 1781427038989, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 992, + "Description": "fix: eliminate dark-mode flash in MonochromeUI/NewBootstrap" + } + ], + "Notes": "修复了 MonochromeUI / NewBootstrap 新界面在深色模式下加载时出现的白色闪烁(FOUC/CLS)问题。" } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 8b0b2ea0..9ab9ad5c 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.5.2 +// @version 3.5.3 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen @@ -14,6 +14,8 @@ // @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 @@ -21,6 +23,7 @@ // @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 @@ -31,6 +34,7 @@ // @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== @@ -40,727 +44,539 @@ * You should have received a copy of the GNU General Public License along with this program. If not, see . */ -const CaptchaSiteKey = "0x4AAAAAAALBT58IhyDViNmv"; -const MonacoCDN = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.53.0/min/vs"; -const AdminUserList = ["zhuchenrui2", "shanwenxiao", "chenlangning", "admin"]; +const MonochromeSkinCSS = ` + /* Fonts loaded via to avoid layout shift */ -// Pre-declared so that closures defined before the async init block can reference them -let CurrentUsername; -let initTheme; -let SearchParams; + :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; + } -let escapeHTML = (str) => { - return str.replace(/[&<>"']/g, function (match) { - const escape = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return escape[match]; - }); -}; + [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; + } -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."); - } - } -}; + * { + border-radius: 0 !important; + box-shadow: none !important; + } -function compareVersions(currVer, remoteVer) { - const currParts = currVer.split('.').map(Number); - const remoteParts = remoteVer.split('.').map(Number); + body { + font-family: var(--mono-font-body) !important; + color: var(--mono-black) !important; + background-color: var(--mono-white) !important; + } - 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 -} + h1, h2, h3, h4, h5, h6 { + font-family: var(--mono-font-heading) !important; + font-weight: 700 !important; + } -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 = (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; - let Temp = ParsedDocument.querySelector("#statics > tbody").children; - 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."); - } - } -}; -async function ensureMonaco() { - if (typeof monaco !== 'undefined') return; - 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."); - } + code, pre, .CodeMirror, kbd, samp { + font-family: var(--mono-font-mono) !important; } - 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."); - } + 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; } - 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."); - } + td, th { + border-color: var(--mono-gray-300) !important; + text-align: center !important; } - 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."); - } - } + .table-striped > tbody > tr:nth-of-type(odd) > * { + background-color: var(--mono-gray-100) !important; } - 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."); - } + table { + margin-top: 16px !important; } - } - } 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."); + /* 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; } - } - } - } 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); + .list-group-item-success { + border-left: 4px solid #52c41a !important; } - } 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."); + .list-group-item-warning { + border-left: 4px solid #ffa900 !important; } - } - 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."); - } - } + .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; } - } 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."); + + .test-case:hover { + border: 2px solid var(--mono-black) !important; } - } - } - }; - 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."); - } - } + .software_list { + width: unset !important; } - } 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."); + .software_item { + margin: 5px 10px !important; + background-color: var(--mono-gray-100) !important; + border: 1px solid var(--mono-gray-300) !important; } - } - 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."); + .software_item img { + width: 50px !important; + height: 50px !important; + object-fit: contain !important; } - } - 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."); + .item-txt { + color: var(--mono-black) !important; } - } }, - 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."); + .cnt-row { + justify-content: inherit; + align-items: stretch; + width: 100% !important; + padding: 1rem 0; } - } }, - 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."); + .cnt-row-head { + padding: 0.8em 1em; + background-color: var(--mono-black); + color: var(--mono-white); + width: 100%; + font-family: var(--mono-font-heading); } - } }, - _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."); - } + .cnt-row-head * { + color: var(--mono-white) !important; + } + .cnt-row-body { + padding: 1em; + border: 2px solid var(--mono-black); + border-top: none; } - 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."); - } + /* Scrollbar */ + ::-webkit-scrollbar { + width: 8px; + height: 8px; } - } 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."); - } + ::-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; } - 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."); + + /* Problem switcher responsive */ + @media (max-width: 768px) { + .problem-switcher-container { + display: none !important; } } - }); + .refreshList { + cursor: pointer; + } + + /* 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; + } + .refreshList { + cursor: pointer; + color: #6c757d; + text-decoration: none; + }`; + +// 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; +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. +(() => { + 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; } - window._xmoj_temp_error_editors = []; + _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; }`; + let skinStyle = document.createElement("style"); + skinStyle.textContent = skinCSS; + head.appendChild(skinStyle); + + // Hide the page until old stylesheets are evicted and our CSS is in place. + // Revealed by the IIFE right after its DOMContentLoaded wait (more reliable + // than a DOMContentLoaded listener here due to sandbox context differences). + _foucStyle = document.createElement("style"); + _foucStyle.textContent = "html { 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 }); } 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."); - } + console.error("[XMOJ-Script] early init error:", e); } +})(); + +const CaptchaSiteKey = "0x4AAAAAAALBT58IhyDViNmv"; +const MonacoCDN = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.53.0/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 { - 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."); - } - } + 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); @@ -769,130 +585,164 @@ function _xmoj_disposeErrorMessageEditors() { } } } +let SmartAlert = (Message) => { + if (localStorage.getItem("UserScript-Alert") !== Message) { + alert(Message); + } + localStorage.setItem("UserScript-Alert", Message); +} /** - * 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. + * 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 GetUsernameHTML = async (Element, Username, Simple = false, Href = "https://www.xmoj.tech/userinfo.php?user=") => { +let GetRelativeTime = (Input) => { 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; + 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) + "年前"; } - let HTMLData = ""; - if (!Simple) { - HTMLData += ``; + 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."); } - 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"; + } +}; + +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 } - HTMLData += `\";">`; - if (!Simple) { - if (AdminUserList.includes(Username)) { - HTMLData += `脚本管理员`; - } - let BadgeInfo = await GetUserBadge(Username); - if (BadgeInfo.Content != "") { - HTMLData += `${BadgeInfo.Content}`; - } + } + 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 (document.getElementById(ID) !== null) { - document.getElementById(ID).innerHTML = HTMLData; - document.getElementById(ID).getElementsByTagName("a")[0].appendChild(document.createTextNode(Username)); + 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); - 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) => { +let GetUserInfo = async (Username) => { 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."); + 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") + } } - } -} -/** - * 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]); + 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 = (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; + let Temp = ParsedDocument.querySelector("#statics > tbody").children; + 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."); } } -} +}; /** - * 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. + * 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 SizeToStringSize = (Memory) => { +let GetUserBadge = async (Username) => { 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"; + 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 { - return Memory; + 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); @@ -901,43 +751,175 @@ let SizeToStringSize = (Memory) => { } } }; -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"; +async function ensureMonaco() { + if (typeof monaco !== 'undefined') return; + 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."); } - } else { - return Memory; } - } catch (e) { + } + 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."); } } -}; -/** - * 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) => { + // after creation, ensure Monaco layout matches the computed size and listen for viewport changes try { - if (UtilityEnabled("AddUnits")) { - if (Time < 1000) { - return Time + "ms"; - } else if (Time < 1000 * 60) { - return (Time / 1000).toFixed(2) + "s"; + 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."); + } } - } else { - return Time; } } catch (e) { console.error(e); @@ -945,16 +927,10 @@ let TimeToStringTime = (Time) => { 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"; + if (options.restoreOnLoad !== false) { + const saved = localStorage.getItem(key); + if (saved !== null && saved !== 'null') editor.setValue(saved); } } catch (e) { console.error(e); @@ -962,1382 +938,1459 @@ let TidyTable = (Table) => { 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) { - const defaultOffItems = ["DebugMode", "SuperDebug", "ReplaceXM"]; - localStorage.setItem("UserScript-Setting-" + Name, defaultOffItems.includes(Name) ? "false" : "true"); - } - return localStorage.getItem("UserScript-Setting-" + Name) == "true"; - } catch (e) { + 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."); } - } -}; -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) { + } }; + 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); - } - } -}; -let getCredential = async () => { - if ('credentials' in navigator && window.PasswordCredential) { - try { - return await navigator.credentials.get({password: true, mediation: 'optional'}); - } catch (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); - } - } - return null; -}; -let clearCredential = async () => { - if ('credentials' in navigator && window.PasswordCredential) { - try { - await navigator.credentials.preventSilentAccess(); - } catch (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); - } - } -}; -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 (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 (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); + } }, + _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); } - 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."); } } - }); - } 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); + 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."); + } } } - if (CallBack) CallBack(Response); - }); -}; + }; + return adapter; +} -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; +(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."); + } } } - if (themeChanged) initTheme(); + } 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."); + } } - SyncSettingsToCloud(); - } - }); -}; - -unsafeWindow.GetContestProblemList = async function(RefreshList) { - try { - const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid")); - const res = await contestReq.text(); - if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) { - const parser = new DOMParser(); - const dom = parser.parseFromString(res, "text/html"); - const rows = (dom.querySelector("#problemset > tbody")).rows; - let problemList = []; - for (let i = 0; i < rows.length; i++) { - problemList.push({ - "title": rows[i].children[2].innerText, - "url": rows[i].children[2].children[0].href - }); + 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."); + } } - localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList)); - if (RefreshList) location.reload(); - } - } catch (e) { - console.error(e); - } -} - -// 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; + container.parentNode.replaceChild(div, container); + container = div; + } else if (typeof containerOrTextArea === 'string') { + container = document.querySelector(containerOrTextArea) || document.getElementById(containerOrTextArea); } - - let Session = GetPHPSESSID(); - if (Session === "") { + 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")) { - console.log("WebSocket: PHPSESSID not available, skipping connection"); + 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; - } - - 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 = () => { + 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")) { - console.log("WebSocket: Connected successfully"); + 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."); } - NotificationSocketReconnectAttempts = 0; - NotificationSocketReconnectDelay = 1000; + } + 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; } - // Start ping keepalive - if (NotificationSocketPingInterval) { - clearInterval(NotificationSocketPingInterval); - } - NotificationSocketPingInterval = setInterval(() => { - if (NotificationSocket && NotificationSocket.readyState === WebSocket.OPEN) { - NotificationSocket.send(JSON.stringify({ type: 'ping' })); + 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")) { - console.log("WebSocket: Sent ping"); + 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."); } - } 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) { + } 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 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"); - } + 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("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 = ""; + 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 (UtilityEnabled("BBSPopup")) { - RequestAPI("GetBBSMentionList", {}, (Response) => { - if (Response.Success) { - let MentionList = Response.Data.MentionList; - for (let i = 0; i < MentionList.length; i++) { - CreateAndShowBBSMentionToast(MentionList[i]); + 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."); } } - }); - } - 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]); + 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."); + } } } - -GM_registerMenuCommand("清除缓存", () => { - let Temp = []; - for (let i = 0; i < localStorage.length; i++) { - if (localStorage.key(i).startsWith("UserScript-User-")) { - Temp.push(localStorage.key(i)); +/** + * 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."); } } - for (let i = 0; i < Temp.length; i++) { - localStorage.removeItem(Temp[i]); - } - location.reload(); -}); -GM_registerMenuCommand("重置数据", () => { - if (confirm("确定要重置数据吗?")) { - localStorage.clear(); - location.reload(); +}; +/** + * 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."); + } } -}); - -// Wrapped in an async IIFE so that `await` is valid in Violentmonkey, -// which executes userscripts as classic scripts (not ES modules). -(async () => { -//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) { - if (!logined) { - location.href = "https://www.xmoj.tech/loginpage.php"; +/** + * 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."); + } } - 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) => { - document.querySelector("html").setAttribute("data-bs-theme", theme); - localStorage.setItem("UserScript-Setting-DarkMode", theme === "dark" ? "true" : "false"); +/** + * 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."); + } + } }; -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); +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."); + } } }; -initTheme(); -PeriodicCloudSync(); -setInterval(PeriodicCloudSync, 60 * 60 * 1000); - - -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."); - } +let UtilityEnabled = (Name) => { + try { + if (localStorage.getItem("UserScript-Setting-" + Name) == null) { + const defaultOffItems = ["DebugMode", "SuperDebug", "ReplaceXM"]; + localStorage.setItem("UserScript-Setting-" + Name, defaultOffItems.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."); } } - - init() { +}; +let storeCredential = async (username, password) => { + if ('credentials' in navigator && window.PasswordCredential) { try { - this.applyStyles(); - this.createOverlay(); - this.createSpacer(); - window.addEventListener('resize', () => this.updateBlurOverlay()); - this.updateBlurOverlay(); + const credential = new PasswordCredential({id: username, password: password}); + await navigator.credentials.store(credential); } 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() { +}; +let getCredential = async () => { + if ('credentials' in navigator && window.PasswordCredential) { 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' - }); - } + return await navigator.credentials.get({password: true, mediation: 'optional'}); } 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() { + return null; +}; +let clearCredential = async () => { + if ('credentials' in navigator && window.PasswordCredential) { 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); - } + await navigator.credentials.preventSilentAccess(); } 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."); +}; +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]; } } - } - - 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); + 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."); - } + }); + } 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 replaceMarkdownImages(text, string) { - return text.replace(/!\[.*?\]\(.*?\)/g, string); -} - -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']); - - function traverse(node) { - if (node.nodeType === Node.TEXT_NODE) { - result += node.textContent; - return; - } - - if (node.nodeType !== Node.ELEMENT_NODE) { - return; +}; +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); } - - const tag = node.nodeName.toUpperCase(); - - // Preserve line breaks for
- if (tag === 'BR') { - result += '\n'; - return; + } + RequestAPI("SetUserSettings", {"Settings": JSON.stringify(Settings)}, (Response) => { + if (UtilityEnabled("DebugMode")) { + if (Response.Success) { + console.log("设置已同步到云端"); + } else { + console.error("设置云端同步失败:", Response.Message); + } } + if (CallBack) CallBack(Response); + }); +}; - // 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 +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; + } } - result += `![](${resolvedSrc})`; + if (themeChanged) initTheme(); } - return; - } - - const isBlock = blockTags.has(tag); - const isCell = cellTags.has(tag); - - if (isBlock && !result.endsWith('\n')) { - result += '\n'; - } - - // Keep table cells visually separated when copied as plain text. - if (isCell && result.length > 0 && !result.endsWith('\n') && !result.endsWith('\t') && !result.endsWith(' ')) { - result += '\t'; + SyncSettingsToCloud(); } + }); +}; - for (let child of node.childNodes) { - traverse(child); +unsafeWindow.GetContestProblemList = async function(RefreshList) { + try { + const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid")); + const res = await contestReq.text(); + if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) { + const parser = new DOMParser(); + const dom = parser.parseFromString(res, "text/html"); + const rows = (dom.querySelector("#problemset > tbody")).rows; + let problemList = []; + for (let i = 0; i < rows.length; i++) { + problemList.push({ + "title": rows[i].children[2].innerText, + "url": rows[i].children[2].children[0].href + }); + } + localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList)); + if (RefreshList) location.reload(); } + } catch (e) { + console.error(e); + } +} - if (isCell && !result.endsWith('\n') && !result.endsWith('\t')) { - result += '\t'; - } +// WebSocket Notification System +let NotificationSocket = null; +let NotificationSocketReconnectAttempts = 0; +let NotificationSocketReconnectDelay = 1000; +let NotificationSocketPingInterval = null; +let NotificationSocketReconnectTimer = null; - if (isBlock && !result.endsWith('\n')) { - result += '\n'; +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; } } - - traverse(element); - return result; + return Session; } -async function main() { +function ConnectNotificationSocket() { try { - if (location.href.startsWith('http://')) { - //use https - location.href = location.href.replace('http://', 'https://'); + // Clear any pending reconnection timer to prevent duplicate connections + if (NotificationSocketReconnectTimer) { + clearTimeout(NotificationSocketReconnectTimer); + NotificationSocketReconnectTimer = null; } - 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; + + let Session = GetPHPSESSID(); + if (Session === "") { + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: PHPSESSID not available, skipping connection"); } - 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"; - } + return; + } - 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 wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-script.uk") + "/ws/notifications?SessionID=" + Session; - let Discussion = null; - if (UtilityEnabled("Discussion")) { - Discussion = document.createElement("li"); - document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion); - Discussion.innerHTML = "讨论"; - } - if (UtilityEnabled("Translate")) { - 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, ""); - } - 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("DebugMode")) { + console.log("WebSocket: Connecting to", wsUrl); + } - if (UtilityEnabled("NewBootstrap")) { - 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("DarkMode")) { - document.querySelector("html").setAttribute("data-bs-theme", "dark"); - } else { - document.querySelector("html").setAttribute("data-bs-theme", "light"); - } - 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); - let earlyStyle = document.createElement("style"); - earlyStyle.textContent = ` - :root { - --mono-black: #000; --mono-white: #fff; - --mono-gray-100: #f5f5f5; --mono-gray-300: #d4d4d4; - --mono-font-heading: 'Playfair Display', Georgia, serif; - --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; - } - [data-bs-theme='dark'] { - --mono-black: #e5e5e5; --mono-white: #1a1a1a; - --mono-gray-100: #222; --mono-gray-300: #404040; - } - * { border-radius: 0 !important; box-shadow: none !important; } - body { font-family: var(--mono-font-body) !important; background-color: var(--mono-white) !important; color: var(--mono-black) !important; } - h1,h2,h3,h4,h5,h6 { font-family: var(--mono-font-heading) !important; } - .table thead th { background-color: var(--mono-black) !important; color: var(--mono-white) !important; } - .card { border: 2px solid var(--mono-black) !important; } - .card-header { background-color: var(--mono-black) !important; color: var(--mono-white) !important; } - `; - document.head.appendChild(earlyStyle); - } - 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' - }, { - type: 'link', - href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css', - rel: 'stylesheet' - }, { - type: 'script', - src: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.js', - isModule: true - }]; - 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); - }); - }); + NotificationSocket = new WebSocket(wsUrl); - await Promise.all(promises); - }; - if (location.pathname == "/submitpage.php") { - await loadResources(); - } else { - loadResources(); - } - document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary"; - document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0"; - document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = `${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ`; - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown"; - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle"; - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove(); - Temp = 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"); + 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"); } - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown"); - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle"); - } - if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) { - document.getElementsByTagName("marquee")[0].remove(); + } else { + clearInterval(NotificationSocketPingInterval); } - let Style = document.createElement("style"); - document.body.appendChild(Style); - if (UtilityEnabled("MonochromeUI")) { - Style.innerHTML = ` - /* Fonts loaded via to avoid layout shift */ + }, 30000); + }; - :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; - } + NotificationSocket.onmessage = (event) => { + HandleNotificationMessage(event); + }; - [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; - } + NotificationSocket.onerror = (error) => { + if (UtilityEnabled("DebugMode")) { + console.error("WebSocket: Error", error); + } + }; - * { - border-radius: 0 !important; - box-shadow: none !important; - } + 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(); + } +} - body { - font-family: var(--mono-font-body) !important; - color: var(--mono-black) !important; - background-color: var(--mono-white) !important; - } +function ReconnectNotificationSocket() { + const delay = Math.min(NotificationSocketReconnectDelay * Math.pow(2, NotificationSocketReconnectAttempts), 30000); + NotificationSocketReconnectAttempts++; - h1, h2, h3, h4, h5, h6 { - font-family: var(--mono-font-heading) !important; - font-weight: 700 !important; + 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]); } + } + }); + } +} - code, pre, .CodeMirror, kbd, samp { - font-family: var(--mono-font-mono) !important; - } +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(); + } +}); - 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; - } +// Wrapped in an async IIFE so that `await` is valid in Violentmonkey, +// which executes userscripts as classic scripts (not ES modules). +(async () => { +if (document.readyState === "loading") { + await new Promise(r => document.addEventListener("DOMContentLoaded", r, { once: true })); +} +// Reveal the page now that DOMContentLoaded has fired. Remove any old Bootstrap +// stylesheets the preload scanner fetched (un-applies them from the CSSOM), then +// remove the FOUC hide so the user sees the correct final state immediately. +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(); + } + _foucStyle.remove(); _foucStyle = null; +} +//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; +} - blockquote { - border-left: 4px solid var(--mono-black) !important; - padding: 0.5em 1em; - } +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) { + 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; - /* 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; - } +const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); +const applyTheme = (theme) => { + 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); - /* 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; - } +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."); + } + } + } - /* 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; - } + 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."); + } + } + } - /* 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; - } + createOverlay() { + try { + if (!document.getElementById('blur-overlay')) { + let overlay = document.createElement('div'); + overlay.id = 'blur-overlay'; + document.body.appendChild(overlay); - /* 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; - } - 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; + let style = document.createElement('style'); + style.textContent = UtilityEnabled("MonochromeUI") ? ` + #blur-overlay { + display: none !important; } - table { - margin-top: 16px !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."); + } + } + } - /* 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; - } + 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."); + } + } + } - /* 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; + 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."); + } + } + } +} - /* 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; - } +function replaceMarkdownImages(text, string) { + return text.replace(/!\[.*?\]\(.*?\)/g, string); +} - /* 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; - } +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']); - /* 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; - } + function traverse(node) { + if (node.nodeType === Node.TEXT_NODE) { + result += node.textContent; + return; + } - .test-case:hover { - border: 2px solid var(--mono-black) !important; - } + if (node.nodeType !== Node.ELEMENT_NODE) { + return; + } - .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; + const tag = node.nodeName.toUpperCase(); + + // Preserve line breaks for
+ if (tag === 'BR') { + result += '\n'; + 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 } + result += `![](${resolvedSrc})`; + } + return; + } - /* Scrollbar */ - ::-webkit-scrollbar { - width: 8px; - height: 8px; - } - ::-webkit-scrollbar-track { - background: var(--mono-white); - } - ::-webkit-scrollbar-thumb { - background: var(--mono-black); - } + const isBlock = blockTags.has(tag); + const isCell = cellTags.has(tag); - /* 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; - } + if (isBlock && !result.endsWith('\n')) { + result += '\n'; + } - /* Problem switcher responsive */ - @media (max-width: 768px) { - .problem-switcher-container { - display: none !important; - } - } - .refreshList { - cursor: pointer; - } + // Keep table cells visually separated when copied as plain text. + if (isCell && result.length > 0 && !result.endsWith('\n') && !result.endsWith('\t') && !result.endsWith(' ')) { + result += '\t'; + } - /* Contain images */ - img { - max-width: 100% !important; - height: auto !important; - } + for (let child of node.childNodes) { + traverse(child); + } - /* Hide blur overlay */ - #blur-overlay { display: none !important; }`; - } else { - Style.innerHTML = ` - 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; + if (isCell && !result.endsWith('\n') && !result.endsWith('\t')) { + result += '\t'; + } + + if (isBlock && !result.endsWith('\n')) { + result += '\n'; + } + } + + traverse(element); + return result; +} + +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"; } - .cnt-row { - justify-content: inherit; - align-items: stretch; - width: 100% !important; - padding: 1rem 0; + + 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"; } - .cnt-row-head { - padding: 0.8em 1em; - background-color: var(--bs-secondary-bg); - border-radius: 0.3rem 0.3rem 0 0; - width: 100%; + + let Discussion = null; + if (UtilityEnabled("Discussion")) { + Discussion = document.createElement("li"); + document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion); + Discussion.innerHTML = "讨论"; } - .cnt-row-body { - padding: 1em; - border: 1px solid var(--bs-secondary-bg); - border-top: none; - border-radius: 0 0 0.3rem 0.3rem; + if (UtilityEnabled("Translate")) { + document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a").innerText = "题库"; } - .refreshList { - cursor: pointer; - color: #6c757d; - text-decoration: none; - }`; + //send analytics + RequestAPI("SendData", {}); + if (UtilityEnabled("ReplaceLinks")) { + document.body.innerHTML = String(document.body.innerHTML).replaceAll(/\[([^<]*)<\/a>\]/g, ""); } - if (UtilityEnabled("AddAnimation")) { - Style.innerHTML += `.status, .test-case { - transition: ${UtilityEnabled("MonochromeUI") ? "100ms ease" : "0.5s"} !important; - }`; + 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("AddColorText")) { - Style.innerHTML += `.red { - color: red !important; + + 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(); + } + document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary"; + document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0"; + document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = `${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ`; + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown"; + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle"; + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove(); + Temp = 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"); + } + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown"); + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle"); } - .green { - color: green !important; + if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) { + document.getElementsByTagName("marquee")[0].remove(); } - .blue { - color: blue !important; - }`; + 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")) { diff --git a/package.json b/package.json index b1f5181a..d94bfece 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.5.2", + "version": "3.5.3", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": {