Untitled
public
Aug 27, 2024
Never
140
1 // ==UserScript== 2 // @name KG_Generate_Report 3 // @namespace klavogonki 4 // @version 0.4.0 5 // @description Добавляет кнопки для генерации отчетов 6 // @include http*://klavogonki.ru/u/* 7 // @author ASplayer9119 8 // ==/UserScript== 9 10 function main () { 11 const EK_IDS = [128258,128060,127823,128396,128376,128439,127971,128428,128242,128397,128234,128384,128252,128427,128340,128443,127956,127954,128295,128390,127952,128320,128408,127977,128360,128324,128368,128268,128068,128411,128365,128383,128012,127947,128245,128286,127824,128066,128378,127950,128413,128317,128424,128388,128061,127906,128309,128280,128339,128304,128430,128020,128239,127867,128251,128440,128018,128351,128055,128375,127908,127902,128321,128071,128412,128444,128433,128409,127982,128455,128037,128407,128410,128356,127904,128453,128331,128298,128294,128394,127955,128275,128260,128057,128449,128257,128007,128402,128282,128341,128315,128287,128046,127978,128042,127959,127868,128437,128274,128261,128361,128344,128358,127897,128034,127846,128405,127957,128045,128256,127980,128337,128338,128438,127962,128281,128421,128305,128265,128454,128386,128059,128432,128052,128235,128380,128381,128370,128027,128005,128039,128291,128300,128422,128345,128347,127842,127863,127865,128333,128445,128441,128328,128237,128047,128240,128049,127979,127960,128243,128262,128359,128322,128431,128369,128419,128415,128398,127841,128063,127843,128247,127844,127963,128417,127822,128323,128025,128232,128032,128420,128348,128316,128296,127898,128311,128332,128334,127864,127910,127951,128406,128447,128050,128450,127953,128393,128036,128452,127975,128008,127896,127827,128026,127901,127968,127967,128035,128387,128301,128306,128244,128272,128426,128377,128350,128354,128250,128293,128253,128353,128302,128352,128382,128058,128062,128312,128067,128299,128069,128342,128249,128248,128446,128335,128336,128349,128448,128451,128327,128325,128284,128259,128278,127966,128273,128269,127973,128267,128297,127974,128319,128313,128263,127976,128442,128241,128372,128072,128040,128041,128070,128043,128044,128404,128403,128401,128400,128399,128048,128395,128051,128053,128391,128371,128363,128362,128006,128288,128009,128010,128429,128011,128236,128015,128264,128019,128022,128024,128355,128233,128423,128028,128030,128385,127895,127903,127845,127948,128038,128276,128279,128283,128285,128289,128292,128271,128255,128254,128056,127899,128064,128065,128029,128231,128307,128367,128373,128389,128392,128416,128418,128434,128364,128357,128308,128310,128314,128318,128326,128330,128435,127893,127909,127905,127907,128013,127866,127981,127892,127964,127965,127972,127958,127961,127826,128017,127969,128329,127949,127894,128238,128277,128266,128014,128246,128414,128290,128270,128303,128016,128379,128033,128054,128366,127970,128374,128031,128343,128346,128021,128023,128425,127825]; 12 13 const AEK_IDS = [247579,247580,247581,247582,247583,247584,247585,247586,247587,247588,247589,247590,247591,247592,247593,247594,247595,247596,247597,247598,247599,247600,247601,247602,247603,247604,247605,247606,247607,247608,247609,247610,247611,247612,247613,247614,247615,247616,247617,247618,247619,247620,247621,247622,247623,247624,247625,247626,247627,247628,247629,247630,247631,247632,247633,247634,247635,247636,247637,247638,247639,247640,247641,247642,247643,247644,247645,247649,247650,247651,247652,247653,247654,247655,247656,247657,247658,247659,247660,247661,247663,247664,247665,247666,247667,247668,247669,247670,247671,247672,247673,247674,247675,247676,247677,247678,247679,247680,247681,247682]; 14 15 const SX_IDS = [247814,247818,247819,247820,247821,247822,247823,247824,247825,247826,247827,247828,247829,247830,247831,247832,247833,247834,247835,247836,247837,247838,247839,247840,247841,247842,247843,247844,247845,247846,247847,247848,247849,247850,247851,247852,247853,247854,247855,247856,247857,247858,247859,247860,247861,247862,247863,247864,247865,247866,247867,247868,247869,247870,247871,247872,247873,247874,247875,247876,247877,247878,247879,247880,247881,247882,247883,247884,247885,247886,247887,247888,247889,247890,247891,247892,247893,247894,247895,247896,247897,247898,247899,247900,247901,247902,247903,247904,247905,247906,247907,247908,247909,247910,247911,247912,247913,247914,247915,247916,247917,247918,247919,247920,247921,247922,247923,247924,247925,247926,247927,247928,247929,247930,247931,247932,247933,247934,247935,247936,247937,247938,247939,247940,247941,247942,247943,247944,247945,247946,247947,247948,247949,247950,247951,247952,247953,247954,247955,247956,247957,247958,247959,247960,247961,247962,247963,247964,247965,247966,247967,247968,247969,247970,247971,247972,247973,247974,247975,247976,247977,247978,247979,247980,247981,247982,247983,247984,247985,247986,247987,247990,247988,247989,247991,247992,247993,247994,247995,247996,247997,247998,247999,248000,248001,248002,248003,248004,248005,248006,248007,248008,248009,248010,248011,248012,248013,248014,248015,248016,248017,248018,248019,248020,248021,248022,248023,248024,248025,248026,248027,248028,248029,248030,248031,248032,248033,248034,248035,248036,248037,248038,248039,248040,248041,248042,248043,248045,248048,248049,248050,248051,248052,248053,248054,248055,248056,248057,248058,248059,248060,248061,248062,248063,248064,248065,248066,248067,248068,248069,248070,248071,248072,248073,248074,248076,248075,248077,248078,248079,248080,248081,248082,248083,248084,248085,248086,248087,248088,248089,248090,248091,248092,248093,248094,248095,248096,248097,248098,248099,248100,248101,248102,248103,248104,248105,248106,248107,248108,248109,248110,248111,248112,248113,248114,248115,248116,248117,248118,248119,248120,248121,248122,248123,248124,248125,248126,248127,248128,248129,248130,248131,248132,248133,248134,248135,248136,248137,248138,248139,248140,248141,248142,248143,248144,248145,248146,248147,248148,248149,248150,248151,248152,248153,248154,248155,248156,248157,248158,248159,248160,248161,248162,248163,248164,248165,248166,248167,248168,248169,248170,248171,248172,248173,248174,248175,248176,248177,248178,248179,248180,248181,248182,248183]; 16 17 const FREQ_IDS = [ 18 192, // частотка 1 19 // 931, // частотка 2 20 // 935, // частотка 3 21 // 936, // частотка 4 22 // 937, // частотка 5 23 // 938, // частотка 6 24 // 939, // частотка 7 25 // 940, // частотка 8 26 ]; 27 28 const ADDITIONAL_IDS = [ 29 // сюда добавить id словарей, которые хочется дополнительно видеть в сводке игрока 30 ]; 31 32 const MINI_MARATHON_ID = 6018; 33 const SHORT_TEXTS_ID = 1789; 34 const ONE_HUNDRED_ID = 25856; 35 36 const SUMMARY_IDS = ['normal', 'noerror', 'marathon', MINI_MARATHON_ID, SHORT_TEXTS_ID, ONE_HUNDRED_ID, ...FREQ_IDS, ...ADDITIONAL_IDS]; 37 38 const NORMAL_IMG = 'http://i.imgur.com/IoBKEbu.png'; 39 40 const HRUST = { 41 13571: { 42 key: 1, 43 name: 'Упражнение №1', 44 id: 13571, 45 img: 'https://i.imgur.com/GM7FYxR.png', 46 ranks: { 47 speedDsc: [820, 740, 650, 550, 460, 360], 48 360: { 49 speed: 360, 50 title: 'Гонщик', 51 img: 'https://i.imgur.com/yEajMRl.png', 52 }, 53 460: { 54 speed: 460, 55 title: 'Маньяк', 56 img: 'https://i.imgur.com/YtcEMZt.png', 57 }, 58 550: { 59 speed: 550, 60 title: 'Супермен', 61 img: 'https://i.imgur.com/Z4fASnv.png', 62 }, 63 650: { 64 speed: 650, 65 title: 'Кибергонщик', 66 img: 'https://i.imgur.com/IwpUfgP.png', 67 }, 68 740: { 69 speed: 740, 70 title: 'Экстракибер', 71 img: 'https://i.imgur.com/JtQAOXO.png', 72 }, 73 820: { 74 speed: 820, 75 title: 'Тахион', 76 img: 'https://i.imgur.com/sQqPYQB.png', 77 }, 78 }, 79 }, 80 13572: { 81 key: 2, 82 name: 'Упражнение №2', 83 id: 13572, 84 img: 'https://i.imgur.com/JbV0w9v.png', 85 ranks: { 86 speedDsc: [820, 720, 620, 520, 450, 350], 87 350: { 88 speed: 350, 89 title: 'Гонщик', 90 img: 'https://i.imgur.com/cMOG18k.png', 91 }, 92 450: { 93 speed: 450, 94 title: 'Маньяк', 95 img: 'https://i.imgur.com/XQGOdpR.png', 96 }, 97 520: { 98 speed: 520, 99 title: 'Супермен', 100 img: 'https://i.imgur.com/nnOhVaU.png', 101 }, 102 620: { 103 speed: 620, 104 title: 'Кибергонщик', 105 img: 'https://i.imgur.com/Yq4Y89C.png', 106 }, 107 720: { 108 speed: 720, 109 title: 'Экстракибер', 110 img: 'https://i.imgur.com/vznSgD8.png', 111 }, 112 820: { 113 speed: 820, 114 title: 'Тахион', 115 img: 'https://i.imgur.com/CtONIMH.png', 116 }, 117 }, 118 }, 119 13573: { 120 key: 3, 121 name: 'Упражнение №3', 122 id: 13573, 123 img: 'https://i.imgur.com/xDO3QVO.png', 124 ranks: { 125 speedDsc: [710, 640, 580, 520, 450, 360], 126 360: { 127 speed: 360, 128 title: 'Гонщик', 129 img: 'https://i.imgur.com/bAB1XDE.png', 130 }, 131 450: { 132 speed: 450, 133 title: 'Маньяк', 134 img: 'https://i.imgur.com/7Tg1vGp.png', 135 }, 136 520: { 137 speed: 520, 138 title: 'Супермен', 139 img: 'https://i.imgur.com/KVup98G.png', 140 }, 141 580: { 142 speed: 580, 143 title: 'Кибергонщик', 144 img: 'https://i.imgur.com/YYWWxiJ.png', 145 }, 146 640: { 147 speed: 640, 148 title: 'Экстракибер', 149 img: 'https://i.imgur.com/K1qp69c.png', 150 }, 151 710: { 152 speed: 710, 153 title: 'Тахион', 154 img: 'https://i.imgur.com/qKlfRm1.png', 155 }, 156 }, 157 }, 158 13574: { 159 key: 4, 160 name: 'Упражнение №4', 161 id: 13574, 162 img: 'https://i.imgur.com/j7pY7HB.png', 163 ranks: { 164 speedDsc: [860, 760, 660, 560, 470, 360], 165 360: { 166 speed: 360, 167 title: 'Гонщик', 168 img: 'https://i.imgur.com/h7RFkmw.png', 169 }, 170 470: { 171 speed: 470, 172 title: 'Маньяк', 173 img: 'https://i.imgur.com/FxVhrLq.png', 174 }, 175 560: { 176 speed: 560, 177 title: 'Супермен', 178 img: 'https://i.imgur.com/XF35GFC.png', 179 }, 180 660: { 181 speed: 660, 182 title: 'Кибергонщик', 183 img: 'https://i.imgur.com/MAr3IbZ.png', 184 }, 185 760: { 186 speed: 760, 187 title: 'Экстракибер', 188 img: 'https://i.imgur.com/CoGcNcD.png', 189 }, 190 860: { 191 speed: 860, 192 title: 'Тахион', 193 img: 'https://i.imgur.com/qhroUuC.png', 194 }, 195 }, 196 }, 197 13583: { 198 key: 5, 199 name: 'Упражнение №5', 200 id: 13583, 201 img: 'https://i.imgur.com/t6KK66v.png', 202 ranks: { 203 speedDsc: [750, 670, 600, 510, 420, 350], 204 350: { 205 speed: 350, 206 title: 'Гонщик', 207 img: 'https://i.imgur.com/FJpFAOm.png', 208 }, 209 420: { 210 speed: 420, 211 title: 'Маньяк', 212 img: 'https://i.imgur.com/SVx4pn7.png', 213 }, 214 510: { 215 speed: 510, 216 title: 'Супермен', 217 img: 'https://i.imgur.com/vXUEA46.png', 218 }, 219 600: { 220 speed: 600, 221 title: 'Кибергонщик', 222 img: 'https://i.imgur.com/0wNJAjj.png', 223 }, 224 670: { 225 speed: 670, 226 title: 'Экстракибер', 227 img: 'https://i.imgur.com/HBO07ng.png', 228 }, 229 750: { 230 speed: 750, 231 title: 'Тахион', 232 img: 'https://i.imgur.com/K25HjtG.png', 233 }, 234 }, 235 }, 236 13584: { 237 key: 6, 238 name: 'Упражнение №6', 239 id: 13584, 240 img: 'https://i.imgur.com/TkzNZzk.png', 241 ranks: { 242 speedDsc: [740, 660, 580, 500, 430, 330], 243 330: { 244 speed: 330, 245 title: 'Гонщик', 246 img: 'https://i.imgur.com/4bBMI9b.png', 247 }, 248 430: { 249 speed: 430, 250 title: 'Маньяк', 251 img: 'https://i.imgur.com/9MMvKSb.png', 252 }, 253 500: { 254 speed: 500, 255 title: 'Супермен', 256 img: 'https://i.imgur.com/XcBaOFf.png', 257 }, 258 580: { 259 speed: 580, 260 title: 'Кибергонщик', 261 img: 'https://i.imgur.com/so2CJ1t.png', 262 }, 263 660: { 264 speed: 660, 265 title: 'Экстракибер', 266 img: 'https://i.imgur.com/ETL2npT.png', 267 }, 268 740: { 269 speed: 740, 270 title: 'Тахион', 271 img: 'https://i.imgur.com/NJXwMi4.png', 272 }, 273 }, 274 }, 275 13585: { 276 key: 7, 277 name: 'Упражнение №7', 278 id: 13585, 279 img: 'https://i.imgur.com/Tzzl8fP.png', 280 ranks: { 281 speedDsc: [800, 700, 600, 500, 430, 340], 282 340: { 283 speed: 340, 284 title: 'Гонщик', 285 img: 'https://i.imgur.com/RQ6HiCY.png', 286 }, 287 430: { 288 speed: 430, 289 title: 'Маньяк', 290 img: 'https://i.imgur.com/Fhr5JXM.png', 291 }, 292 500: { 293 speed: 500, 294 title: 'Супермен', 295 img: 'https://i.imgur.com/3KhjIQF.png', 296 }, 297 600: { 298 speed: 600, 299 title: 'Кибергонщик', 300 img: 'https://i.imgur.com/4QYKMf7.png', 301 }, 302 700: { 303 speed: 700, 304 title: 'Экстракибер', 305 img: 'https://i.imgur.com/v8LTzeY.png', 306 }, 307 800: { 308 speed: 800, 309 title: 'Тахион', 310 img: 'https://i.imgur.com/LPNPXMO.png', 311 }, 312 }, 313 }, 314 13654: { 315 key: 8, 316 name: 'Упражнение №8', 317 id: 13654, 318 img: 'https://i.imgur.com/MXrVIzN.png', 319 ranks: { 320 speedDsc: [610, 550, 490, 430, 370, 280], 321 280: { 322 speed: 280, 323 title: 'Гонщик', 324 img: 'https://i.imgur.com/tXyEMjY.png', 325 }, 326 370: { 327 speed: 370, 328 title: 'Маньяк', 329 img: 'https://i.imgur.com/RDhvvpb.png', 330 }, 331 430: { 332 speed: 430, 333 title: 'Супермен', 334 img: 'https://i.imgur.com/GhgwUlp.png', 335 }, 336 490: { 337 speed: 490, 338 title: 'Кибергонщик', 339 img: 'https://i.imgur.com/Ti3rH5J.png', 340 }, 341 550: { 342 speed: 550, 343 title: 'Экстракибер', 344 img: 'https://i.imgur.com/WB3ENLK.png', 345 }, 346 610: { 347 speed: 610, 348 title: 'Тахион', 349 img: 'https://i.imgur.com/zJHxSA8.png', 350 }, 351 }, 352 }, 353 13656: { 354 key: 9, 355 name: 'Упражнение №9', 356 id: 13656, 357 img: 'https://i.imgur.com/dJQBk7c.png', 358 ranks: { 359 speedDsc: [500, 420, 370, 320, 250, 200], 360 200: { 361 speed: 200, 362 title: 'Гонщик', 363 img: 'https://i.imgur.com/X10EezY.png', 364 }, 365 250: { 366 speed: 250, 367 title: 'Маньяк', 368 img: 'https://i.imgur.com/r2MwcqF.png', 369 }, 370 320: { 371 speed: 320, 372 title: 'Супермен', 373 img: 'https://i.imgur.com/QJR1qJU.png', 374 }, 375 370: { 376 speed: 370, 377 title: 'Кибергонщик', 378 img: 'https://i.imgur.com/ynn4FWU.png', 379 }, 380 420: { 381 speed: 420, 382 title: 'Экстракибер', 383 img: 'https://i.imgur.com/SWMGrvu.png', 384 }, 385 500: { 386 speed: 500, 387 title: 'Тахион', 388 img: 'https://i.imgur.com/wcRPbLa.png', 389 }, 390 }, 391 }, 392 13659: { 393 key: 10, 394 name: 'Упражнение №10', 395 id: 13659, 396 img: 'https://i.imgur.com/OgycVZr.png', 397 ranks: { 398 speedDsc: [830, 740, 640, 540, 460, 360], 399 360: { 400 speed: 360, 401 title: 'Гонщик', 402 img: 'https://i.imgur.com/SJvsE5N.png', 403 }, 404 460: { 405 speed: 460, 406 title: 'Маньяк', 407 img: 'https://i.imgur.com/j8Ir1Eb.png', 408 }, 409 540: { 410 speed: 540, 411 title: 'Супермен', 412 img: 'https://i.imgur.com/0CjGRRR.png', 413 }, 414 640: { 415 speed: 640, 416 title: 'Кибергонщик', 417 img: 'https://i.imgur.com/XbHAu2x.png', 418 }, 419 740: { 420 speed: 740, 421 title: 'Экстракибер', 422 img: 'https://i.imgur.com/MWlYQHl.png', 423 }, 424 830: { 425 speed: 830, 426 title: 'Тахион', 427 img: 'https://i.imgur.com/7tF7loD.png', 428 }, 429 }, 430 }, 431 13661: { 432 key: 11, 433 name: 'Упражнение №11', 434 id: 13661, 435 img: 'https://i.imgur.com/5QHWW3X.png', 436 ranks: { 437 speedDsc: [780, 680, 580, 480, 390, 330], 438 330: { 439 speed: 330, 440 title: 'Гонщик', 441 img: 'https://i.imgur.com/dZM5Obo.png', 442 }, 443 390: { 444 speed: 390, 445 title: 'Маньяк', 446 img: 'https://i.imgur.com/qcP3lB8.png', 447 }, 448 480: { 449 speed: 480, 450 title: 'Супермен', 451 img: 'https://i.imgur.com/pjkWjgI.png', 452 }, 453 580: { 454 speed: 580, 455 title: 'Кибергонщик', 456 img: 'https://i.imgur.com/yTWaU2f.png', 457 }, 458 680: { 459 speed: 680, 460 title: 'Экстракибер', 461 img: 'https://i.imgur.com/wUXznwl.png', 462 }, 463 780: { 464 speed: 780, 465 title: 'Тахион', 466 img: 'https://i.imgur.com/B0134Re.png', 467 }, 468 }, 469 }, 470 13663: { 471 key: 12, 472 name: 'Упражнение №12', 473 id: 13663, 474 img: 'https://i.imgur.com/VL6sgjp.png', 475 ranks: { 476 speedDsc: [850, 720, 620, 520, 400, 340], 477 340: { 478 speed: 340, 479 title: 'Гонщик', 480 img: 'https://i.imgur.com/RXHyWNl.png', 481 }, 482 400: { 483 speed: 400, 484 title: 'Маньяк', 485 img: 'https://i.imgur.com/s1RnHR6.png', 486 }, 487 520: { 488 speed: 520, 489 title: 'Супермен', 490 img: 'https://i.imgur.com/CsgPRDT.png', 491 }, 492 620: { 493 speed: 620, 494 title: 'Кибергонщик', 495 img: 'https://i.imgur.com/30L8oiV.png', 496 }, 497 720: { 498 speed: 720, 499 title: 'Экстракибер', 500 img: 'https://i.imgur.com/Y02ipCl.png', 501 }, 502 850: { 503 speed: 850, 504 title: 'Тахион', 505 img: 'https://i.imgur.com/s2rHDZP.png', 506 }, 507 }, 508 }, 509 13664: { 510 key: 13, 511 name: 'Упражнение №13', 512 id: 13664, 513 img: 'https://i.imgur.com/uEXInoT.png', 514 ranks: { 515 speedDsc: [900, 800, 670, 550, 450, 350], 516 350: { 517 speed: 350, 518 title: 'Гонщик', 519 img: 'https://i.imgur.com/xUq0vhe.png', 520 }, 521 450: { 522 speed: 450, 523 title: 'Маньяк', 524 img: 'https://i.imgur.com/Z5Idxnd.png', 525 }, 526 550: { 527 speed: 550, 528 title: 'Супермен', 529 img: 'https://i.imgur.com/mQcxZyX.png', 530 }, 531 670: { 532 speed: 670, 533 title: 'Кибергонщик', 534 img: 'https://i.imgur.com/2LtgIh3.png', 535 }, 536 800: { 537 speed: 800, 538 title: 'Экстракибер', 539 img: 'https://i.imgur.com/FVz4Bqw.png', 540 }, 541 900: { 542 speed: 900, 543 title: 'Тахион', 544 img: 'https://i.imgur.com/ZlwNd9y.png', 545 }, 546 }, 547 }, 548 16346: { 549 key: 14, 550 name: 'Упражнение №14', 551 id: 16346, 552 img: 'https://i.imgur.com/t0IUXo4.png', 553 ranks: { 554 speedDsc: [750, 670, 580, 490, 420, 340], 555 340: { 556 speed: 340, 557 title: 'Гонщик', 558 img: 'https://i.imgur.com/QkUI1jV.png', 559 }, 560 420: { 561 speed: 420, 562 title: 'Маньяк', 563 img: 'https://i.imgur.com/FPcKwY2.png', 564 }, 565 490: { 566 speed: 490, 567 title: 'Супермен', 568 img: 'https://i.imgur.com/yriiarB.png', 569 }, 570 580: { 571 speed: 580, 572 title: 'Кибергонщик', 573 img: 'https://i.imgur.com/JNMCmjr.png', 574 }, 575 670: { 576 speed: 670, 577 title: 'Экстракибер', 578 img: 'https://i.imgur.com/EzJKv1z.png', 579 }, 580 750: { 581 speed: 750, 582 title: 'Тахион', 583 img: 'https://i.imgur.com/KxVQWHj.png', 584 }, 585 }, 586 }, 587 16759: { 588 key: 15, 589 name: 'Упражнение №15', 590 id: 16759, 591 img: 'https://i.imgur.com/9m9M4uO.png', 592 ranks: { 593 speedDsc: [830, 730, 630, 550, 440, 370], 594 370: { 595 speed: 370, 596 title: 'Гонщик', 597 img: 'https://i.imgur.com/eCEkMAX.png', 598 }, 599 440: { 600 speed: 440, 601 title: 'Маньяк', 602 img: 'https://i.imgur.com/IhhdsCx.png', 603 }, 604 550: { 605 speed: 550, 606 title: 'Супермен', 607 img: 'https://i.imgur.com/p7ptaqH.png', 608 }, 609 630: { 610 speed: 630, 611 title: 'Кибергонщик', 612 img: 'https://i.imgur.com/QPBehyj.png', 613 }, 614 730: { 615 speed: 730, 616 title: 'Экстракибер', 617 img: 'https://i.imgur.com/kcYmd0A.png', 618 }, 619 830: { 620 speed: 830, 621 title: 'Тахион', 622 img: 'https://i.imgur.com/HPYAwD7.png', 623 }, 624 }, 625 }, 626 16762: { 627 key: 16, 628 name: 'Упражнение №16', 629 id: 16762, 630 img: 'https://i.imgur.com/heHjq71.png', 631 ranks: { 632 speedDsc: [880, 780, 670, 570, 470, 370], 633 370: { 634 speed: 370, 635 title: 'Гонщик', 636 img: 'https://i.imgur.com/W0Q0GOa.png', 637 }, 638 470: { 639 speed: 470, 640 title: 'Маньяк', 641 img: 'https://i.imgur.com/HYRBO2g.png', 642 }, 643 570: { 644 speed: 570, 645 title: 'Супермен', 646 img: 'https://i.imgur.com/ReBExFN.png', 647 }, 648 670: { 649 speed: 670, 650 title: 'Кибергонщик', 651 img: 'https://i.imgur.com/K3Ja1q6.png', 652 }, 653 780: { 654 speed: 780, 655 title: 'Экстракибер', 656 img: 'https://i.imgur.com/lFRh9Xp.png', 657 }, 658 880: { 659 speed: 880, 660 title: 'Тахион', 661 img: 'https://i.imgur.com/qgO3Ped.png', 662 }, 663 }, 664 }, 665 17495: { 666 key: 17, 667 name: 'Упражнение №17', 668 id: 17495, 669 img: 'https://i.imgur.com/5XtzRA6.png', 670 ranks: { 671 speedDsc: [800, 700, 600, 500, 441, 339], 672 339: { 673 speed: 339, 674 title: 'Гонщик', 675 img: 'https://i.imgur.com/Zp4AoR5.png', 676 }, 677 441: { 678 speed: 441, 679 title: 'Маньяк', 680 img: 'https://i.imgur.com/03DZ0OD.png', 681 }, 682 500: { 683 speed: 500, 684 title: 'Супермен', 685 img: 'https://i.imgur.com/n9AlOdC.png', 686 }, 687 600: { 688 speed: 600, 689 title: 'Кибергонщик', 690 img: 'https://i.imgur.com/i4wJNIp.png', 691 }, 692 700: { 693 speed: 700, 694 title: 'Экстракибер', 695 img: 'https://i.imgur.com/BZx7UtT.png', 696 }, 697 800: { 698 speed: 800, 699 title: 'Тахион', 700 img: 'https://i.imgur.com/JRtvmZI.png', 701 }, 702 }, 703 }, 704 17497: { 705 key: 18, 706 name: 'Упражнение №18', 707 id: 17497, 708 img: 'https://i.imgur.com/RZEfa3s.png', 709 ranks: { 710 speedDsc: [770, 680, 600, 520, 450, 350], 711 350: { 712 speed: 350, 713 title: 'Гонщик', 714 img: 'https://i.imgur.com/rML2BMp.png', 715 }, 716 450: { 717 speed: 450, 718 title: 'Маньяк', 719 img: 'https://i.imgur.com/uHqcmFx.png', 720 }, 721 520: { 722 speed: 520, 723 title: 'Супермен', 724 img: 'https://i.imgur.com/NCQiHsR.png', 725 }, 726 600: { 727 speed: 600, 728 title: 'Кибергонщик', 729 img: 'https://i.imgur.com/eNnGdDw.png', 730 }, 731 680: { 732 speed: 680, 733 title: 'Экстракибер', 734 img: 'https://i.imgur.com/2a3Bn4q.png', 735 }, 736 770: { 737 speed: 770, 738 title: 'Тахион', 739 img: 'https://i.imgur.com/Q6GXO0z.png', 740 }, 741 }, 742 }, 743 17498: { 744 key: 19, 745 name: 'Упражнение №19', 746 id: 17498, 747 img: 'https://i.imgur.com/MLOb54T.png', 748 ranks: { 749 speedDsc: [720, 630, 550, 480, 420, 330], 750 330: { 751 speed: 330, 752 title: 'Гонщик', 753 img: 'https://i.imgur.com/eeDzKJ4.png', 754 }, 755 420: { 756 speed: 420, 757 title: 'Маньяк', 758 img: 'https://i.imgur.com/Xfxsrco.png', 759 }, 760 480: { 761 speed: 480, 762 title: 'Супермен', 763 img: 'https://i.imgur.com/QACC9JB.png', 764 }, 765 550: { 766 speed: 550, 767 title: 'Кибергонщик', 768 img: 'https://i.imgur.com/rtarV6T.png', 769 }, 770 630: { 771 speed: 630, 772 title: 'Экстракибер', 773 img: 'https://i.imgur.com/Mm4RkzB.png', 774 }, 775 720: { 776 speed: 720, 777 title: 'Тахион', 778 img: 'https://i.imgur.com/DDXqGHy.png', 779 }, 780 }, 781 }, 782 17499: { 783 key: 20, 784 name: 'Упражнение №20', 785 id: 17499, 786 img: 'https://i.imgur.com/W7YHKkf.png', 787 ranks: { 788 speedDsc: [820, 720, 620, 520, 420, 350], 789 350: { 790 speed: 350, 791 title: 'Гонщик', 792 img: 'https://i.imgur.com/Ht58QSH.png', 793 }, 794 420: { 795 speed: 420, 796 title: 'Маньяк', 797 img: 'https://i.imgur.com/EnEAa86.png', 798 }, 799 520: { 800 speed: 520, 801 title: 'Супермен', 802 img: 'https://i.imgur.com/1Iw4h8O.png', 803 }, 804 620: { 805 speed: 620, 806 title: 'Кибергонщик', 807 img: 'https://i.imgur.com/GjZY3bR.png', 808 }, 809 720: { 810 speed: 720, 811 title: 'Экстракибер', 812 img: 'https://i.imgur.com/2XUdsEu.png', 813 }, 814 820: { 815 speed: 820, 816 title: 'Тахион', 817 img: 'https://i.imgur.com/oSbuVM5.png', 818 }, 819 }, 820 }, 821 32013: { 822 key: 21, 823 name: 'Упражнение №21', 824 id: 32013, 825 img: 'https://i.imgur.com/2RCNzCN.png', 826 ranks: { 827 speedDsc: [710, 640, 580, 500, 430, 340], 828 340: { 829 speed: 340, 830 title: 'Гонщик', 831 img: 'https://i.imgur.com/sRfUjE4.png', 832 }, 833 430: { 834 speed: 430, 835 title: 'Маньяк', 836 img: 'https://i.imgur.com/TIX7uet.png', 837 }, 838 500: { 839 speed: 500, 840 title: 'Супермен', 841 img: 'https://i.imgur.com/k60nmHJ.png', 842 }, 843 580: { 844 speed: 580, 845 title: 'Кибергонщик', 846 img: 'https://i.imgur.com/6fwBreh.png', 847 }, 848 640: { 849 speed: 640, 850 title: 'Экстракибер', 851 img: 'https://i.imgur.com/qW56Yif.png', 852 }, 853 710: { 854 speed: 710, 855 title: 'Тахион', 856 img: 'https://i.imgur.com/wqHC0Rc.png', 857 }, 858 }, 859 }, 860 32014: { 861 key: 22, 862 name: 'Упражнение №22', 863 id: 32014, 864 img: 'https://i.imgur.com/i5YMtU8.png', 865 ranks: { 866 speedDsc: [860, 760, 660, 560, 470, 370], 867 370: { 868 speed: 370, 869 title: 'Гонщик', 870 img: 'https://i.imgur.com/3xfekby.png', 871 }, 872 470: { 873 speed: 470, 874 title: 'Маньяк', 875 img: 'https://i.imgur.com/fYSigf1.png', 876 }, 877 560: { 878 speed: 560, 879 title: 'Супермен', 880 img: 'https://i.imgur.com/PGxQ7v0.png', 881 }, 882 660: { 883 speed: 660, 884 title: 'Кибергонщик', 885 img: 'https://i.imgur.com/XQETcru.png', 886 }, 887 760: { 888 speed: 760, 889 title: 'Экстракибер', 890 img: 'https://i.imgur.com/Vey8mmz.png', 891 }, 892 860: { 893 speed: 860, 894 title: 'Тахион', 895 img: 'https://i.imgur.com/q2TH2Ra.png', 896 }, 897 }, 898 }, 899 32015: { 900 key: 23, 901 name: 'Упражнение №23', 902 id: 32015, 903 img: 'https://i.imgur.com/RcU16nl.png', 904 ranks: { 905 speedDsc: [650, 550, 470, 400, 330, 270], 906 270: { 907 speed: 270, 908 title: 'Гонщик', 909 img: 'https://i.imgur.com/BinniPv.png', 910 }, 911 330: { 912 speed: 330, 913 title: 'Маньяк', 914 img: 'https://i.imgur.com/9ONAR3q.png', 915 }, 916 400: { 917 speed: 400, 918 title: 'Супермен', 919 img: 'https://i.imgur.com/s1roPvh.png', 920 }, 921 470: { 922 speed: 470, 923 title: 'Кибергонщик', 924 img: 'https://i.imgur.com/LlZHSkC.png', 925 }, 926 550: { 927 speed: 550, 928 title: 'Экстракибер', 929 img: 'https://i.imgur.com/4SX7o6W.png', 930 }, 931 650: { 932 speed: 650, 933 title: 'Тахион', 934 img: 'https://i.imgur.com/006ylHq.png', 935 }, 936 }, 937 }, 938 32016: { 939 key: 24, 940 name: 'Упражнение заключительное', 941 id: 32016, 942 img: 'https://i.imgur.com/D6MYqn0.png', 943 ranks: { 944 speedDsc: [900, 800, 700, 600, 500, 400], 945 400: { 946 speed: 400, 947 title: 'Гонщик', 948 img: 'https://i.imgur.com/aYKKctq.png', 949 }, 950 500: { 951 speed: 500, 952 title: 'Маньяк', 953 img: 'https://i.imgur.com/qvIBCNV.png', 954 }, 955 600: { 956 speed: 600, 957 title: 'Супермен', 958 img: 'https://i.imgur.com/qTWOtN9.png', 959 }, 960 700: { 961 speed: 700, 962 title: 'Кибергонщик', 963 img: 'https://i.imgur.com/ZnPU947.png', 964 }, 965 800: { 966 speed: 800, 967 title: 'Экстракибер', 968 img: 'https://i.imgur.com/cQqT4fe.png', 969 }, 970 900: { 971 speed: 900, 972 title: 'Тахион', 973 img: 'https://i.imgur.com/FSm4K8j.png', 974 }, 975 }, 976 }, 977 }; 978 979 const HRUST_IDS = Object 980 .values(HRUST) 981 .sort((a, b) => a.key - b.key) 982 .map(item => item.id); 983 984 const CRACK = { 985 71989: { 986 key: 1, 987 name: 'Exercise No. 1', 988 id: 71989, 989 img: 'https://i.imgur.com/Ux05kVO.png', 990 ranks: { 991 speedDsc: [820, 720, 620, 520, 450, 350], 992 350: { 993 speed: 350, 994 title: 'Гонщик', 995 img: 'https://i.imgur.com/xnGAlYc.png', 996 }, 997 450: { 998 speed: 450, 999 title: 'Маньяк', 1000 img: 'https://i.imgur.com/Q8HWpKB.png', 1001 }, 1002 520: { 1003 speed: 520, 1004 title: 'Супермен', 1005 img: 'https://i.imgur.com/Apr2vSC.png', 1006 }, 1007 620: { 1008 speed: 620, 1009 title: 'Кибергонщик', 1010 img: 'https://i.imgur.com/BRhjtIL.png', 1011 }, 1012 720: { 1013 speed: 720, 1014 title: 'Экстракибер', 1015 img: 'https://i.imgur.com/NkWZMCZ.png', 1016 }, 1017 820: { 1018 speed: 820, 1019 title: 'Тахион', 1020 img: 'https://i.imgur.com/ZrgHciO.png', 1021 }, 1022 }, 1023 }, 1024 71993: { 1025 key: 2, 1026 name: 'Exercise No. 2', 1027 id: 71993, 1028 img: 'https://i.imgur.com/lNVIIwu.png', 1029 ranks: { 1030 speedDsc: [820, 720, 620, 520, 440, 350], 1031 350: { 1032 speed: 350, 1033 title: 'Гонщик', 1034 img: 'https://i.imgur.com/qN7TuHm.png', 1035 }, 1036 440: { 1037 speed: 440, 1038 title: 'Маньяк', 1039 img: 'https://i.imgur.com/d4VO2NH.png', 1040 }, 1041 520: { 1042 speed: 520, 1043 title: 'Супермен', 1044 img: 'https://i.imgur.com/Oi8BHgT.png', 1045 }, 1046 620: { 1047 speed: 620, 1048 title: 'Кибергонщик', 1049 img: 'https://i.imgur.com/fsjjGPq.png', 1050 }, 1051 720: { 1052 speed: 720, 1053 title: 'Экстракибер', 1054 img: 'https://i.imgur.com/WL9wKqa.png', 1055 }, 1056 820: { 1057 speed: 820, 1058 title: 'Тахион', 1059 img: 'https://i.imgur.com/9CQk30r.png', 1060 }, 1061 }, 1062 }, 1063 94895: { 1064 key: 3, 1065 name: 'Exercise No. 3', 1066 id: 94895, 1067 img: 'https://i.imgur.com/8fh2zaH.png', 1068 ranks: { 1069 speedDsc: [710, 640, 580, 520, 450, 360], 1070 360: { 1071 speed: 360, 1072 title: 'Гонщик', 1073 img: 'https://i.imgur.com/DXBFiOM.png', 1074 }, 1075 450: { 1076 speed: 450, 1077 title: 'Маньяк', 1078 img: 'https://i.imgur.com/Vw8gBHI.png', 1079 }, 1080 520: { 1081 speed: 520, 1082 title: 'Супермен', 1083 img: 'https://i.imgur.com/vreynJD.png', 1084 }, 1085 580: { 1086 speed: 580, 1087 title: 'Кибергонщик', 1088 img: 'https://i.imgur.com/Y5unrji.png', 1089 }, 1090 640: { 1091 speed: 640, 1092 title: 'Экстракибер', 1093 img: 'https://i.imgur.com/8esa9kj.png', 1094 }, 1095 710: { 1096 speed: 710, 1097 title: 'Тахион', 1098 img: 'https://i.imgur.com/Xnbyhpx.png', 1099 }, 1100 }, 1101 }, 1102 71997: { 1103 key: 4, 1104 name: 'Exercise No. 4', 1105 id: 71997, 1106 img: 'https://i.imgur.com/WZjP92h.png', 1107 ranks: { 1108 speedDsc: [860, 760, 670, 570, 480, 380], 1109 380: { 1110 speed: 380, 1111 title: 'Гонщик', 1112 img: 'https://i.imgur.com/yGVfETj.png', 1113 }, 1114 480: { 1115 speed: 480, 1116 title: 'Маньяк', 1117 img: 'https://i.imgur.com/qcWOSps.png', 1118 }, 1119 570: { 1120 speed: 570, 1121 title: 'Супермен', 1122 img: 'https://i.imgur.com/j8Ny9ce.png', 1123 }, 1124 670: { 1125 speed: 670, 1126 title: 'Кибергонщик', 1127 img: 'https://i.imgur.com/lSUvllv.png', 1128 }, 1129 760: { 1130 speed: 760, 1131 title: 'Экстракибер', 1132 img: 'https://i.imgur.com/RDGMlQQ.png', 1133 }, 1134 860: { 1135 speed: 860, 1136 title: 'Тахион', 1137 img: 'https://i.imgur.com/vb3aC6O.png', 1138 }, 1139 }, 1140 }, 1141 72026: { 1142 key: 5, 1143 name: 'Exercise No. 5', 1144 id: 72026, 1145 img: 'https://i.imgur.com/H8LFlUx.png', 1146 ranks: { 1147 speedDsc: [820, 720, 620, 520, 450, 350], 1148 350: { 1149 speed: 350, 1150 title: 'Гонщик', 1151 img: 'https://i.imgur.com/YivUe4M.png', 1152 }, 1153 450: { 1154 speed: 450, 1155 title: 'Маньяк', 1156 img: 'https://i.imgur.com/qJanTpJ.png', 1157 }, 1158 520: { 1159 speed: 520, 1160 title: 'Супермен', 1161 img: 'https://i.imgur.com/f5k5ukG.png', 1162 }, 1163 620: { 1164 speed: 620, 1165 title: 'Кибергонщик', 1166 img: 'https://i.imgur.com/koXCGRM.png', 1167 }, 1168 720: { 1169 speed: 720, 1170 title: 'Экстракибер', 1171 img: 'https://i.imgur.com/RGnxsPv.png', 1172 }, 1173 820: { 1174 speed: 820, 1175 title: 'Тахион', 1176 img: 'https://i.imgur.com/msVHMa2.png', 1177 }, 1178 }, 1179 }, 1180 72027: { 1181 key: 6, 1182 name: 'Exercise No. 6', 1183 id: 72027, 1184 img: 'https://i.imgur.com/vubBpH5.png', 1185 ranks: { 1186 speedDsc: [800, 700, 610, 520, 430, 360], 1187 360: { 1188 speed: 360, 1189 title: 'Гонщик', 1190 img: 'https://i.imgur.com/KZdHiTX.pn', 1191 }, 1192 430: { 1193 speed: 430, 1194 title: 'Маньяк', 1195 img: 'https://i.imgur.com/5JflEQg.png', 1196 }, 1197 520: { 1198 speed: 520, 1199 title: 'Супермен', 1200 img: 'https://i.imgur.com/l4woPFn.png', 1201 }, 1202 610: { 1203 speed: 610, 1204 title: 'Кибергонщик', 1205 img: 'https://i.imgur.com/ZfS5XrZ.png', 1206 }, 1207 700: { 1208 speed: 700, 1209 title: 'Экстракибер', 1210 img: 'https://i.imgur.com/UaHQGUN.png', 1211 }, 1212 800: { 1213 speed: 800, 1214 title: 'Тахион', 1215 img: 'https://i.imgur.com/OWBDXtZ.png', 1216 }, 1217 }, 1218 }, 1219 72102: { 1220 key: 7, 1221 name: 'Exercise No. 7', 1222 id: 72102, 1223 img: 'https://i.imgur.com/Qa9HWqw.png', 1224 ranks: { 1225 speedDsc: [800, 710, 630, 530, 430, 330], 1226 330: { 1227 speed: 330, 1228 title: 'Гонщик', 1229 img: 'https://i.imgur.com/rlRV9cc.png', 1230 }, 1231 430: { 1232 speed: 430, 1233 title: 'Маньяк', 1234 img: 'https://i.imgur.com/Zj6MJgQ.png', 1235 }, 1236 530: { 1237 speed: 530, 1238 title: 'Супермен', 1239 img: 'https://i.imgur.com/rZm4yad.png', 1240 }, 1241 630: { 1242 speed: 630, 1243 title: 'Кибергонщик', 1244 img: 'https://i.imgur.com/9S01ACi.png', 1245 }, 1246 710: { 1247 speed: 710, 1248 title: 'Экстракибер', 1249 img: 'https://i.imgur.com/YhIjzao.png', 1250 }, 1251 800: { 1252 speed: 800, 1253 title: 'Тахион', 1254 img: 'https://i.imgur.com/dk4MjXp.png', 1255 }, 1256 }, 1257 }, 1258 72103: { 1259 key: 8, 1260 name: 'Exercise No. 8', 1261 id: 72103, 1262 img: 'https://i.imgur.com/ywGt4ws.png', 1263 ranks: { 1264 speedDsc: [740, 660, 580, 500, 430, 330], 1265 330: { 1266 speed: 330, 1267 title: 'Гонщик', 1268 img: 'https://i.imgur.com/aPlowj5.png', 1269 }, 1270 430: { 1271 speed: 430, 1272 title: 'Маньяк', 1273 img: 'https://i.imgur.com/TP56YMF.png', 1274 }, 1275 500: { 1276 speed: 500, 1277 title: 'Супермен', 1278 img: 'https://i.imgur.com/dwIAUSV.png', 1279 }, 1280 580: { 1281 speed: 580, 1282 title: 'Кибергонщик', 1283 img: 'https://i.imgur.com/7ha3TEE.png', 1284 }, 1285 660: { 1286 speed: 660, 1287 title: 'Экстракибер', 1288 img: 'https://i.imgur.com/r7xSZDO.png', 1289 }, 1290 740: { 1291 speed: 740, 1292 title: 'Тахион', 1293 img: 'https://i.imgur.com/xAw3ien.png', 1294 }, 1295 }, 1296 }, 1297 72104: { 1298 key: 9, 1299 name: 'Exercise No. 9', 1300 id: 72104, 1301 img: 'https://i.imgur.com/erjVQUm.png', 1302 ranks: { 1303 speedDsc: [610, 550, 490, 430, 370, 280], 1304 280: { 1305 speed: 280, 1306 title: 'Гонщик', 1307 img: 'https://i.imgur.com/oYOMxTL.png', 1308 }, 1309 370: { 1310 speed: 370, 1311 title: 'Маньяк', 1312 img: 'https://i.imgur.com/cxpix80.png', 1313 }, 1314 430: { 1315 speed: 430, 1316 title: 'Супермен', 1317 img: 'https://i.imgur.com/ArpyERt.png', 1318 }, 1319 490: { 1320 speed: 490, 1321 title: 'Кибергонщик', 1322 img: 'https://i.imgur.com/y429OJn.png', 1323 }, 1324 550: { 1325 speed: 550, 1326 title: 'Экстракибер', 1327 img: 'https://i.imgur.com/iCyOdiQ.png', 1328 }, 1329 610: { 1330 speed: 610, 1331 title: 'Тахион', 1332 img: 'https://i.imgur.com/JuKUf2a.png', 1333 }, 1334 }, 1335 }, 1336 72106: { 1337 key: 10, 1338 name: 'Exercise No. 10', 1339 id: 72106, 1340 img: 'https://i.imgur.com/Ej0rUdl.png', 1341 ranks: { 1342 speedDsc: [830, 740, 650, 550, 460, 360], 1343 360: { 1344 speed: 360, 1345 title: 'Гонщик', 1346 img: 'https://i.imgur.com/Z1oVUXi.png', 1347 }, 1348 460: { 1349 speed: 460, 1350 title: 'Маньяк', 1351 img: 'https://i.imgur.com/WEVWsGY.png', 1352 }, 1353 550: { 1354 speed: 550, 1355 title: 'Супермен', 1356 img: 'https://i.imgur.com/94E7Nsv.png', 1357 }, 1358 650: { 1359 speed: 650, 1360 title: 'Кибергонщик', 1361 img: 'https://i.imgur.com/LMMbsYn.png', 1362 }, 1363 740: { 1364 speed: 740, 1365 title: 'Экстракибер', 1366 img: 'https://i.imgur.com/x5HmVRT.png', 1367 }, 1368 830: { 1369 speed: 830, 1370 title: 'Тахион', 1371 img: 'https://i.imgur.com/H2yWTHy.png', 1372 }, 1373 }, 1374 }, 1375 72118: { 1376 key: 11, 1377 name: 'Exercise No. 11', 1378 id: 72118, 1379 img: 'https://i.imgur.com/SB4T3wW.png', 1380 ranks: { 1381 speedDsc: [790, 690, 590, 490, 420, 340], 1382 340: { 1383 speed: 340, 1384 title: 'Гонщик', 1385 img: 'https://i.imgur.com/isunysC.png', 1386 }, 1387 420: { 1388 speed: 420, 1389 title: 'Маньяк', 1390 img: 'https://i.imgur.com/5MLt384.png', 1391 }, 1392 490: { 1393 speed: 490, 1394 title: 'Супермен', 1395 img: 'https://i.imgur.com/o7UHKpU.png', 1396 }, 1397 590: { 1398 speed: 590, 1399 title: 'Кибергонщик', 1400 img: 'https://i.imgur.com/hB98M22.png', 1401 }, 1402 690: { 1403 speed: 690, 1404 title: 'Экстракибер', 1405 img: 'https://i.imgur.com/d5naZiL.png', 1406 }, 1407 790: { 1408 speed: 790, 1409 title: 'Тахион', 1410 img: 'https://i.imgur.com/3FdTOF7.png', 1411 }, 1412 }, 1413 }, 1414 72186: { 1415 key: 12, 1416 name: 'Exercise No. 12', 1417 id: 72186, 1418 img: 'https://i.imgur.com/BNEDCiS.png', 1419 ranks: { 1420 speedDsc: [820, 720, 620, 520, 420, 340], 1421 340: { 1422 speed: 340, 1423 title: 'Гонщик', 1424 img: 'https://i.imgur.com/0QPBpwj.png', 1425 }, 1426 420: { 1427 speed: 420, 1428 title: 'Маньяк', 1429 img: 'https://i.imgur.com/pdndpbj.png', 1430 }, 1431 520: { 1432 speed: 520, 1433 title: 'Супермен', 1434 img: 'https://i.imgur.com/7kuipDX.png', 1435 }, 1436 620: { 1437 speed: 620, 1438 title: 'Кибергонщик', 1439 img: 'https://i.imgur.com/6gyntdR.png', 1440 }, 1441 720: { 1442 speed: 720, 1443 title: 'Экстракибер', 1444 img: 'https://i.imgur.com/qTYanB4.png', 1445 }, 1446 820: { 1447 speed: 820, 1448 title: 'Тахион', 1449 img: 'https://i.imgur.com/hMwqVLf.png', 1450 }, 1451 }, 1452 }, 1453 72354: { 1454 key: 13, 1455 name: 'Exercise No. 13', 1456 id: 72354, 1457 img: 'https://i.imgur.com/ZmCmGdO.png', 1458 ranks: { 1459 speedDsc: [830, 730, 630, 540, 440, 370], 1460 370: { 1461 speed: 370, 1462 title: 'Гонщик', 1463 img: 'https://i.imgur.com/WzWJw5O.png', 1464 }, 1465 440: { 1466 speed: 440, 1467 title: 'Маньяк', 1468 img: 'https://i.imgur.com/2fQLoRp.png', 1469 }, 1470 540: { 1471 speed: 540, 1472 title: 'Супермен', 1473 img: 'https://i.imgur.com/wiPXuhj.png', 1474 }, 1475 630: { 1476 speed: 630, 1477 title: 'Кибергонщик', 1478 img: 'https://i.imgur.com/wDX2xrp.png', 1479 }, 1480 730: { 1481 speed: 730, 1482 title: 'Экстракибер', 1483 img: 'https://i.imgur.com/cDa7X2h.png', 1484 }, 1485 830: { 1486 speed: 830, 1487 title: 'Тахион', 1488 img: 'https://i.imgur.com/IgD29oj.png', 1489 }, 1490 }, 1491 }, 1492 72356: { 1493 key: 14, 1494 name: 'Exercise No. 14', 1495 id: 72356, 1496 img: 'https://i.imgur.com/mgEnv67.png', 1497 ranks: { 1498 speedDsc: [800, 730, 630, 530, 420, 340], 1499 340: { 1500 speed: 340, 1501 title: 'Гонщик', 1502 img: 'https://i.imgur.com/XuF40Bb.png', 1503 }, 1504 420: { 1505 speed: 420, 1506 title: 'Маньяк', 1507 img: 'https://i.imgur.com/YLBq5V0.png', 1508 }, 1509 530: { 1510 speed: 530, 1511 title: 'Супермен', 1512 img: 'https://i.imgur.com/Jxd7ycP.png', 1513 }, 1514 630: { 1515 speed: 630, 1516 title: 'Кибергонщик', 1517 img: 'https://i.imgur.com/6R9HScI.png', 1518 }, 1519 730: { 1520 speed: 730, 1521 title: 'Экстракибер', 1522 img: 'https://i.imgur.com/tfFIhit.png', 1523 }, 1524 800: { 1525 speed: 800, 1526 title: 'Тахион', 1527 img: 'https://i.imgur.com/xYD4AnE.png', 1528 }, 1529 }, 1530 }, 1531 72357: { 1532 key: 15, 1533 name: 'Exercise No. 15', 1534 id: 72357, 1535 img: 'https://i.imgur.com/YjOD0k5.png', 1536 ranks: { 1537 speedDsc: [760, 680, 590, 500, 430, 350], 1538 350: { 1539 speed: 350, 1540 title: 'Гонщик', 1541 img: 'https://i.imgur.com/6e1Axi3.png', 1542 }, 1543 430: { 1544 speed: 430, 1545 title: 'Маньяк', 1546 img: 'https://i.imgur.com/DFcSbRQ.png', 1547 }, 1548 500: { 1549 speed: 500, 1550 title: 'Супермен', 1551 img: 'https://i.imgur.com/uSRAWAY.png', 1552 }, 1553 590: { 1554 speed: 590, 1555 title: 'Кибергонщик', 1556 img: 'https://i.imgur.com/KVb4N5B.png', 1557 }, 1558 680: { 1559 speed: 680, 1560 title: 'Экстракибер', 1561 img: 'https://i.imgur.com/RhhvkQ8.png', 1562 }, 1563 760: { 1564 speed: 760, 1565 title: 'Тахион', 1566 img: 'https://i.imgur.com/oI9sTBj.png', 1567 }, 1568 }, 1569 }, 1570 72428: { 1571 key: 16, 1572 name: 'Exercise No. 16', 1573 id: 72428, 1574 img: 'https://i.imgur.com/2fFC36Z.png', 1575 ranks: { 1576 speedDsc: [820, 720, 620, 520, 400, 340], 1577 340: { 1578 speed: 340, 1579 title: 'Гонщик', 1580 img: 'https://i.imgur.com/2jFgZIi.png', 1581 }, 1582 400: { 1583 speed: 400, 1584 title: 'Маньяк', 1585 img: 'https://i.imgur.com/jTSXNDs.png', 1586 }, 1587 520: { 1588 speed: 520, 1589 title: 'Супермен', 1590 img: 'https://i.imgur.com/LRKZzYC.png', 1591 }, 1592 620: { 1593 speed: 620, 1594 title: 'Кибергонщик', 1595 img: 'https://i.imgur.com/nqnOdTu.png', 1596 }, 1597 720: { 1598 speed: 720, 1599 title: 'Экстракибер', 1600 img: 'https://i.imgur.com/Y2sZqKk.png', 1601 }, 1602 820: { 1603 speed: 820, 1604 title: 'Тахион', 1605 img: 'https://i.imgur.com/StAwJIC.png', 1606 }, 1607 }, 1608 }, 1609 72569: { 1610 key: 17, 1611 name: 'Exercise No. 17', 1612 id: 72569, 1613 img: 'https://i.imgur.com/tJ5J5vW.png', 1614 ranks: { 1615 speedDsc: [810, 710, 610, 510, 420, 340], 1616 340: { 1617 speed: 340, 1618 title: 'Гонщик', 1619 img: 'https://i.imgur.com/qAixPzb.png', 1620 }, 1621 420: { 1622 speed: 420, 1623 title: 'Маньяк', 1624 img: 'https://i.imgur.com/iAX9UZE.png', 1625 }, 1626 510: { 1627 speed: 510, 1628 title: 'Супермен', 1629 img: 'https://i.imgur.com/TxPFcAt.png', 1630 }, 1631 610: { 1632 speed: 610, 1633 title: 'Кибергонщик', 1634 img: 'https://i.imgur.com/AELpLIq.png', 1635 }, 1636 710: { 1637 speed: 710, 1638 title: 'Экстракибер', 1639 img: 'https://i.imgur.com/nqmbFkp.png', 1640 }, 1641 810: { 1642 speed: 810, 1643 title: 'Тахион', 1644 img: 'https://i.imgur.com/1UhOXy8.png', 1645 }, 1646 }, 1647 }, 1648 72572: { 1649 key: 18, 1650 name: 'Exercise No. 18', 1651 id: 72572, 1652 img: 'https://i.imgur.com/pxhQutj.png', 1653 ranks: { 1654 speedDsc: [860, 760, 660, 560, 450, 350], 1655 350: { 1656 speed: 350, 1657 title: 'Гонщик', 1658 img: 'https://i.imgur.com/bRiYRjS.png', 1659 }, 1660 450: { 1661 speed: 450, 1662 title: 'Маньяк', 1663 img: 'https://i.imgur.com/ST2BOzs.png', 1664 }, 1665 560: { 1666 speed: 560, 1667 title: 'Супермен', 1668 img: 'https://i.imgur.com/ZwnPCIi.png', 1669 }, 1670 660: { 1671 speed: 660, 1672 title: 'Кибергонщик', 1673 img: 'https://i.imgur.com/0ujt68I.png', 1674 }, 1675 760: { 1676 speed: 760, 1677 title: 'Экстракибер', 1678 img: 'https://i.imgur.com/kIuFztd.png', 1679 }, 1680 860: { 1681 speed: 860, 1682 title: 'Тахион', 1683 img: 'https://i.imgur.com/LMy4YfI.png', 1684 }, 1685 }, 1686 }, 1687 72573: { 1688 key: 19, 1689 name: 'Exercise No. 19', 1690 id: 72573, 1691 img: 'https://i.imgur.com/oanrIOc.png', 1692 ranks: { 1693 speedDsc: [800, 700, 600, 500, 430, 340], 1694 340: { 1695 speed: 340, 1696 title: 'Гонщик', 1697 img: 'https://i.imgur.com/YPwHMQ6.png', 1698 }, 1699 430: { 1700 speed: 430, 1701 title: 'Маньяк', 1702 img: 'https://i.imgur.com/o4sCSaq.png', 1703 }, 1704 500: { 1705 speed: 500, 1706 title: 'Супермен', 1707 img: 'https://i.imgur.com/GJyw4Kw.png', 1708 }, 1709 600: { 1710 speed: 600, 1711 title: 'Кибергонщик', 1712 img: 'https://i.imgur.com/MmocQH1.png', 1713 }, 1714 700: { 1715 speed: 700, 1716 title: 'Экстракибер', 1717 img: 'https://i.imgur.com/SWyvWeU.png', 1718 }, 1719 800: { 1720 speed: 800, 1721 title: 'Тахион', 1722 img: 'https://i.imgur.com/wniQM38.png', 1723 }, 1724 }, 1725 }, 1726 82553: { 1727 key: 20, 1728 name: 'Exercise No. 20', 1729 id: 82553, 1730 img: 'https://i.imgur.com/lTZ6F9C.png', 1731 ranks: { 1732 speedDsc: [650, 550, 470, 400, 330, 270], 1733 270: { 1734 speed: 270, 1735 title: 'Гонщик', 1736 img: 'https://i.imgur.com/EAiHNqU.png', 1737 }, 1738 330: { 1739 speed: 330, 1740 title: 'Маньяк', 1741 img: 'https://i.imgur.com/ADBAdBW.png', 1742 }, 1743 400: { 1744 speed: 400, 1745 title: 'Супермен', 1746 img: 'https://i.imgur.com/xPXytYF.png', 1747 }, 1748 470: { 1749 speed: 470, 1750 title: 'Кибергонщик', 1751 img: 'https://i.imgur.com/S8QQHs5.png', 1752 }, 1753 550: { 1754 speed: 550, 1755 title: 'Экстракибер', 1756 img: 'https://i.imgur.com/HEV7cGd.png', 1757 }, 1758 650: { 1759 speed: 650, 1760 title: 'Тахион', 1761 img: 'https://i.imgur.com/l719WOh.png', 1762 }, 1763 }, 1764 }, 1765 85836: { 1766 key: 21, 1767 name: 'Exercise No. 21', 1768 id: 85836, 1769 img: 'https://i.imgur.com/hY41ly2.png', 1770 ranks: { 1771 speedDsc: [810, 710, 610, 510, 410, 340], 1772 340: { 1773 speed: 340, 1774 title: 'Гонщик', 1775 img: 'https://i.imgur.com/AmH9RQa.png', 1776 }, 1777 410: { 1778 speed: 410, 1779 title: 'Маньяк', 1780 img: 'https://i.imgur.com/hFm5e2t.png', 1781 }, 1782 510: { 1783 speed: 510, 1784 title: 'Супермен', 1785 img: 'https://i.imgur.com/QsRZmfg.png', 1786 }, 1787 610: { 1788 speed: 610, 1789 title: 'Кибергонщик', 1790 img: 'https://i.imgur.com/BrWdkHD.png', 1791 }, 1792 710: { 1793 speed: 710, 1794 title: 'Экстракибер', 1795 img: 'https://i.imgur.com/t6HFDaA.png', 1796 }, 1797 810: { 1798 speed: 810, 1799 title: 'Тахион', 1800 img: 'https://i.imgur.com/QiSt4iO.png', 1801 }, 1802 }, 1803 }, 1804 86075: { 1805 key: 22, 1806 name: 'Exercise No. 22', 1807 id: 86075, 1808 img: 'https://i.imgur.com/FYmhWf6.png', 1809 ranks: { 1810 speedDsc: [720, 630, 550, 480, 420, 330], 1811 330: { 1812 speed: 330, 1813 title: 'Гонщик', 1814 img: 'https://i.imgur.com/V3YDGVs.png', 1815 }, 1816 420: { 1817 speed: 420, 1818 title: 'Маньяк', 1819 img: 'https://i.imgur.com/tXZe8Xa.png', 1820 }, 1821 480: { 1822 speed: 480, 1823 title: 'Супермен', 1824 img: 'https://i.imgur.com/9lqMM1a.png', 1825 }, 1826 550: { 1827 speed: 550, 1828 title: 'Кибергонщик', 1829 img: 'https://i.imgur.com/LP3OPdH.png', 1830 }, 1831 630: { 1832 speed: 630, 1833 title: 'Экстракибер', 1834 img: 'https://i.imgur.com/P3k33bW.png', 1835 }, 1836 720: { 1837 speed: 720, 1838 title: 'Тахион', 1839 img: 'https://i.imgur.com/s1pJTMh.png', 1840 }, 1841 }, 1842 }, 1843 86176: { 1844 key: 23, 1845 name: 'Exercise No. 23', 1846 id: 86176, 1847 img: 'https://i.imgur.com/9mBI7BK.png', 1848 ranks: { 1849 speedDsc: [800, 700, 600, 500, 430, 330], 1850 330: { 1851 speed: 330, 1852 title: 'Гонщик', 1853 img: 'https://i.imgur.com/rHLcAKb.png', 1854 }, 1855 430: { 1856 speed: 430, 1857 title: 'Маньяк', 1858 img: 'https://i.imgur.com/Ol8DVHS.png', 1859 }, 1860 500: { 1861 speed: 500, 1862 title: 'Супермен', 1863 img: 'https://i.imgur.com/Rbk325V.png', 1864 }, 1865 600: { 1866 speed: 600, 1867 title: 'Кибергонщик', 1868 img: 'https://i.imgur.com/pkWbEaC.png', 1869 }, 1870 700: { 1871 speed: 700, 1872 title: 'Экстракибер', 1873 img: 'https://i.imgur.com/WxTgsKx.png', 1874 }, 1875 800: { 1876 speed: 800, 1877 title: 'Тахион', 1878 img: 'https://i.imgur.com/rKZk7N6.png', 1879 }, 1880 }, 1881 }, 1882 72680: { 1883 key: 24, 1884 name: 'Final exercise', 1885 id: 72680, 1886 img: 'https://i.imgur.com/ZZZoylt.png', 1887 ranks: { 1888 speedDsc: [900, 800, 700, 600, 500, 400], 1889 400: { 1890 speed: 400, 1891 title: 'Гонщик', 1892 img: 'https://i.imgur.com/GR6lbDb.png', 1893 }, 1894 500: { 1895 speed: 500, 1896 title: 'Маньяк', 1897 img: 'https://i.imgur.com/8KuaPDn.png', 1898 }, 1899 600: { 1900 speed: 600, 1901 title: 'Супермен', 1902 img: 'https://i.imgur.com/FMStHks.png', 1903 }, 1904 700: { 1905 speed: 700, 1906 title: 'Кибергонщик', 1907 img: 'https://i.imgur.com/yUnSKye.png', 1908 }, 1909 800: { 1910 speed: 800, 1911 title: 'Экстракибер', 1912 img: 'https://i.imgur.com/dEHI6cf.png', 1913 }, 1914 900: { 1915 speed: 900, 1916 title: 'Тахион', 1917 img: 'https://i.imgur.com/yBNSY1a.png', 1918 }, 1919 }, 1920 }, 1921 }; 1922 1923 const CRACK_IDS = Object 1924 .values(CRACK) 1925 .sort((a, b) => a.key - b.key) 1926 .map(item => item.id); 1927 1928 const HRUST_LIKE_DATA = { 1929 HRUST, 1930 CRACK, 1931 }; 1932 1933 const MODE_FINAL_RACE_LINK = { 1934 HRUST: 'normal', 1935 CRACK: 5539, 1936 }; 1937 1938 const MODE_IDS_MAP = { 1939 EK: EK_IDS, 1940 AEK: AEK_IDS, 1941 SX: SX_IDS, 1942 SUM: SUMMARY_IDS, 1943 HRUST: HRUST_IDS, 1944 CRACK: CRACK_IDS, 1945 }; 1946 1947 const TOWER_SVG = `<svg fill="#0000004d" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 1948 width="1em" height="1em" viewBox="0 0 45.436 45.436" 1949 xml:space="preserve"> 1950 <g> 1951 <path d="M36.316,14.393l4.424-7.08c0.17-0.271,0.25-0.584,0.25-0.905V1.684C40.99,0.741,40.245,0,39.301,0h-4.494 1952 c-0.943,0-1.739,0.741-1.739,1.684V4.22c0,0.387-0.281,0.712-0.668,0.712h-4.532c-0.387,0-0.704-0.325-0.704-0.712V1.684 1953 C27.164,0.741,26.403,0,25.459,0h-5.484c-0.943,0-1.705,0.741-1.705,1.684V4.22c0,0.387-0.316,0.712-0.703,0.712h-4.532 1954 c-0.387,0-0.669-0.325-0.669-0.712V1.684C12.367,0.741,11.572,0,10.628,0H6.134C5.191,0,4.446,0.741,4.446,1.684v4.724 1955 c0,0.32,0.08,0.634,0.25,0.905l4.423,7.08c0.17,0.271,0.259,0.584,0.259,0.904v19.307c0,0.6-0.319,1.155-0.833,1.464l-3.278,1.971 1956 c-0.514,0.309-0.821,0.864-0.821,1.465v4.246c0,0.944,0.744,1.686,1.688,1.686h33.167c0.942,0,1.688-0.741,1.688-1.686v-4.246 1957 c0-0.601-0.305-1.156-0.818-1.465l-3.287-1.97c-0.514-0.31-0.828-0.864-0.828-1.464V15.297 1958 C36.057,14.978,36.146,14.664,36.316,14.393z M27.163,24.426c0,0.695-0.56,1.281-1.255,1.281h-6.381 1959 c-0.695,0-1.256-0.586-1.256-1.281v-5.483c0-2.417,1.957-4.369,4.374-4.369h0.145c2.418,0,4.373,1.952,4.373,4.369V24.426z"/> 1960 </g> 1961 </svg>`; 1962 1963 const EXERCISE_SVG = `<svg version="1.0" xmlns="http://www.w3.org/2000/svg" 1964 width="1em" height="1em" viewBox="0 0 1280.000000 1216.000000" 1965 preserveAspectRatio="xMidYMid meet"> 1966 <g transform="translate(0.000000,1216.000000) scale(0.100000,-0.100000)" 1967 fill="#0000004d" stroke="none"> 1968 <path d="M8335 12149 c-22 -4 -82 -15 -133 -24 -462 -78 -879 -407 -1096 -866 1969 -229 -483 -222 -1030 19 -1514 63 -127 186 -306 254 -369 32 -31 41 -56 18 1970 -56 -7 0 -268 68 -580 150 -312 83 -580 150 -595 150 -25 0 -1858 -493 -1867 1971 -502 -3 -2 -169 -368 -370 -814 -325 -720 -383 -841 -525 -1096 -88 -158 -175 1972 -313 -194 -345 -19 -32 -31 -61 -28 -65 15 -15 656 -157 666 -148 6 5 299 355 1973 651 779 352 423 648 773 657 776 21 8 478 -102 496 -119 10 -10 -24 -183 -183 1974 -917 -163 -756 -555 -2574 -570 -2645 -5 -25 -2081 -1984 -2102 -1984 -8 0 1975 -511 369 -1118 819 l-1105 820 -302 -149 c-167 -82 -304 -155 -305 -162 -2 -7 1976 46 -105 105 -218 l108 -205 1028 -1342 c565 -739 1032 -1343 1037 -1343 6 0 1977 893 362 1972 804 l1962 803 1004 262 c553 144 1014 261 1024 259 16 -2 119 1978 -255 568 -1388 301 -762 551 -1391 556 -1397 5 -8 115 -26 288 -48 154 -20 1979 292 -40 307 -45 24 -8 29 -6 33 13 11 48 115 769 115 799 0 49 -508 3350 -517 1980 3359 -5 4 -530 99 -1168 210 -638 111 -1165 207 -1172 213 -8 8 112 338 484 1981 1326 272 723 500 1321 507 1328 10 10 236 -76 1137 -434 696 -276 1129 -442 1982 1136 -437 7 4 415 548 908 1208 l896 1199 162 88 c89 48 165 95 169 105 10 23 1983 119 565 115 569 -10 10 -501 234 -508 232 -4 -2 -510 -415 -1124 -918 -614 1984 -503 -1122 -916 -1130 -918 -13 -3 -1387 907 -1399 927 -9 14 4 20 96 37 299 1985 55 570 203 789 430 485 503 605 1278 299 1920 -81 169 -178 303 -319 444 -229 1986 229 -487 367 -774 415 -51 9 -114 20 -140 25 -57 12 -185 11 -242 -1z"/> 1987 </g> 1988 </svg> 1989 `; 1990 1991 const NUMBER_OF_MS_IN_ONE_SECOND = 1000; 1992 const NUMBER_OF_SECONDS_IN_ONE_MINUTE = 60; 1993 const NUMBER_OF_MINUTES_IN_ONE_HOUR = 60; 1994 const NUMBER_OF_SECONDS_IN_ONE_HOUR = NUMBER_OF_SECONDS_IN_ONE_MINUTE * NUMBER_OF_MINUTES_IN_ONE_HOUR; 1995 1996 const REPORT_FORMAT = { 1997 FORUM: 'FORUM', 1998 MD: 'MD', 1999 }; 2000 2001 const TYPES = { 2002 EK: 'phrases', 2003 AEK: 'phrases', 2004 SX: 'phrases', 2005 BOOK: 'book', 2006 }; 2007 2008 const NAME_OF_EXERCISES = { 2009 EK: 'УК.', 2010 AEK: 'доп.УК.', 2011 SX: 'SX.', 2012 } 2013 2014 const FILTER_MODES = { 2015 EK: 'EK', 2016 AEK: 'AEK', 2017 SX: 'SX', 2018 BOOK: 'BOOK', 2019 AWARD: 'AWARD', 2020 AWARDSORT: 'AWARDSORT', 2021 SUM: 'SUM', 2022 HRUST: 'HRUST', 2023 CRACK: 'CRACK', 2024 }; 2025 2026 const EXERCISES_TITLE = { 2027 EK: 'Башня Кощея (УК 1-364)', 2028 AEK: 'Виртуальная Башня (доп.УК 1-100)', 2029 SX: 'Virtual Tower (SX 1-364)', 2030 BOOK: 'Книжная полка', 2031 SUM: 'Сводка', 2032 }; 2033 2034 const BUTTON_TXT = { 2035 AWARD_MD: 'Награды (md)', 2036 AWARDSORT_MD: 'Награды сорт (md)', 2037 SUM_FORUM: 'Сводка (forum)', 2038 SUM_MD: 'Сводка (md)', 2039 BOOK_FORUM: 'Книги (forum)', 2040 BOOK_MD: 'Книги (md)', 2041 HRUST: 'HRUST (md)', 2042 CRACK: 'Crack (md)', 2043 EK_FORUM: 'УК (forum)', 2044 EK_MD: 'УК (md)', 2045 AEK_FORUM: 'доп.УК (forum)', 2046 AEK_MD: 'доп.УК (md)', 2047 SX_FORUM: 'SX (forum)', 2048 SX_MD: 'SX (md)', 2049 }; 2050 2051 const EXERCISES_BUTTON_ICON = { 2052 BOOK: 'icon-bookmark', 2053 AWARD: 'icon-trophy', 2054 AWARDSORT: 'icon-trophy', 2055 SUM: 'icon-signup', 2056 }; 2057 2058 const EXERCISES_BUTTON_SVG = { 2059 EK: TOWER_SVG, 2060 AEK: TOWER_SVG, 2061 SX: TOWER_SVG, 2062 HRUST: EXERCISE_SVG, 2063 CRACK: EXERCISE_SVG, 2064 }; 2065 2066 const NUMBER_OF_EXERCISES = { 2067 EK: EK_IDS.length, 2068 AEK: AEK_IDS.length, 2069 SX: SX_IDS.length, 2070 }; 2071 2072 const RANKS = { 2073 0: 'Новичок', 2074 1: 'Любитель', 2075 2: 'Таксист', 2076 3: 'Профи', 2077 4: 'Гонщик', 2078 5: 'Маньяк', 2079 6: 'Супермен', 2080 7: 'Кибергонщик', 2081 8: 'Экстракибер', 2082 9: 'Тахион', 2083 10: 'Гиперион', 2084 11: 'Варп', 2085 }; 2086 2087 const RANK_COLOR = { 2088 'Новичок': '#8d8d8d', 2089 'Любитель': '#4f9a97', 2090 'Таксист': '#187818', 2091 'Профи': '#8c8100', 2092 'Гонщик': '#ba5800', 2093 'Маньяк': '#bc0143', 2094 'Супермен': '#5e0b9e', 2095 'Кибергонщик': '#2e32ce', 2096 'Экстракибер': '#061956', 2097 'Тахион': '#45161c', 2098 'Гиперион': '#ff4500', 2099 'Варп': '#bc57af', 2100 }; 2101 2102 const VOCAB_PREFIX = 'voc-'; 2103 const VOCAB_COLOR = '#524ca7'; 2104 2105 const BOOK_RANK = { 2106 0: 'Иное', 2107 100: 'Бронзовая книга', 2108 300: 'Серебряная книга', 2109 1000: 'Золотая книга', 2110 }; 2111 2112 const BOOK_CATEGORY_DSC = [1000, 300, 100, 0]; 2113 2114 const BOOK_RANK_COLOR = { 2115 'Золотая книга': '#ffd700', 2116 'Серебряная книга': '#a8a9ad', 2117 'Бронзовая книга': '#cd7f32', 2118 'Иное': VOCAB_COLOR, 2119 }; 2120 2121 const BOOK_RANK_IMG = { 2122 'Золотая книга': { 2123 title: 'Золотая книга - от 1 000 000 символов', 2124 url: 'https://klavogonki.ru/wiki/images/6/67/Kniga3.png', 2125 }, 2126 'Серебряная книга': { 2127 title: 'Серебряная книга - от 300 000 символов', 2128 url: 'https://klavogonki.ru/wiki/images/8/85/Kniga2.png', 2129 }, 2130 'Бронзовая книга': { 2131 title: 'Бронзовая книга - от 100 000 символов', 2132 url: 'https://klavogonki.ru/wiki/images/6/67/Kniga1.png', 2133 }, 2134 'Иное': { 2135 title: 'Книга - до 100 000 символов', 2136 url: 'https://i.imgur.com/uoEcwIZ.png', 2137 }, 2138 }; 2139 2140 const AWARD_CATEGORY_DSC = [100000, 50000, 20000, 10000, 5000, 2000, 1000, 300]; 2141 2142 const AWARD = { 2143 300: 'Медаль за 300 текстов', 2144 1000: 'Звезда за 1 000 текстов', 2145 2000: 'Кубок за 2 000 текстов', 2146 5000: 'Корона за 5 000 текстов', 2147 10000: 'Золотой шлем за 10 000 текстов', 2148 20000: 'Бриллиантовый шлем за 20 000 текстов', 2149 50000: 'Графеновый шлем за 50 000 текстов', 2150 100000: 'Клаворуль за 100 000 текстов', 2151 }; 2152 2153 const AWARD_IMG = { 2154 300: 'https://klavogonki.ru/wiki/images/4/49/Award1.png', 2155 1000: 'https://klavogonki.ru/wiki/images/a/a9/Award2.png', 2156 2000: 'https://klavogonki.ru/wiki/images/c/cd/Award3.png', 2157 5000: 'https://klavogonki.ru/wiki/images/1/17/Award4.png', 2158 10000: 'https://klavogonki.ru/wiki/images/d/de/Award5.png', 2159 20000: 'https://klavogonki.ru/wiki/images/a/aa/Award6.png', 2160 50000: 'https://klavogonki.ru/wiki/images/b/bb/Award7.png', 2161 100000: 'https://klavogonki.ru/wiki/images/b/bb/Award8.png', 2162 }; 2163 2164 const GAME_TYPES = { 2165 normal: 'Обычный', 2166 noerror: 'Безошибочный', 2167 sprint: 'Спринт', 2168 abra: 'Абракадабра', 2169 referats: 'Яндекс.Рефераты', 2170 chars: 'Буквы', 2171 digits: 'Цифры', 2172 marathon: 'Марафон', 2173 }; 2174 2175 const GAME_COLOR = { 2176 normal: '#000000', 2177 noerror: '#4692aa', 2178 sprint: '#833f3a', 2179 abra: '#3d4856', 2180 referats: '#698725', 2181 chars: '#b55900', 2182 digits: '#777777', 2183 marathon: '#d43e68', 2184 }; 2185 2186 const FETCH_HIDDEN_PROFILE = false; 2187 2188 const BOOK_LIST_DONT_HIDE_NUMBER = 10; 2189 2190 const NUMBER_OF_RACES_FOR_FULL_LAP = 10; 2191 const NUMBER_OF_TOP_TO_SHOW = 5; 2192 2193 const PLACES_AFTER_POINT_TO_ROUND = 0; 2194 2195 async function handler(mode, format, login, rank) { 2196 await copyToClipboard(''); 2197 2198 switch (mode) { 2199 case FILTER_MODES.BOOK: { 2200 await handleBooks(mode, format, login, rank); 2201 return; 2202 } 2203 2204 case FILTER_MODES.EK: 2205 case FILTER_MODES.AEK: 2206 case FILTER_MODES.SX: { 2207 await handleEKLikeExercises(mode, format, login, rank); 2208 return; 2209 } 2210 2211 case FILTER_MODES.AWARD: { 2212 await handleAwards(mode); 2213 return; 2214 } 2215 2216 case FILTER_MODES.AWARDSORT: { 2217 await handleAwards(mode, true); 2218 return; 2219 } 2220 2221 case FILTER_MODES.SUM: { 2222 await handleSummary(mode, format, login, rank); 2223 return; 2224 } 2225 2226 case FILTER_MODES.HRUST: 2227 case FILTER_MODES.CRACK: { 2228 await handleHrustLike(mode); 2229 return; 2230 } 2231 2232 default: { 2233 throw new Error(`Invalid report mode: ${mode}`); 2234 } 2235 } 2236 } 2237 2238 async function handleBooks(mode, format, login, rank) { 2239 const userId = getUserId(); 2240 const statsOverview = await fetchStatsOverview(userId, mode); 2241 2242 if (statsOverview.err) { 2243 await copyToClipboard(statsOverview.err); 2244 return; 2245 } 2246 2247 const stats = filterStats(statsOverview.gametypes, mode); 2248 2249 if (!stats.length) { 2250 const noBooksTxt = createNoBooksTxt(format); 2251 await copyToClipboard(noBooksTxt); 2252 return; 2253 } 2254 2255 const userName = login; 2256 const userRank = rank; 2257 const title = getTitle(mode); 2258 2259 const totalBooks = stats.length; 2260 const sortedByLengthBooksDsc = sortBooksByLengthDsc(stats); 2261 const booksByRank = calculateBooksByRank(sortedByLengthBooksDsc); 2262 2263 const totalSeconds = calculateTotalSeconds(sortedByLengthBooksDsc); 2264 const totalHours = calculateTotalHours(totalSeconds); 2265 2266 const booksList = getBooksList(sortedByLengthBooksDsc); 2267 2268 const data = { 2269 title, 2270 userId, 2271 userName, 2272 userRank, 2273 totalBooks, 2274 totalHours, 2275 booksByRank, 2276 booksList, 2277 }; 2278 const booksReportTxt = createBooksReportTxt(format, data); 2279 await copyToClipboard(booksReportTxt); 2280 } 2281 2282 async function handleEKLikeExercises(mode, format, login, rank) { 2283 const userId = getUserId(); 2284 const statsOverview = await fetchStatsOverview(userId, mode); 2285 2286 if (statsOverview.err) { 2287 await copyToClipboard(statsOverview.err); 2288 return; 2289 } 2290 2291 const stats = filterStats(statsOverview.gametypes, mode); 2292 2293 const userName = login; 2294 const userRank = rank; 2295 const title = getTitle(mode); 2296 2297 const isAllExercisesCompleted = checkAllExercisesCompleted(stats, mode); 2298 if (!isAllExercisesCompleted) { 2299 if (stats.length > NUMBER_OF_EXERCISES[mode]) { 2300 console.log('Wrong filters'); 2301 return; 2302 } 2303 2304 const notCompltetedTxt = createNotCompletedTxt(format, stats, mode); 2305 await copyToClipboard(notCompltetedTxt); 2306 return; 2307 } 2308 2309 const sortedByBestSpeedStatsAsc = sortStatsByBestSpeedAsc(stats); 2310 2311 const towerRank = calculateTowerRank(sortedByBestSpeedStatsAsc); 2312 const laps = calculateLaps(sortedByBestSpeedStatsAsc); 2313 2314 const recordsMean = calculateRecordsMean(sortedByBestSpeedStatsAsc); 2315 const recordsMedian = calculateRecordsMedian(sortedByBestSpeedStatsAsc); 2316 2317 const recordsByRank = calculateRecordsByRank(sortedByBestSpeedStatsAsc); 2318 2319 const bestTop = getBest(sortedByBestSpeedStatsAsc); 2320 const worstTop = getWorst(sortedByBestSpeedStatsAsc); 2321 2322 const totalNumberOfRaces = calculateTotalNumberOfRaces(sortedByBestSpeedStatsAsc); 2323 const avgSpeed = calculateAvgSpeed(sortedByBestSpeedStatsAsc); 2324 2325 const totalSeconds = calculateTotalSeconds(sortedByBestSpeedStatsAsc); 2326 const totalHours = calculateTotalHours(totalSeconds); 2327 2328 const data = { 2329 title, 2330 userName, 2331 userRank, 2332 towerRank, 2333 laps, 2334 recordsMean, 2335 recordsMedian, 2336 recordsByRank, 2337 bestTop, 2338 worstTop, 2339 totalNumberOfRaces, 2340 avgSpeed, 2341 totalHours, 2342 }; 2343 const reportTxt = createEKLikeExercisesReportTxt(format, data); 2344 await copyToClipboard(reportTxt); 2345 } 2346 2347 async function handleAwards(mode, sort = false) { 2348 const userId = getUserId(); 2349 const statsOverview = await fetchStatsOverview(userId, mode); 2350 2351 if (statsOverview.err) { 2352 await copyToClipboard(statsOverview.err); 2353 return; 2354 } 2355 2356 const awardsList = createAwardsList(userId, statsOverview.gametypes, sort); 2357 const awardsReportTxt = createAwardsReportMD(awardsList); 2358 await copyToClipboard(awardsReportTxt); 2359 } 2360 2361 async function handleSummary(mode, format, login, rank) { 2362 const userId = getUserId(); 2363 const summaryStats = await fetchSummaryStats(userId); 2364 2365 if (summaryStats.err) { 2366 await copyToClipboard(summaryStats.err); 2367 return; 2368 } 2369 2370 const userName = login; 2371 const userRank = rank; 2372 const title = getTitle(mode); 2373 2374 2375 const data = { 2376 userName, 2377 userRank, 2378 title, 2379 summaryStats, 2380 }; 2381 const reportTxt = createSummaryReportTxt(format, data); 2382 await copyToClipboard(reportTxt); 2383 } 2384 2385 async function handleHrustLike(mode) { 2386 const userId = getUserId(); 2387 const statsOverview = await fetchStatsOverview(userId, mode); 2388 2389 if (statsOverview.err) { 2390 await copyToClipboard(statsOverview.err); 2391 return; 2392 } 2393 2394 const stats = filterStats(statsOverview.gametypes, mode); 2395 const exercisesList = createExercisesList(stats, mode); 2396 const exercisesReportTxt = createExercisesReportMD(exercisesList, mode); 2397 await copyToClipboard(exercisesReportTxt); 2398 } 2399 2400 function getUserId() { 2401 const delimeter = '/'; 2402 const startIdx = 2; 2403 const lastIdx = window.location.hash.indexOf(delimeter, startIdx); 2404 const userId = window.location.hash.slice(startIdx, lastIdx); 2405 return userId; 2406 } 2407 2408 async function fetchStatsOverview(userId, mode) { 2409 const STATS_OVERVIEW_URL = `${window.location.protocol}//klavogonki.ru/api/profile/get-stats-overview?userId=${userId}`; 2410 const response = await fetch(STATS_OVERVIEW_URL); 2411 const statsOverview = await response.json(); 2412 if (!statsOverview.ok || !statsOverview.gametypes) { 2413 if (FETCH_HIDDEN_PROFILE && statsOverview.err === 'permission blocked' && NAME_OF_EXERCISES[mode]) { 2414 const statsFromHiddenProfile = await fetchExercisesStatsFromHiddenProfile(userId, mode); 2415 return statsFromHiddenProfile; 2416 } 2417 console.log('Could not load Stats Overview'); 2418 return { err: 'Ошибка при получении статистики игрока' }; 2419 } 2420 return statsOverview; 2421 } 2422 2423 async function fetchSummaryStats(userId) { 2424 const gameTypes = SUMMARY_IDS.map(id => GAME_TYPES[id] ? id : `${VOCAB_PREFIX}${id}`); 2425 const result = []; 2426 for (const gameType of gameTypes) { 2427 const stats = await fetchGameStat(userId, gameType); 2428 if (!stats.ok) { 2429 if (stats.err === 'invalid gametype') { 2430 console.log('there is no stats for: ', gameType); 2431 } else { 2432 return { err: 'Ошибка при получении статистики игрока' }; 2433 } 2434 } else { 2435 result.push({ 2436 type: gameType, 2437 name: stats.gametype?.name, 2438 num_races: stats.info?.num_races || 'n/a', 2439 best_speed: stats.info?.best_speed || 'n/a', 2440 qual: stats.info?.qual || 'n/a', 2441 date: stats.best_speed_post?.message?.speed === stats.info?.best_speed ? new Date(stats.best_speed_post?.date.sec * NUMBER_OF_MS_IN_ONE_SECOND).toLocaleDateString() : 'n/a', 2442 }); 2443 } 2444 } 2445 return result; 2446 } 2447 2448 async function fetchGameStat(userId, gameType) { 2449 const url = createGameTypeURL(userId, gameType); 2450 const response = await fetch(url); 2451 const stats = await response.json(); 2452 return stats; 2453 } 2454 2455 async function fetchExercisesStatsFromHiddenProfile(userId, mode) { 2456 const vocabIds = MODE_IDS_MAP[mode]; 2457 const results = { 2458 ok: 1, 2459 gametypes: {}, 2460 }; 2461 for (const vocabId of vocabIds) { 2462 const gameType = `${VOCAB_PREFIX}${vocabId}`; 2463 const popupURL = createPopupURL(userId, gameType); 2464 const vocabData = await fetchFromPopup(popupURL); 2465 vocabData.id = vocabId; 2466 if (vocabData.num_races) { 2467 results.gametypes[gameType] = vocabData; 2468 } 2469 } 2470 return results; 2471 } 2472 2473 function createGameTypeURL(userId, gameType) { 2474 return `${window.location.protocol}//klavogonki.ru/api/profile/get-stats-details?userId=${userId}&gametype=${gameType}`; 2475 } 2476 2477 function createPopupURL(userId, gameType) { 2478 return `${window.location.protocol}//klavogonki.ru/ajax/profile-popup?user_id=${userId}&gametype=${gameType}`; 2479 } 2480 2481 async function fetchFromPopup(url) { 2482 const response = await fetch(url); 2483 const gameStatsStr = await response.text(); 2484 const dom = new DOMParser().parseFromString(gameStatsStr, 'text/html'); 2485 const name = getNameFromPopupDom(dom); 2486 const runs = getRunsFromPopupDom(dom); 2487 const numRaces = runs.texts; 2488 const totalSeconds = calculateTotalSecondsFromRuns(runs); 2489 const avgSpeed = getAvgSpeedFromPopupDom(dom); 2490 const bestSpeed = getBestSpeedFromPopupDom(dom); 2491 const data = { 2492 name, 2493 num_races: numRaces, 2494 info: { 2495 num_races: numRaces, 2496 avg_speed: avgSpeed, 2497 best_speed: bestSpeed, 2498 haul: totalSeconds, 2499 }, 2500 }; 2501 return data; 2502 } 2503 2504 function getNameFromPopupDom(dom) { 2505 const nameSpan = dom.querySelector('td.gametype-title span'); 2506 const name = nameSpan.textContent; 2507 const bracketsIdx = name.indexOf('«'); 2508 if (bracketsIdx === -1) { 2509 return name; 2510 } 2511 const nameWithoutBrackets = name.slice(bracketsIdx + 1, -1); 2512 return nameWithoutBrackets; 2513 } 2514 2515 function getRunsFromPopupDom(dom) { 2516 const tds = dom.querySelectorAll('td'); 2517 2518 let runsTd; 2519 for (const td of tds) { 2520 if (td.innerText.includes('минут') && td.innerText.includes('текст')) { 2521 runsTd = td; 2522 break; 2523 } 2524 } 2525 const runsStr = runsTd.textContent; 2526 2527 const runs = parseRunsStr(runsStr); 2528 return runs; 2529 } 2530 2531 function parseRunsStr(str) { 2532 const [textsStr, time1Str, time2Str] = str.split(' \n'); 2533 2534 let hours = 0; 2535 let minutes = 0; 2536 let texts = 0; 2537 2538 if (time1Str.includes('час')) { 2539 hours = Number(time1Str.match(/\d+/)[0]); 2540 minutes = Number(time2Str.match(/\d+/)[0]); 2541 } else if (time1Str.includes('минут')) { 2542 minutes = Number(time1Str.match(/\d+/)[0]); 2543 } else { 2544 console.log('Time parsing error'); 2545 } 2546 2547 texts = Number(textsStr.match(/\d+/)[0]); 2548 2549 const runsObj = { 2550 hours, 2551 minutes, 2552 texts, 2553 } 2554 return runsObj; 2555 } 2556 2557 function calculateTotalSecondsFromRuns(runs) { 2558 return runs.hours * NUMBER_OF_SECONDS_IN_ONE_HOUR + runs.minutes * NUMBER_OF_SECONDS_IN_ONE_MINUTE; 2559 } 2560 2561 function getAvgSpeedFromPopupDom(dom) { 2562 const tds = dom.querySelectorAll('td'); 2563 const speedTds = Array.from(tds) 2564 .map(td => td.innerText) 2565 .filter(text => text.includes('зн/мин')); 2566 2567 const avgSpeedTxt = speedTds[1].replace('зн/мин', '').trim(); 2568 const avgSpeed = parseInt(avgSpeedTxt); 2569 return avgSpeed; 2570 } 2571 2572 function getBestSpeedFromPopupDom(dom) { 2573 const tds = dom.querySelectorAll('td'); 2574 const speedTds = Array.from(tds) 2575 .map(td => td.innerText) 2576 .filter(text => text.includes('зн/мин')); 2577 2578 const bestSpeedTxt = speedTds[0].replace('зн/мин', '').trim(); 2579 const bestSpeed = parseInt(bestSpeedTxt); 2580 return bestSpeed; 2581 } 2582 2583 function filterStats(gameTypes, filterMode) { 2584 switch (filterMode) { 2585 case FILTER_MODES.EK: { 2586 return filterEK(gameTypes); 2587 } 2588 2589 case FILTER_MODES.AEK: { 2590 return filterAEK(gameTypes); 2591 } 2592 2593 case FILTER_MODES.SX: { 2594 return filterSX(gameTypes); 2595 } 2596 2597 case FILTER_MODES.BOOK: { 2598 return filterBook(gameTypes); 2599 } 2600 2601 case FILTER_MODES.HRUST: { 2602 return filterHrust(gameTypes); 2603 } 2604 2605 case FILTER_MODES.CRACK: { 2606 return filterCrack(gameTypes); 2607 } 2608 2609 default: { 2610 return console.log(`Wrong filter mode: ${filterMode}`); 2611 } 2612 } 2613 } 2614 2615 function filterEK(gameTypes) { 2616 const filtered = Object.values(gameTypes).filter(gameType => { 2617 return EK_IDS.includes(gameType.id); 2618 }); 2619 return filtered; 2620 } 2621 2622 function filterAEK(gameTypes) { 2623 const filtered = Object.values(gameTypes).filter(gameType => { 2624 return AEK_IDS.includes(gameType.id); 2625 }); 2626 return filtered; 2627 } 2628 2629 function filterSX(gameTypes) { 2630 const filtered = Object.values(gameTypes).filter(gameType => { 2631 return SX_IDS.includes(gameType.id); 2632 }); 2633 return filtered; 2634 } 2635 2636 function filterBook(gameTypes) { 2637 const filtered = Object.values(gameTypes).filter(gameType => { 2638 return gameType.type === TYPES.BOOK && gameType.book_done; 2639 }); 2640 return filtered; 2641 } 2642 2643 function filterHrust(gameTypes) { 2644 const filtered = Object.values(gameTypes).filter(gameType => { 2645 return HRUST_IDS.includes(gameType.id); 2646 }); 2647 return filtered; 2648 } 2649 2650 function filterCrack(gameTypes) { 2651 const filtered = Object.values(gameTypes).filter(gameType => { 2652 return CRACK_IDS.includes(gameType.id); 2653 }); 2654 return filtered; 2655 } 2656 2657 function sortBooksByLengthDsc(books) { 2658 const sortedBooks = books.sort((a, b) => { 2659 return b.symbols - a.symbols; 2660 }); 2661 return sortedBooks; 2662 } 2663 2664 function calculateBooksByRank(booksDsc) { 2665 const booksByRank = booksDsc.reduce((map, book) => { 2666 const rank = calculateBookRank(book) 2667 if (!map.get(rank)) { 2668 map.set(rank, 1); 2669 } else { 2670 map.set(rank, map.get(rank) + 1); 2671 } 2672 return map; 2673 }, new Map()); 2674 return booksByRank; 2675 } 2676 2677 function calculateBookRank(book) { 2678 const bookCategory = getBookCategory(book); 2679 const rank = BOOK_RANK[bookCategory]; 2680 return rank; 2681 } 2682 2683 function getBookCategory(book) { 2684 const charsK = Math.round(book.symbols / 1000); 2685 for (const category of BOOK_CATEGORY_DSC) { 2686 if (charsK >= category) { 2687 return category; 2688 } 2689 } 2690 return BOOK_CATEGORY_DSC.at(-1); 2691 } 2692 2693 function getBooksList(sortedByLengthBooksDsc) { 2694 const books = sortedByLengthBooksDsc.map(book => { 2695 return ({ 2696 name: book.name, 2697 rank: calculateBookRank(book), 2698 symbols: book.symbols, 2699 id: book.id, 2700 }); 2701 }); 2702 return books; 2703 } 2704 2705 function checkAllExercisesCompleted(stats, mode) { 2706 return stats.length === NUMBER_OF_EXERCISES[mode]; 2707 } 2708 2709 function createNoBooksTxt(format) { 2710 switch (format) { 2711 case REPORT_FORMAT.FORUM: { 2712 return createNoBooksTxtForum(); 2713 } 2714 2715 case REPORT_FORMAT.MD: { 2716 return createNoBooksTxtMD(); 2717 } 2718 2719 default: { 2720 throw new Error('Unknown report format: ', format); 2721 } 2722 } 2723 } 2724 2725 function createNoBooksTxtForum() { 2726 return '[color="#f00"][b]Не набрано ни одной книги![/b][/color]\n'; 2727 } 2728 2729 function createNoBooksTxtMD() { 2730 return createMDSectionHeader('Не набрано ни одной книги!'); 2731 } 2732 2733 function createNotCompletedTxt(format, stats, mode) { 2734 switch (format) { 2735 case REPORT_FORMAT.FORUM: { 2736 return createNotCompletedTxtForum(stats, mode); 2737 } 2738 2739 case REPORT_FORMAT.MD: { 2740 return createNotCompletedTxtMD(stats, mode); 2741 } 2742 2743 default: { 2744 throw new Error('Unknown report format: ', format); 2745 } 2746 } 2747 } 2748 2749 function createNotCompletedTxtForum(stats, mode) { 2750 let txt = '[color="#f00"][b]Не все упражнения были пройдены хотя бы один раз![/b][/color]\n'; 2751 txt += `Пройдено [b]${stats.length}[/b] из [b]${NUMBER_OF_EXERCISES[mode]}[/b] ${NAME_OF_EXERCISES[mode]}\n`; 2752 return txt; 2753 } 2754 2755 function createNotCompletedTxtMD(stats, mode) { 2756 let txt = ''; 2757 txt += createMDSectionHeader('Не все упражнения были пройдены хотя бы один раз!'); 2758 txt += `Пройдено **${stats.length}** из **${NUMBER_OF_EXERCISES[mode]}** ${NAME_OF_EXERCISES[mode]}\n`; 2759 return txt; 2760 } 2761 2762 function sortStatsByBestSpeedAsc(stats) { 2763 const sortedStats = stats.sort((a, b) => { 2764 return a.info.best_speed - b.info.best_speed; 2765 }); 2766 return sortedStats; 2767 } 2768 2769 function getTitle(mode) { 2770 return EXERCISES_TITLE[mode]; 2771 } 2772 2773 function calculateTowerRank(statsAsc) { 2774 const minSpeed = statsAsc[0].info.best_speed; 2775 const minSpeedCategory = Math.floor(minSpeed / 100); 2776 const rank = RANKS[minSpeedCategory]; 2777 return rank; 2778 } 2779 2780 function calculateLaps(stats) { 2781 const minRaces = stats.reduce((min, exercise) => { 2782 return Math.min(min, exercise.info.num_races); 2783 }, Infinity); 2784 const laps = Math.floor(minRaces / NUMBER_OF_RACES_FOR_FULL_LAP); 2785 return laps; 2786 } 2787 2788 function calculateRecordsMean(statsAsc) { 2789 const mean = statsAsc.reduce((sum, exercise) => { 2790 return sum + exercise.info.best_speed; 2791 }, 0) / statsAsc.length; 2792 const roundedMean = parseFloat(mean.toFixed(PLACES_AFTER_POINT_TO_ROUND)); 2793 return roundedMean; 2794 } 2795 2796 function calculateRecordsMedian(statsAsc) { 2797 const middleIdx = Math.floor(statsAsc.length / 2); 2798 let median; 2799 if (statsAsc.length % 2 === 0) { 2800 median = (statsAsc[middleIdx - 1].info.best_speed + statsAsc[middleIdx].info.best_speed) / 2; 2801 } else { 2802 median = statsAsc[middleIdx].info.best_speed; 2803 } 2804 const roundedMedian = parseFloat(median.toFixed(PLACES_AFTER_POINT_TO_ROUND)); 2805 return roundedMedian; 2806 } 2807 2808 function calculateRecordsByRank(stats) { 2809 const recordsByRank = stats.reduce((map, exercise) => { 2810 const speedCategory = Math.floor(exercise.info.best_speed / 100); 2811 const rank = RANKS[speedCategory]; 2812 if (!map.get(rank)) { 2813 map.set(rank, 1); 2814 } else { 2815 map.set(rank, map.get(rank) + 1); 2816 } 2817 return map; 2818 }, new Map()); 2819 return recordsByRank; 2820 } 2821 2822 function getBest(statsAsc) { 2823 const best = statsAsc.slice(-NUMBER_OF_TOP_TO_SHOW).reverse().map(exercise => { 2824 return ({ 2825 name: exercise.name, 2826 speed: exercise.info.best_speed, 2827 }); 2828 }); 2829 return best; 2830 } 2831 2832 function getWorst(statsAsc) { 2833 const worst = statsAsc.slice(0, NUMBER_OF_TOP_TO_SHOW).map(exercise => { 2834 return ({ 2835 name: exercise.name, 2836 speed: exercise.info.best_speed, 2837 }); 2838 }); 2839 return worst; 2840 } 2841 2842 function calculateTotalNumberOfRaces(stats) { 2843 const totalNumberOfRaces = stats.reduce((sum, exercise) => { 2844 return sum + exercise.info.num_races; 2845 }, 0); 2846 return totalNumberOfRaces; 2847 } 2848 2849 function calculateAvgSpeed(stats) { 2850 const avgSpeed = stats.reduce((sum, exercise) => { 2851 return sum + exercise.info.avg_speed; 2852 }, 0) / stats.length; 2853 const roundedAvg = parseFloat(avgSpeed.toFixed(PLACES_AFTER_POINT_TO_ROUND)); 2854 return roundedAvg; 2855 } 2856 2857 function calculateTotalSeconds(stats) { 2858 const totalSeconds = stats.reduce((acc, exercise) => { 2859 return acc + exercise.info.haul; 2860 }, 0); 2861 return totalSeconds; 2862 } 2863 2864 function calculateTotalHours(seconds) { 2865 const totalHours = Math.round(seconds / NUMBER_OF_SECONDS_IN_ONE_HOUR); 2866 return totalHours; 2867 } 2868 2869 function createEKLikeExercisesReportTxt(format, data) { 2870 switch (format) { 2871 case REPORT_FORMAT.FORUM: { 2872 return createEKLikeExercisesReportForum(data); 2873 } 2874 2875 case REPORT_FORMAT.MD: { 2876 return createEKLikeExercisesReportMD(data); 2877 } 2878 2879 default: { 2880 throw new Error('Unknown report format', format); 2881 } 2882 } 2883 } 2884 2885 function createEKLikeExercisesReportForum({ 2886 title, 2887 userName, 2888 userRank, 2889 towerRank, 2890 laps, 2891 recordsMean, 2892 recordsMedian, 2893 recordsByRank, 2894 bestTop, 2895 worstTop, 2896 totalNumberOfRaces, 2897 avgSpeed, 2898 totalHours 2899 }) { 2900 let txt = ''; 2901 txt += createForumHeader(title); 2902 txt += `\n`; 2903 txt += `Ранг башни: [color="${RANK_COLOR[towerRank]}"][b]${towerRank}[/b][/color]\n`; 2904 txt += createForumUserInfo(userName, userRank); 2905 txt += `\n`; 2906 txt += `Количество полных кругов*: [b]${laps}[/b]\n`; 2907 txt += `\n`; 2908 txt += `Средняя скорость рекордов: [b]${recordsMean}[/b] зн/мин\n`; 2909 txt += `Медианная скорость рекордов: [b]${recordsMedian}[/b] зн/мин\n`; 2910 txt += `\n`; 2911 txt += `Общая средняя скорость: [b]${avgSpeed}[/b] зн/мин\n`; 2912 txt += `Общий пробег: [b]${totalNumberOfRaces}[/b]\n`; 2913 txt += `Общее время набора: [b]${totalHours}[/b] ч.\n`; 2914 txt += `\n`; 2915 txt += `Распределение рекордов по рангам:\n`; 2916 for (let [rank, counter] of recordsByRank) { 2917 txt += `[color="${RANK_COLOR[rank]}"]${rank}[/color]: [b]${counter}[/b]\n`; 2918 } 2919 txt += `\n`; 2920 txt += `Топ-5 лучших:\n`; 2921 for (let {name, speed} of bestTop) { 2922 txt += `[color="${VOCAB_COLOR}"]${name}[/color]: [b]${speed}[/b] зн/мин\n`; 2923 } 2924 txt += `\n`; 2925 txt += `Топ-5 худших:\n`; 2926 for (let {name, speed} of worstTop) { 2927 txt += `[color="${VOCAB_COLOR}"]${name}[/color]: [b]${speed}[/b] зн/мин\n`; 2928 } 2929 txt += `\n`; 2930 txt += `[size="1"][i]*1 круг = 10 заездов в каждом словаре[/i][/size]\n`; 2931 return txt; 2932 } 2933 2934 function createEKLikeExercisesReportMD({ 2935 title, 2936 userName, 2937 towerRank, 2938 laps, 2939 recordsMean, 2940 recordsMedian, 2941 recordsByRank, 2942 bestTop, 2943 worstTop, 2944 totalNumberOfRaces, 2945 avgSpeed, 2946 totalHours 2947 }) { 2948 let txt = ''; 2949 txt += createMDHeader(title); 2950 txt += `\n`; 2951 txt += `Ранг башни: **${towerRank}**\n`; 2952 txt += createMDUserInfo(userName); 2953 txt += `\n`; 2954 txt += `Количество полных кругов*: **${laps}**\n`; 2955 txt += `\n`; 2956 txt += `Средняя скорость рекордов: **${recordsMean}** зн/мин\n`; 2957 txt += `Медианная скорость рекордов: **${recordsMedian}** зн/мин\n`; 2958 txt += `\n`; 2959 txt += `Общая средняя скорость: **${avgSpeed}** зн/мин\n`; 2960 txt += `Общий пробег: **${totalNumberOfRaces}**\n`; 2961 txt += createMDTotalHours(totalHours); 2962 txt += `\n`; 2963 txt += createMDSectionHeader('Распределение рекордов по рангам'); 2964 let rankListRows = []; 2965 for (let [rank, counter] of recordsByRank) { 2966 rankListRows.push([rank, `**${counter}**`]); 2967 } 2968 txt += createMDTable({ 2969 header: ['Ранг', 'Кол-во'], 2970 align: ['left', 'center'], 2971 rows: rankListRows, 2972 }); 2973 txt += `\n`; 2974 txt += createMDSectionHeader('Топ-5 лучших'); 2975 let bestTopRows = []; 2976 for (let {name, speed} of bestTop) { 2977 bestTopRows.push([name, `**${speed}** зн/мин`]); 2978 } 2979 txt += createMDTable({ 2980 header: ['Упражнение', 'Рекорд'], 2981 align: ['left', 'center'], 2982 rows: bestTopRows, 2983 }); 2984 txt += `\n`; 2985 txt += createMDSectionHeader('Топ-5 худших'); 2986 let worstTopRows = []; 2987 for (let {name, speed} of worstTop) { 2988 worstTopRows.push([name, `**${speed}** зн/мин`]); 2989 } 2990 txt += createMDTable({ 2991 header: ['Упражнение', 'Рекорд'], 2992 align: ['left', 'center'], 2993 rows: worstTopRows, 2994 }); 2995 txt += `\n`; 2996 txt += createMDNote('1 круг = 10 заездов в каждом словаре'); 2997 return txt; 2998 } 2999 3000 function createBooksReportTxt(format, data) { 3001 switch (format) { 3002 case REPORT_FORMAT.FORUM: { 3003 return createBooksReportForum(data); 3004 } 3005 3006 case REPORT_FORMAT.MD: { 3007 return createBooksReportMD(data); 3008 } 3009 3010 default: { 3011 throw new Error('Unknown report format', format); 3012 } 3013 } 3014 } 3015 3016 function createBooksReportForum({ 3017 title, 3018 userId, 3019 userName, 3020 userRank, 3021 totalBooks, 3022 totalHours, 3023 booksByRank, 3024 booksList, 3025 }) { 3026 let txt = ''; 3027 txt += createForumHeader(title); 3028 txt += `\n`; 3029 txt += createForumUserInfo(userName, userRank); 3030 txt += `\n`; 3031 txt += createForumTotalBooks(totalBooks); 3032 txt += createForumTotalHours(totalHours); 3033 txt += `\n`; 3034 txt += `Распределение книг по размеру:\n`; 3035 for (let [rank, counter] of booksByRank) { 3036 txt += `[color="${BOOK_RANK_COLOR[rank]}"]${rank}[/color]: [b]${counter}[/b]\n`; 3037 } 3038 txt += `\n`; 3039 txt += `Список всех набранных книг:\n`; 3040 if (booksList.length > BOOK_LIST_DONT_HIDE_NUMBER) { 3041 txt += `[hide]\n`; 3042 } 3043 for (let {name, rank, id} of booksList) { 3044 const bookLink = createForumVocabStatsLink(userId, 'ссылка', id); 3045 txt += `[color="${BOOK_RANK_COLOR[rank]}"]${name}[/color]: ${bookLink}\n`; 3046 } 3047 if (booksList.length > BOOK_LIST_DONT_HIDE_NUMBER) { 3048 txt += `[/hide]\n`; 3049 } 3050 return txt; 3051 } 3052 3053 function createBooksReportMD({ 3054 title, 3055 userId, 3056 userName, 3057 totalBooks, 3058 totalHours, 3059 booksByRank, 3060 booksList, 3061 }) { 3062 let txt = ''; 3063 txt += createMDHeader(title); 3064 txt += '\n'; 3065 txt += createMDUserInfo(userName); 3066 txt += '\n'; 3067 txt += createMDTotalBooks(totalBooks); 3068 txt += createMDTotalHours(totalHours); 3069 txt += `\n`; 3070 txt += createMDSectionHeader('Распределение книг по размеру'); 3071 let bookTypeRows = []; 3072 for (let [rank, counter] of booksByRank) { 3073 const bookImg = createMDBookImg(rank); 3074 bookTypeRows.push([bookImg, rank, counter]); 3075 } 3076 txt += createMDTable({ 3077 header: [ '', 'Тип книги', 'Кол-во'], 3078 align: [ 'center', 'left', 'center' ], 3079 rows: bookTypeRows, 3080 }); 3081 txt += `\n`; 3082 txt += createMDSectionHeader('Список всех набранных книг'); 3083 let bookListRows = []; 3084 for (let {name, rank, symbols, id} of booksList) { 3085 const bookImg = createMDBookImg(rank); 3086 const bookLink = createMDVocabStatsLink(userId, name, id); 3087 bookListRows.push([bookImg, bookLink, symbols]); 3088 } 3089 txt += createMDTable({ 3090 header: [ '', 'Название', 'Кол-во символов'], 3091 align: [ 'center', 'left', 'center' ], 3092 rows: bookListRows, 3093 }); 3094 return txt; 3095 } 3096 3097 function createSummaryReportTxt(format, data) { 3098 switch (format) { 3099 case REPORT_FORMAT.FORUM: { 3100 return createSummaryReportForum(data); 3101 } 3102 3103 case REPORT_FORMAT.MD: { 3104 return createSummaryReportMD(data); 3105 } 3106 3107 default: { 3108 throw new Error('Unknown report format', format); 3109 } 3110 } 3111 } 3112 3113 function createSummaryReportForum({ 3114 userName, 3115 userRank, 3116 title, 3117 summaryStats, 3118 }) { 3119 let txt = ''; 3120 txt += createForumHeader(title); 3121 txt += `\n`; 3122 txt += createForumUserInfo(userName, userRank); 3123 txt += `\n`; 3124 for (const gameType of summaryStats) { 3125 txt += `[color="${GAME_COLOR[gameType.type] || VOCAB_COLOR}"]${gameType.name}[/color]\n`; 3126 txt += `Рекорд: [b]${gameType.best_speed}[/b] зн/мин\n`; 3127 txt += `Дата рекорда: [b]${gameType.date}[/b]\n`; 3128 txt += `Квалификация: [b]${gameType.qual}[/b] зн/мин\n`; 3129 txt += `Пробег: [b]${gameType.num_races}[/b]\n`; 3130 txt += `\n`; 3131 } 3132 return txt; 3133 } 3134 3135 function createSummaryReportMD({ 3136 userName, 3137 title, 3138 summaryStats, 3139 }) { 3140 let txt = ''; 3141 txt += createMDHeader(title); 3142 txt += `\n`; 3143 txt += createMDUserInfo(userName); 3144 txt += `\n`; 3145 let rankListRows = []; 3146 for (const gameType of summaryStats) { 3147 rankListRows.push([ 3148 gameType.name, 3149 `**${gameType.best_speed}**`, 3150 gameType.date, 3151 `${gameType.qual}`, 3152 gameType.num_races, 3153 ]); 3154 } 3155 txt += createMDTable({ 3156 header: ['Режим', 'Рекорд', 'Дата', 'Квалификация', 'Пробег'], 3157 align: ['left', 'center', 'center', 'center', 'center'], 3158 rows: rankListRows, 3159 }); 3160 return txt; 3161 } 3162 3163 function createAwardsList(userId, gameTypes, sort) { 3164 const awardsList = Object.keys(gameTypes).reduce((acc, gameType) => { 3165 const game = gameTypes[gameType]; 3166 const awardCategory = getAwardCategory(game); 3167 if (checkIsNoAward(awardCategory)) { 3168 return acc; 3169 } 3170 3171 const awardDescription = getAwardDescription(game, awardCategory, gameType); 3172 const awardImg = getAwardImg(game, awardCategory); 3173 const awardUrl = createStatsUrl(userId, gameType); 3174 3175 const awardData = { 3176 img: awardImg, 3177 description: awardDescription, 3178 url: awardUrl, 3179 book: game.type === TYPES.BOOK, 3180 symbols: game.symbols, 3181 }; 3182 acc.push(awardData); 3183 return acc; 3184 }, []); 3185 if (sort) { 3186 const sortedAwardsList = sortAwardsList(awardsList); 3187 return sortedAwardsList; 3188 } 3189 return awardsList; 3190 } 3191 3192 function sortAwardsList(awardsList) { 3193 const sorted = awardsList.sort((a, b) => { 3194 if (a.book) { 3195 if (b.book) { 3196 return b.symbols - a.symbols; 3197 } else { 3198 return 1; 3199 } 3200 } else { 3201 if (b.book) { 3202 return -1; 3203 } else { 3204 return 0; 3205 } 3206 } 3207 }); 3208 return sorted; 3209 } 3210 3211 function getAwardCategory(game) { 3212 if (game.type === TYPES.BOOK) { 3213 return game.book_done ? getBookCategory(game) : 0; 3214 } 3215 3216 const races = game.num_races; 3217 for (const category of AWARD_CATEGORY_DSC) { 3218 if (races >= category) { 3219 return category; 3220 } 3221 } 3222 return 0; 3223 } 3224 3225 function checkIsNoAward(awardCategory) { 3226 return awardCategory === 0; 3227 } 3228 3229 function getAwardDescription(game, awardCategory, gameType) { 3230 if (game.type === TYPES.BOOK) { 3231 const bookRank = calculateBookRank(game); 3232 return getBookAwardDescription(bookRank, filterName(game.name)); 3233 } 3234 3235 return `${AWARD[awardCategory]} ${checkIsVocab(gameType) ? 'по словарю' : 'в режиме'} «${filterName(game.name)}»`; 3236 } 3237 3238 function getBookAwardDescription(bookRank, name) { 3239 return `${bookRank} «${name}»`; 3240 } 3241 3242 function filterName(name) { 3243 let filteredName = name; 3244 const badSymbols = ["\\", "'", "[", "]", "(", ")", "{", "}", "!", "*", "_", "~", ">", "<", "#", "=", "-", "`", ":", ";", "Оо"]; 3245 for (const badSymbol of badSymbols) { 3246 if (filteredName.includes(badSymbol)) { 3247 if (badSymbol.length > 1) { 3248 filteredName = filteredName.replaceAll(badSymbol, badSymbol.split('').join(' ')); 3249 } else { 3250 filteredName = filteredName.replaceAll(badSymbol, ' ' + '\\' + badSymbol + ' '); 3251 } 3252 } 3253 } 3254 return filteredName; 3255 } 3256 3257 function getAwardImg(game, awardCategory) { 3258 if (game.type === TYPES.BOOK) { 3259 const bookRank = calculateBookRank(game); 3260 return BOOK_RANK_IMG[bookRank]?.url; 3261 } 3262 3263 return AWARD_IMG[awardCategory]; 3264 } 3265 3266 function createAwardsReportMD(awardsList) { 3267 const awardLinkList = awardsList.map(award => { 3268 const { description, url, img } = award; 3269 const awardMDImg = createMDImg(description, img, description); 3270 const awardMDLink = createMDLink(awardMDImg, url); 3271 return awardMDLink; 3272 }); 3273 const txt = awardLinkList.join(' '); 3274 return txt; 3275 } 3276 3277 function createExercisesList(stats, mode) { 3278 const list = MODE_IDS_MAP[mode].map(vocabId => { 3279 const exercise = stats.find(gameType => gameType.id === vocabId); 3280 const info = HRUST_LIKE_DATA[mode]; 3281 const exerciseInfo = info[vocabId]; 3282 const key = exerciseInfo.key; 3283 const record = exercise?.info?.best_speed || 0; 3284 const races = exercise?.num_races || 0; 3285 const done = record > exerciseInfo.ranks.speedDsc[0]; 3286 const gradeIdx = exerciseInfo.ranks.speedDsc.findIndex(grade => record >= grade); 3287 const grade = gradeIdx === -1 ? 0 : exerciseInfo.ranks.speedDsc[gradeIdx]; 3288 const nextGradeIdx = calculateNextGradeIdx(gradeIdx); 3289 const nextGrade = exerciseInfo.ranks.speedDsc.at(nextGradeIdx); 3290 const left = nextGrade - record; 3291 const img = grade === 0 ? exerciseInfo.img : exerciseInfo.ranks[grade].img; 3292 const url = createStartNewExerciseRaceUrl(vocabId); 3293 3294 const data = { 3295 key, 3296 record, 3297 races, 3298 done, 3299 left, 3300 img, 3301 url, 3302 id: vocabId, 3303 needed: nextGrade, 3304 }; 3305 return data; 3306 }); 3307 return list; 3308 } 3309 3310 function calculateNextGradeIdx(gradeIdx) { 3311 if (gradeIdx === -1) { 3312 return gradeIdx; 3313 } else if (gradeIdx === 0) { 3314 return gradeIdx; 3315 } else { 3316 return gradeIdx - 1; 3317 } 3318 } 3319 3320 function createStartNewExerciseRaceUrl(vocabId) { 3321 return `${window.location.protocol}//klavogonki.ru/create/?gametype=voc&voc=${vocabId}&type=practice&timeout=5&submit=1`; 3322 } 3323 3324 function createStartNewRaceUrl(gameType) { 3325 return `${window.location.protocol}//klavogonki.ru/create/?gametype=${gameType}&type=practice&timeout=5&submit=1`; 3326 } 3327 3328 function createExercisesReportMD(exercisesList, mode) { 3329 const exercisesMdList = exercisesList.map(exercise => { 3330 const description = createExerciseDescription(exercise); 3331 const img = createMDImg(exercise.key, exercise.img, description); 3332 const link = createMDLink(img, exercise.url); 3333 return link; 3334 }); 3335 const sumLink = createSumLink(exercisesList, mode); 3336 exercisesMdList.push(sumLink); 3337 const txt = exercisesMdList.join(''); 3338 return txt; 3339 } 3340 3341 function createExerciseDescription(exercise) { 3342 let txt = ''; 3343 txt += `Рекорд: ${exercise.record}\n`; 3344 txt += `Требуется: ${exercise.needed}\n`; 3345 if (exercise.done) { 3346 txt += 'Пройдено ✔\n'; 3347 } else { 3348 txt += `Осталось: ${exercise.left}\n`; 3349 } 3350 txt += `Пробег: ${exercise.races}`; 3351 return txt; 3352 } 3353 3354 function createSumDescription(sumOfRecords, sumOfRaces) { 3355 let txt = ''; 3356 txt += `Сумма рекордов: ${sumOfRecords}\n`; 3357 txt += `Суммарный пробег: ${sumOfRaces}`; 3358 return txt; 3359 } 3360 3361 function createSumLink(exercisesList, mode) { 3362 const sumOfRecords = exercisesList.reduce((acc, exercise) => acc + exercise.record, 0); 3363 const sumOfRaces = exercisesList.reduce((acc, exercise) => acc + exercise.races, 0); 3364 const sumDescription = createSumDescription(sumOfRecords, sumOfRaces); 3365 const finalRaceId = MODE_FINAL_RACE_LINK[mode]; 3366 let sumUrl; 3367 let sumImg; 3368 if (GAME_TYPES[finalRaceId]) { 3369 sumUrl = createStartNewRaceUrl(finalRaceId); 3370 sumImg = createMDImg(finalRaceId, NORMAL_IMG, sumDescription); 3371 } else { 3372 sumUrl = createStartNewExerciseRaceUrl(finalRaceId); 3373 sumImg = createMDImg(finalRaceId, NORMAL_IMG, sumDescription); 3374 } 3375 const sumLink = createMDLink(sumImg, sumUrl); 3376 return sumLink; 3377 } 3378 3379 function createForumHeader(title) { 3380 return `[size="3"]${title}[/size]\n`; 3381 } 3382 3383 function createMDHeader(title) { 3384 return `### ${title}\n`; 3385 } 3386 3387 function createMDSectionHeader(title) { 3388 return `##### ${title}\n` 3389 } 3390 3391 function createMDImg(label, url, title) { 3392 return ``; 3393 } 3394 3395 function createForumLink(label, url) { 3396 return `[url="${url}"]${label}[/url]`; 3397 } 3398 3399 function createMDLink(label, url) { 3400 return `[${label}](${url})`; 3401 } 3402 3403 function createMDTable({ header, align, rows }) { 3404 let table = ''; 3405 table += header.reduce((acc, elem) => { 3406 acc += elem + ' | '; 3407 return acc; 3408 }, '| '); 3409 table += '\n'; 3410 table += align.reduce((acc, elem) => { 3411 if (elem === 'right') { 3412 acc += '---:'; 3413 } else if (elem === 'center') { 3414 acc += ':---:'; 3415 } else { 3416 acc += ':---'; 3417 } 3418 acc += ' | '; 3419 return acc; 3420 }, '| '); 3421 table += '\n'; 3422 table += rows.reduce((acc, row) => { 3423 acc += row.reduce((acc, elem) => { 3424 acc += elem + ' | '; 3425 return acc; 3426 }, '| '); 3427 acc += '\n'; 3428 return acc; 3429 }, ''); 3430 return table; 3431 } 3432 3433 function createMDBookImg(rank) { 3434 return createMDImg(rank, BOOK_RANK_IMG[rank].url, BOOK_RANK_IMG[rank].title); 3435 } 3436 3437 function createForumUserInfo(userName, userRank) { 3438 return `Игрок: [color="${RANK_COLOR[userRank]}"][b]${userName}[/b][/color]\n`; 3439 } 3440 3441 function createMDUserInfo(userName) { 3442 return `Игрок: **${userName}**\n`; 3443 } 3444 3445 function createForumTotalBooks(totalBooks) { 3446 return `Всего набрано книг: [b]${totalBooks}[/b]\n`; 3447 } 3448 3449 function createMDTotalBooks(totalBooks) { 3450 return `Всего набрано книг: **${totalBooks}**\n`; 3451 }; 3452 3453 function createForumTotalHours(totalHours) { 3454 return `Общее время набора: [b]${totalHours}[/b] ч.\n`; 3455 } 3456 3457 function createMDTotalHours(totalHours) { 3458 return `Общее время набора: **${totalHours}** ч.\n`; 3459 } 3460 3461 function createMDNote(note) { 3462 return `_*${note}_\n`; 3463 } 3464 3465 function checkIsVocab(gameType) { 3466 return gameType.startsWith(VOCAB_PREFIX); 3467 } 3468 3469 function createStatsUrl(userId, gameType) { 3470 if (checkIsVocab(gameType)) { 3471 const vocabId = gameType.slice(VOCAB_PREFIX.length); 3472 return createVocabStatsURL(userId, vocabId); 3473 } 3474 3475 if (GAME_TYPES[gameType]) { 3476 return createStandardStatsUrl(userId, gameType); 3477 } else { 3478 console.log('Unknown gameType', gameType); 3479 } 3480 } 3481 3482 function createStandardStatsUrl(userId, gameType) { 3483 return `https://klavogonki.ru/u/#/${userId}/stats/${gameType}`; 3484 } 3485 3486 function createVocabStatsURL(userId, vocabId) { 3487 return `https://klavogonki.ru/u/#/${userId}/stats/${VOCAB_PREFIX}${vocabId}`; 3488 } 3489 3490 function createForumVocabStatsLink(userId, label, vocabId) { 3491 const url = createVocabStatsURL(userId, vocabId); 3492 return createForumLink(label, url); 3493 } 3494 3495 function createMDVocabStatsLink(userId, label, vocabId) { 3496 const url = createVocabStatsURL(userId, vocabId); 3497 return createMDLink(label, url); 3498 } 3499 3500 async function copyToClipboard(textToCopy) { 3501 // Navigator clipboard api needs a secure context (https) 3502 if (navigator.clipboard && window.isSecureContext) { 3503 await navigator.clipboard.writeText(textToCopy); 3504 } else { 3505 // Use the 'out of viewport hidden text area' trick 3506 const textArea = document.createElement("textarea"); 3507 textArea.value = textToCopy; 3508 3509 // Move textarea out of the viewport so it's not visible 3510 textArea.style.position = "absolute"; 3511 textArea.style.left = "-999999px"; 3512 3513 document.body.prepend(textArea); 3514 textArea.select(); 3515 3516 try { 3517 // Deprecated method!!! 3518 document.execCommand('copy'); 3519 } catch (error) { 3520 console.error(error); 3521 } finally { 3522 textArea.remove(); 3523 } 3524 } 3525 } 3526 3527 function createMenu (sidebarNode, login, rank) { 3528 const menuStructure = Object.keys(BUTTON_TXT).map(key => { 3529 const [mode, format] = key.split('_'); 3530 return ({ 3531 text: BUTTON_TXT[key], 3532 mode: FILTER_MODES[mode], 3533 icon: EXERCISES_BUTTON_ICON[mode], 3534 svg: EXERCISES_BUTTON_SVG[mode], 3535 format, 3536 }); 3537 }); 3538 3539 const menu = document.createElement('ul'); 3540 menu.className = 'profile-nav'; 3541 menuStructure.forEach(function (item) { 3542 const li = document.createElement('li'); 3543 const a = document.createElement('a'); 3544 a.href = '#'; 3545 a.innerHTML = `<div class="icon-icomoon ${item.icon ? item.icon : ''}">${item.svg && !item.icon ? item.svg : ''}</div> 3546 <span>${item.text}</span>`; 3547 a.onclick = function(e) { 3548 e.preventDefault(); 3549 handler(item.mode, item.format, login, rank); 3550 } 3551 li.appendChild(a); 3552 menu.appendChild(li); 3553 }); 3554 3555 return sidebarNode.appendChild(menu); 3556 } 3557 3558 function initMenu () { 3559 const sidebarNode = document.querySelector('.sidebar'); 3560 if (!sidebarNode) { 3561 return false; 3562 } 3563 3564 const loginNode = document.querySelector('.profile-header .name'); 3565 if (!loginNode || !loginNode.firstChild) { 3566 return false; 3567 } 3568 3569 const rankNode = document.querySelector('.profile-header .title'); 3570 if (!rankNode) { 3571 return false; 3572 } 3573 3574 const login = loginNode.firstChild.textContent.trim(); 3575 const rank = rankNode.textContent.trim(); 3576 return createMenu(sidebarNode, login, rank); 3577 } 3578 3579 const observer = window.setInterval(function () { 3580 if (!initMenu()) { 3581 return; 3582 } 3583 window.clearInterval(observer); 3584 const injector = angular.element('body').injector(); 3585 injector.invoke(function ($routeParams, $rootScope, $timeout) { 3586 let id = $routeParams.user; 3587 $rootScope.$on('routeSegmentChange', function () { 3588 if (id !== $routeParams.user) { 3589 id = $routeParams.user; 3590 // Wait for the digest cycle: 3591 $timeout(initMenu); 3592 } 3593 }) 3594 }); 3595 }, 500); 3596 } 3597 3598 const inject = document.createElement('script'); 3599 inject.setAttribute('type', 'application/javascript'); 3600 inject.appendChild(document.createTextNode('(' + main.toString() + ')()')); 3601 document.body.appendChild(inject);