G

Untitled

public
Guest Aug 27, 2024 Never 140
Clone
JavaScript KG_Generate_Report_0.4.0 3601 lines (3379 loc) | 107.27 KB
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 `![${label}](${url} '${title}')`;
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);