RBR Czech - Class Addendum

Adds omitted class links on rbr.onlineracing.cz

  1. // ==UserScript==
  2. // @name RBR Czech - Class Addendum
  3. // @namespace RBRCzechScripts
  4. // @author Łukasz Demolin "Maggot"
  5. // @version 0.8.0.3
  6. // @date 2016-08-24
  7. // @icon https://dl.dropboxusercontent.com/u/10106549/e-rajdy/richard_burns_rally.ico
  8. // @description Adds omitted class links on rbr.onlineracing.cz
  9. // @description Also adds missing tracks on the "Ranks" and "Record" pages everywhere. Added dropdown-menu with stage select on stagerec/stagerank pages.
  10. // @description Unfortunately, the times and car names are not displayed (major hurdle).
  11. // @require https://code.jquery.com/jquery-3.0.0.js
  12. // @include http://rbr.onlineracing.cz/index.php*
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_xmlhttpRequest
  16.  
  17. // ==/UserScript==
  18.  
  19. // Populating a handy faux-dictionary with classes and their correct ids (to avoid messing up and hardcoding).
  20. var classesDict = { "WRC legacy": 1,
  21. "N4" : 3,
  22. "S2000" : 13,
  23. "S1600" : 2,
  24. "A8" : 4,
  25. "A7" : 5,
  26. "A6" : 6,
  27. "A5" : 7,
  28. "N3" : 8,
  29. "H" : 11,
  30. "GT" : 14,
  31. "RC1" : 101,
  32. "RC2" : 102,
  33. "RGT" : 103,
  34. "RC3" : 104,
  35. "RC4" : 105,
  36. "RC5" : 106,
  37. "WRC" : 111,
  38. "H/B" : 107,
  39. "H/A" : 108,
  40. "H/4" : 109,
  41. "H/2" : 110
  42. };
  43.  
  44. // A dictionary of sorts to assign all the countries correctly (sorts the duplicates like CZ/Czech Republic and mistakes like Sweeden). Preparing for CSS classes (avoiding spaces -> using the international 2-digit codes).
  45. var countriesDict = { "Finland" : "FI",
  46. "Great Britain" : "GB",
  47. "Rally School" : "GB",
  48. "Rally school" : "GB",
  49. "Australia" : "AU",
  50. "France" : "FR",
  51. "Japan" : "JP",
  52. "USA": "US",
  53. "CZ" : "CZ",
  54. "Czech Republic" : "CZ",
  55. "Netherlands" : "NL",
  56. "Germany" : "DE",
  57. "Ireland" : "IE",
  58. "Slovakia" : "SK",
  59. "Lithuania" : "LT",
  60. "Poland" : "PL",
  61. "Spain" : "ES",
  62. "Montekland" : "Montek",
  63. "Sweeden" : "SE",
  64. "Sweden" : "SE",
  65. "Argentina" : "AR",
  66. "Estonia" : "EE",
  67. "Italy" : "IT",
  68. "Itally" : "IT",
  69. "Slovenia" : "SI",
  70. "Switzerland" : "CH",
  71. "Swiss" : "CH",
  72. "Ukraine" : "UA",
  73. "Test":"Test"
  74. };
  75.  
  76. // An array of helpful data related to sorting and subtitling the stages and countries.
  77. var countriesSortOrder = [["FI", "Finland", "http://rbr.onlineracing.cz/images/flgs/FI.gif"],
  78. ["SE", "Sweden", "http://rbr.onlineracing.cz/images/flgs/SE.gif"],
  79. ["GB", "Great Britain", "http://rbr.onlineracing.cz/images/flgs/GB.gif"],
  80. ["IE", "Ireland", "http://rbr.onlineracing.cz/images/flgs/IE.gif"],
  81. ["AU", "Australia", "http://rbr.onlineracing.cz/images/flgs/AU.gif"],
  82. ["FR", "France", "http://rbr.onlineracing.cz/images/flgs/FR.gif"],
  83. ["JP", "Japan", "http://rbr.onlineracing.cz/images/flgs/JP.gif"],
  84. ["US", "USA", "http://rbr.onlineracing.cz/images/flgs/US.gif"],
  85. ["CZ", "Czech Republic", "http://rbr.onlineracing.cz/images/flgs/CZ.gif"],
  86. ["SK", "Slovakia", "http://rbr.onlineracing.cz/images/flgs/SK.gif"],
  87. ["PL", "Poland", "http://rbr.onlineracing.cz/images/flgs/PL.gif"],
  88. ["DE", "Germany", "http://rbr.onlineracing.cz/images/flgs/DE.gif"],
  89. ["NL", "Netherlands", "http://rbr.onlineracing.cz/images/flgs/NL.gif"],
  90. ["LT", "Lithuania", "http://rbr.onlineracing.cz/images/flgs/LT.gif"],
  91. ["EE", "Estonia", "http://rbr.onlineracing.cz/images/flgs/EE.gif"],
  92. ["SI", "Slovenia", "http://rbr.onlineracing.cz/images/flgs/SI.gif"],
  93. ["UA", "Ukraine", "http://rbr.onlineracing.cz/images/flgs/UA.gif"],
  94. ["ES", "Spain", "http://rbr.onlineracing.cz/images/flgs/ES.gif"],
  95. ["IT", "Italy", "http://rbr.onlineracing.cz/images/flgs/IT.gif"],
  96. ["CH", "Switzerland", "http://rbr.onlineracing.cz/images/flgs/CH.gif"],
  97. ["AR", "Argentina", "http://rbr.onlineracing.cz/images/flgs/AR.gif"],
  98. ["Montek", "Montekland", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAQCAIAAACHs/j/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAD5klEQVR4Xl2TbUxbVRjHn+ee+35v29s3oGtBbZnRDChjsNE007llwsYSNbwMo0OIMca4qYnJwhKDxrlojPEDw5e4+AKZ7wlxWczitwW3IQw3MIw5ySoQMyhvZW1vL+297fUDrBn8vpxzPvx/ef455wBsAKtrilxuAZESRdLdU3Dht5DNxtsUYegqd2XoOYuF9Xjl/nMFbe1FG4PrkI1HtFjJ13322TnH3Kza3GIeOMSXBlyipL7QkSos4FU1c/KUGg6Xnflci0RWAIBhaIHndN1Yz2+wAYgSe/oTW/Nh75XfNV9JxF9qULnyXM5LEQVQgBwCNZRJux/ffZMX1Oqd1KMPmz2nycREbINFkIQ9e+1VVXaPR3mm0T0ySheXkOMncHCYSqySlIGqAaoBqkGv6iEtE44ucamMMjW9u6pKvn8mem3RVO32JPNlb+H27SV6OjowkEjE+Rvj4cV5hWFvNTaNhR/TAQHAyOIgIFptoCYaX3n50rVrybxrMyUPypf/YFMGdLxEAQAgIIXByqDdLnW9g9FlUPW1GSFl4PlfrbW7lPJypazc6vdbZVmC/FUgoiSJDENoWt67V+3/GW+Mm2ACmBCdi+q6bhjURx+YUxGMzlFbPCDJ5kOB9JH24NNP2ZAk/xoz5uc10zQBABxO57Otrh9+LF2K12m6K2VAfBU/7obCIgQAROh6m8wuEYeT1B2oa2s/sueJUFMLGf+b6j3r83gVAOpevXtwHKc4LC2HrSOjnGrAcgJ/6ieRGez7jvSeZZMZHL6+nkHAgD/Asqwkwp9j3OWr7lePbgkEFJ5nIV82m83ShCp+oHR/nd3pnCcMrCwjL+DO2lx8BRYWcFWjBi6ad++CCRCLxXK5bFMzlgWL7/y3NZ0Gmo0vLbLptAkAwDD0jmrnL+f8k1Oh29NcNEbW+r77HhNdppIZuLMIyQz88y998hTldAMiNBzCuAbdn9lEUV4vCABrT0YQBYtMmwB6xpQtpLtHqT84bSIsLeCH71MuF+Xx5hCQ41Bx5HZUZ6OzlMtFG1m/2209/ubkV2eSGV2/X7oOw5Bjxx7R0qFYgool2ZReoWYckRlqcJhEZlDVUTO4lSSd0l0Tt2RfsSMUdrzVZdlVa0fc/LsAALZts42MFXae8FZUukeuBzVDTKX3a/rzWqZVyzy5qgc13fdNn9jUvPX8hcrmVjsiIqIgCCzLbnbxPH+wwe0rlgmhfT7rwnJbLEkdfcN6c7ImZbAjo8y335dphrOz00YhJYpcRaXN7rBttuQhhORn3rdPmV2oqm+wCBL/6Rf1KcPS8aJYUCRfvFTz2uvOfESSpPx+jf8Bk4d9ASiTkI4AAAAASUVORK5CYII="],
  99. ["ROW", "Rest of the World", "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAIABADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEE/8QAIRAAAQMCBwEAAAAAAAAAAAAAEQABEgITBBQhIiMxYQX/xAAUAQEAAAAAAAAAAAAAAAAAAAAH/8QAFxEBAAMAAAAAAAAAAAAAAAAAABEiQf/aAAwDAQACEQMRAD8A04FmzvO1ZL2yIXDtnLSB78U+gzZx7DVk8gELh3QjpA9eIiWpsPsf/9k="]];
  100.  
  101. // An array or array with data of all the stages available on the site.
  102. var stagesDict = [[10,"Kaihuavaara","Finland","Snow","6.1 km","Yes","Open"],
  103. [11,"Mustaselka","Finland","Snow","7.9 km","Yes","Open"],
  104. [12,"Sikakama","Finland","Snow","10.2 km","Yes","Open"],
  105. [13,"Autiovaara","Finland","Snow","6.1 km","Yes","Open"],
  106. [14,"Kaihuavaara II","Finland","Snow","6.1 km","Yes","Open"],
  107. [15,"Mustaselka II","Finland","Snow","7.7 km","Yes","Open"],
  108. [16,"Sikakama II","Finland","Snow","10.2 km","Yes","Open"],
  109. [17,"Autiovaara II","Finland","Snow","6.1 km","Yes","Open"],
  110. [20,"Harwood Forest","Great Britain","Gravel","6.1 km","Yes","Open"],
  111. [21,"Falstone","Great Britain","Gravel","6.6 km","Yes","Open"],
  112. [22,"Chirdonhead","Great Britain","Gravel","7 km","Yes","Open"],
  113. [23,"Shepherds Shield","Great Britain","Gravel","4.8 km","Yes","Open"],
  114. [24,"Harwood Forest II","Great Britain","Gravel","5.9 km","Yes","Open"],
  115. [25,"Chirdonhead II","Great Britain","Gravel","6.9 km","Yes","Open"],
  116. [26,"Falstone II","Great Britain","Gravel","6.6 km","Yes","Open"],
  117. [27,"Shepherds Shield II","Great Britain","Gravel","4.9 km","Yes","Open"],
  118. [30,"Greenhills II","Australia","Gravel","6 km","Yes","Open"],
  119. [31,"New Bobs","Australia","Gravel","10.1 km","Yes","Open"],
  120. [32,"Greenhills","Australia","Gravel","6 km","Yes","Open"],
  121. [33,"Mineshaft","Australia","Gravel","8.2 km","Yes","Open"],
  122. [34,"East-West","Australia","Gravel","9.5 km","Yes","Open"],
  123. [35,"New Bobs II","Australia","Gravel","10 km","Yes","Open"],
  124. [36,"East-West II","Australia","Gravel","9.6 km","Yes","Open"],
  125. [37,"Mineshaft II","Australia","Gravel","8.2 km","Yes","Open"],
  126. [41,"Cote D'Arbroz","France","Tarmac","4.5 km","Yes","Open"],
  127. [42,"Joux Verte","France","Tarmac","7.9 km","Yes","Open"],
  128. [43,"Bisanne","France","Tarmac","5.6 km","Yes","Open"],
  129. [44,"Joux Plane","France","Tarmac","11.1 km","Yes","Open"],
  130. [45,"Joux Verte II","France","Tarmac","7.8 km","Yes","Open"],
  131. [46,"Cote D'Arbroz II","France","Tarmac","4.3 km","Yes","Open"],
  132. [47,"Bisanne II","France","Tarmac","5.6 km","Yes","Open"],
  133. [48,"Joux Plane II","France","Tarmac","11.1 km","Yes","Open"],
  134. [50,"Sipirkakim II","Japan","Gravel","8.7 km","Yes","Open"],
  135. [51,"Noiker","Japan","Gravel","13.8 km","Yes","Open"],
  136. [52,"Sipirkakim","Japan","Gravel","8.7 km","Yes","Open"],
  137. [53,"Pirka Menoko","Japan","Gravel","6.7 km","Yes","Open"],
  138. [54,"Tanner","Japan","Gravel","3.9 km","Yes","Open"],
  139. [55,"Noiker II","Japan","Gravel","13.7 km","Yes","Open"],
  140. [56,"Tanner II","Japan","Gravel","4 km","Yes","Open"],
  141. [57,"Pirka Menoko II","Japan","Gravel","6.7 km","Yes","Open"],
  142. [60,"Frazier Wells II","USA","Gravel","5 km","Yes","Open"],
  143. [61,"Fraizer Wells","USA","Gravel","5 km","Yes","Open"],
  144. [62,"Prospect Ridge","USA","Gravel","7.8 km","Yes","Open"],
  145. [63,"Diamond Creek","USA","Gravel","7.1 km","Yes","Open"],
  146. [64,"Hualapai Nation","USA","Gravel","8.6 km","Yes","Open"],
  147. [65,"Prospect Ridge II","USA","Gravel","7.9 km","Yes","Open"],
  148. [66,"Diamond Creek II","USA","Gravel","6.8 km","Yes","Open"],
  149. [67,"Hualapai Nation II","USA","Gravel","8.6 km","Yes","Open"],
  150. [70,"Prospect Ridge II A","USA","Gravel","7.6 km","Yes","Open"],
  151. [71,"Rally School Stage","Rally school","Gravel","2.2 km","Yes","Open"],
  152. [90,"Rally School Stage II","Rally school","Gravel","2.3 km","Yes","Open"],
  153. [94,"Stryckovy okruh","CZ","Tarmac","9.2 km","Yes","Open"],
  154. [95,"Sumburk 2007","CZ","Tarmac","12.4 km","No","Open"],
  155. [96,"Sosnova","CZ","Tarmac","7.1 km","No","Open"],
  156. [105,"Sosnova 2010","CZ","Tarmac","4.2 km","No","Restricted"],
  157. [106,"Stryckovy - Zadni Porici","CZ","Tarmac","6.9 km","Yes","Open"],
  158. [107,"PTD Rallysprint","Netherlands","Gravel","5.1 km","Yes","Open"],
  159. [108,"Osli - Stryckovy","CZ","Tarmac","10.6 km","Yes","Open"],
  160. [125,"Bergheim v1.1","Germany","Tarmac","8 km","Yes","Open"],
  161. [131,"Lyon - Gerland","France","Tarmac","0.7 km","Yes","Restricted"],
  162. [132,"Gestel","Netherlands","Tarmac","7.2 km","Yes","Open"],
  163. [139,"RSI slalom Shonen","Ireland","Tarmac","1 km","Yes","Open"],
  164. [140,"RSI slalom gegeWRC","Ireland","Tarmac","1.8 km","Yes","Open"],
  165. [141,"Mlynky","Slovakia","Tarmac","7.1 km","Yes","Open"],
  166. [142,"Mlynky Snow","Slovakia","Snow","7.1 km","Yes","Open"],
  167. [143,"Peklo","Slovakia","Tarmac","8.5 km","Yes","Open"],
  168. [144,"Peklo Snow","Slovakia","Snow","8.5 km","Yes","Open"],
  169. [145,"Versme","Lithuania","Gravel","3.2 km","Yes","Open"],
  170. [146,"Peklo II","Slovakia","Tarmac","8.5 km","Yes","Open"],
  171. [147,"Peklo II Snow","Slovakia","Snow","8.5 km","Yes","Open"],
  172. [148,"ROC 2008","Great Britain","Tarmac","2 km","Yes","Open"],
  173. [149,"Sieversdorf V1.1","Germany","Tarmac","8 km","Yes","Open"],
  174. [152,"RP 2009 Shakedown","Poland","Gravel","4.4 km","Yes","Open"],
  175. [153,"RP 2009 Shakedown Reversed","Poland","Gravel","4.4 km","Yes","Open"],
  176. [154,"Bruchsal-Unteroewisheim","Germany","Tarmac","8.9 km","Yes","Open"],
  177. [155,"Humalamaki 1.0","Finland","Gravel","4.4 km","Yes","Open"],
  178. [156,"Mlynky II","Slovakia","Tarmac","7.1 km","Yes","Open"],
  179. [157,"Grand Canaria ROC 2000","Spain","Gravel","3.8 km","Yes","Open"],
  180. [158,"Sweet Lamb","Great Britain","Gravel","5.1 km","Yes","Open"],
  181. [159,"Sweet Lamb II","Great Britain","Gravel","5.1 km","Yes","Open"],
  182. [471,"Aragona","Italy","Tarmac","6.4 km","Yes","Restricted"],
  183. [472,"Muxarello","Italy","Tarmac","15.4 km","Yes","Restricted"],
  184. [479,"Shomaru Pass","Japan","Tarmac","5.8 km","Yes","Open"],
  185. [480,"Shomaru Pass II","Japan","Tarmac","5.8 km","Yes","Open"],
  186. [481,"Karlstad","Sweeden","Snow","1.9 km","Yes","Open"],
  187. [482,"Karlstad II","Sweeden","Snow","1.9 km","Yes","Open"],
  188. [484,"Humalamaki Reversed","Finland","Gravel","4.4 km","Yes","Open"],
  189. [488,"Jirkovicky","Montekland","Gravel","3.5 km","No","Restricted"],
  190. [489,"Jirkovicky II","Montekland","Gravel","3.5 km","No","Restricted"],
  191. [490,"Sourkov","Montekland","Gravel","6.2 km","No","Restricted"],
  192. [491,"Lernovec","Montekland","Gravel","5 km","No","Restricted"],
  193. [492,"Uzkotin","Montekland","Gravel","7.8 km","No","Restricted"],
  194. [493,"Hroudovany","Montekland","Gravel","7.1 km","No","Restricted"],
  195. [494,"Snekovice","Montekland","Gravel","8.6 km","No","Restricted"],
  196. [495,"Lernovec II","Montekland","Gravel","5 km","No","Restricted"],
  197. [496,"Uzkotin II","Montekland","Gravel","7.6 km","No","Restricted"],
  198. [497,"Hroudovany II","Montekland","Gravel","7.1 km","No","Restricted"],
  199. [498,"Snekovice II","Montekland","Gravel","8.6 km","No","Restricted"],
  200. [499,"Sourkov 2","Montekland","Gravel","6.2 km","No","Restricted"],
  201. [516,"Hradek 1","CZ","Tarmac","5.8 km","No","Open"],
  202. [517,"Hradek 2","CZ","Tarmac","5.8 km","No","Open"],
  203. [518,"Liptakov 1","CZ","Tarmac","6 km","No","Open"],
  204. [519,"Liptakov 2","CZ","Tarmac","6 km","No","Open"],
  205. [522,"Rally School Czech","Czech Republic","Tarmac","3.2 km","Yes","Open"],
  206. [524,"Rally School Czech II","Czech Republic","Tarmac","3.1 km","Yes","Open"],
  207. [528,"Kuadonvaara","Finland","Snow","5.7 km","Yes","Open"],
  208. [533,"Karowa 2009","Poland","Tarmac","1.6 km","Yes","Open"],
  209. [534,"Haugenau 2012","France","Tarmac","5.7 km","Yes","Open"],
  210. [544,"Fernet Branca","Argentina","Gravel","6 km","Yes","Open"],
  211. [545,"Junior Wheels I","Australia","Gravel","5.6 km","Yes","Open"],
  212. [546,"Junior Wheels II","Australia","Gravel","5.6 km","Yes","Open"],
  213. [550,"Foron","France","Tarmac","9.2 km","Yes","Open"],
  214. [551,"Foron II","France","Tarmac","9.2 km","Yes","Open"],
  215. [552,"Foron Snow","France","Snow","9.1 km","Yes","Open"],
  216. [553,"Foron Snow II","France","Snow","9.1 km","Yes","Open"],
  217. [555,"Maton I","France","Tarmac","3.5 km","Yes","Open"],
  218. [556,"Maton II","France","Tarmac","3.5 km","Yes","Open"],
  219. [557,"Red Bull HC","Italy","Gravel","14 km","Yes","Open"],
  220. [558,"Maton snow","France","Snow","3.5 km","Yes","Open"],
  221. [559,"Maton snow II","France","Snow","3.5 km","Yes","Open"],
  222. [560,"Loch Ard","Great Britain","Gravel","8.3 km","Yes","Open"],
  223. [561,"Loch Ard II","Great Britain","Gravel","8.3 km","Yes","Open"],
  224. [565,"Undva Reversed","Estonia","Gravel","10 km","Yes","Open"],
  225. [566,"Undva","Estonia","Gravel","10 km","Yes","Open"],
  226. [570,"Peyregrosse - Mandagout","France","Tarmac","12.8 km","Yes","Open"],
  227. [571,"Peyregrosse - Mandagout NIGHT","France","Tarmac","12.8 km","Yes","Open"],
  228. [572,"Castrezzato","Italy","Tarmac","8.1 km","Yes","Open"],
  229. [573,"SS Daniel Bonara","Italy","Tarmac","5.5 km","Yes","Open"],
  230. [574,"Sorica","Slovenia","Tarmac","15.5 km","Yes","Open"],
  231. [582,"Barum rally 2009 Semetin","CZ","Tarmac","11.7 km","Yes","Open"],
  232. [583,"Barum rally 2010 Semetin","CZ","Tarmac","11.7 km","Yes","Open"],
  233. [585,"SWISS II","Switzerland","Gravel","5.6 km","Yes","Open"],
  234. [586,"SWISS I","Switzerland","Tarmac","5.6 km","Yes","Open"],
  235. [587,"Swiss IV","Swiss","Gravel","8.2 km","Yes","Open"],
  236. [589,"Swiss III","Swiss","Tarmac","8.2 km","Yes","Open"],
  237. [590,"Blanare","France","Snow","7.6 km","Yes","Open"],
  238. [591,"Blanare II","France","Snow","6.6 km","Yes","Open"],
  239. [592,"Slovakia Ring 2014","Slovakia","Tarmac","11 km","Yes","Open"],
  240. [593,"Slovakia Ring 2014 II","Slovakia","Tarmac","11 km","Yes","Open"],
  241. [595,"Sardian","USA","Tarmac","5.1 km","Yes","Open"],
  242. [596,"Sardian Night","USA","Tarmac","5.1 km","Yes","Open"],
  243. [597,"Mlynky Snow II","Slovakia","Snow","7.1 km","Yes","Open"],
  244. [598,"Pikes Peak 2008","USA","Tarmac","19.9 km","Yes","Open"],
  245. [599,"Northumbria","Great Britain","Gravel","9 km","Yes","Open"],
  246. [601,"Northumbria Tarmac","Great Britain","Tarmac","9 km","Yes","Open"],
  247. [700,"Passo Valle","Italy","Tarmac","5.8 km","Yes","Open"],
  248. [701, "Passo Valle Reverse", "Itally", "Tarmac", "5.8 km", "Yes", "Open"],
  249. [711,"Akagi Mountain","Japan","Tarmac","3.5 km","Yes","Open"],
  250. [712,"Akagi Mountain II","Japan","Tarmac","3.5 km","Yes","Open"],
  251. [777,"Pian del Colle","Italy","Tarmac","8.3 km","Yes","Open"],
  252. [778,"Pian del Colle Reversed","Italy","Tarmac","8.4 km","Yes","Open"],
  253. [779,"Pian del Colle Snow","Italy","Snow","8.3 km","Yes","Open"],
  254. [780,"Pian del Colle Snow Reversed","Italy","Snow","8.4 km","Yes","Open"],
  255. [800,"Ai-Petri","Ukraine","Tarmac","17.3 km","Yes","Open"],
  256. [801,"Uchan-Su","Ukraine","Tarmac","10.8 km","Yes","Open"],
  257. [802,"Ai-Petri Winter","Ukraine","Snow","17.3 km","Yes","Open"],
  258. [803,"Uchan-Su Winter","Ukraine","Snow","10.8 km","Yes","Open"],
  259. [810,"Livadija","Ukraine","Tarmac","5.5 km","Yes","Open"],
  260. [811,"Livadija II","Ukraine","Tarmac","5.5 km","Yes","Open"],
  261. [830,"Azov","Ukraine","Gravel","19.1 km","Yes","Open"],
  262. [831,"Azov II","Ukraine","Gravel","19.2 km","Yes","Open"],
  263. [886,"Zaraso Salos Trekas - 5 laps","Lithuania","Gravel","5 km","Yes","Open"],
  264. [887,"Zaraso Salos Trekas - 2 laps","Lithuania","Gravel","2 km","Yes","Open"],
  265. [888,"Shakedown Rally del Salento 2014","Italy","Tarmac","3.8 km","Yes","Open"],
  266. [911,"Torre Vecchia","Italy","Tarmac","9.8 km","Yes","Open"],
  267. [969,"Tavia","Italy","Gravel","3.8 km","Yes","Open"],
  268. [979,"Berica","Italy","Gravel","14.8 km","Yes","Open"],
  269. [980,"Rally Wisla Shakedown","Poland","Tarmac","2.5 km","Yes","Open"],
  270. [981,"Hyppyjulma gravel","Finland","Gravel","6.1 km","Yes","Open"],
  271. [982,"Hyppyjulma gravel II","Finland","Gravel","6.1 km","Yes","Open"],
  272. [983,"Hyppyjulma tarmac","Finland","Tarmac","6.1 km","Yes","Open"],
  273. [984,"Hyppyjulma tarmac II","Finland","Tarmac","6.1 km","Yes","Open"],
  274. [985,"Kolmenjarvet gravel","Finland","Gravel","6.1 km","Yes","Open"],
  275. [986,"Kolmenjarvet gravel II","Finland","Gravel","6.1 km","Yes","Open"],
  276. [987,"Kolmenjarvet tarmac","Finland","Tarmac","6.1 km","Yes","Open"],
  277. [988,"Kolmenjarvet tarmac II","Finland","Tarmac","6.1 km","Yes","Open"],
  278. [993,"Kormoran Shakedown","Poland","Gravel","5.2 km","Yes","Open"],
  279. [994,"Kormoran I","Poland","Gravel","10.3 km","No","Open"],
  280. [995,"Kormoran II","Poland","Gravel","12 km","No","Open"],
  281. [996, "SSS Mikolajki I", "Poland", "Gravel", "2.6 km", "Yes", "Restricted"],
  282. [997, "SSS Mikolajki II", "Poland", "Gravel", "2.6 km", "Yes", "Restricted"],
  283. [1012,"Puy du Lac","France","Tarmac","5 km","Yes","Open"],
  284. [1024,"GB Sprint Extreme","Great Britain","Gravel","6.7 km","Yes","Open"],
  285. [1025,"FSO Zeran - Warsaw","Poland","Tarmac","7.1 km","Yes","Open"],
  286. [1033,"Track Test","Test","Tarmac","1.6 km","Yes","Restricted"],
  287. [1141,"Snow Cote D'Arbroz","France","Snow","4.5 km","Yes","Open"],
  288. [1142,"Snow Joux Verte","France","Snow","7.9 km","Yes","Open"],
  289. [1143,"Snow Bisanne","France","Snow","5.6 km","Yes","Open"],
  290. [1144,"Snow Joux Plane","France","Snow","11.1 km","Yes","Open"],
  291. [1145,"Snow Joux Verte II","France","Snow","7.9 km","Yes","Open"],
  292. [1146,"Snow Cote D'Arbroz II","France","Snow","4.5 km","Yes","Open"],
  293. [1147,"Snow Bisanne II","France","Snow","5.6 km","Yes","Open"],
  294. [1148,"Snow Joux Plane II","France","Snow","11.1 km","Yes","Open"],
  295. [1899,"Courcelles Val'd Esnoms","France","Tarmac","9.9 km","Yes","Open"],
  296. [1900,"Vieux Moulin-Perrancey","France","Gravel","20.5 km","Yes","Open"]];
  297.  
  298. // A couple of helpful vars to make traversing the stage data easier.
  299. var stageID = 0, stageName = 1, stageCountry = 2, stageSurface = 3, stageDistance = 4, stageIsShakedownable = 5, stageIsRestricted = 6;
  300.  
  301. // An array "with holes" of stages with the information wheter or not they are on the page (undefined -> not, "true" -> yes)
  302. var isStageOnThePage = [];
  303.  
  304. // Reading the favourite class from local storage (if doesn't exist - 0 to know it's not there).
  305. var favClass = GM_getValue("favClass", 0);
  306. // Creating a progressbar object to hold information about it.
  307. var progressBar = { requests: 0, completed: 0, location: "", increment: function(){
  308. this.completed ++;
  309. var bar = $(this.location).children("span");
  310. var percComplete = (this.completed/this.requests)*100;
  311. if (this.requests > 0) {$(bar).css("width",percComplete + "%"); $(bar).html("Loading: " + Math.round(percComplete) + "%  ");}
  312. if (this.completed === this.requests) {
  313. $(bar).addClass("meterdone");
  314. $(bar).html("All data loaded!   ");
  315. }
  316. }};
  317.  
  318. // Parsing the URL looking for a certain parameter (returning a string with value, or empty string if no value found).
  319. function parseurl( name ){
  320. name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  321. var regexS = "[\\?&]"+name+"=([^&#]*)";
  322. var regex = new RegExp( regexS );
  323. var results = regex.exec( document.URL );
  324. console.log(results);
  325. if( results === null )
  326. return "";
  327. else{/*
  328. if (name == "stageid"){
  329. return results[0]; // results "stageid=13"
  330. }
  331. else {
  332. return results[1];
  333. }*/
  334. return results[1];
  335. }
  336. }
  337.  
  338. // Creating a base link to display in each row of the table. Link's contents depend on the type of site we're on.
  339. function makeLinkBase(){
  340. var doLinkow = [];
  341. if (whereAmI == "urec" || whereAmI == "urank") { doLinkow[0] = whereAmI.replace("u", "stage"); }
  342. else { if (whereAmI == "tstats") { switch (parseurl("type")) { case "1": doLinkow[0] = "stagerec"; break; case "3": doLinkow[0] = "stagerank"; break; default: doLinkow[0] = "stagerec"; break; }}}
  343. doLinkow[1] = parseurl("classid");
  344. doLinkow[2] = parseurl("state");
  345. if (doLinkow[1] === "") { doLinkow[1] = 1; }
  346.  
  347. return doLinkow;
  348. }
  349.  
  350. // Finding the results table, non-jquery method (legacy).
  351. function findResultsTable(){
  352. var tables = document.getElementsByTagName("table");
  353. for (var i = 0; i < tables.length; i++) {
  354. if (tables[i].width == '70%'){
  355. return tables[i].children[0];
  356. }
  357. }
  358. return null;
  359. }
  360.  
  361. // Adding a head to the results table (it's missing by default)
  362. function addHeadAndBody(){
  363. console.log("Searching for the head...");
  364. var thead = $("thead", resultsTable);
  365. /*console.log(thead);*/
  366. if (thead.length === 0){ // To avoid adding a second thead
  367. console.log("No head, wrapping.");
  368. $("<thead></thead>").prependTo(resultsTable);
  369. $("tr:first", resultsTable).appendTo($("thead", resultsTable));
  370. }
  371. }
  372.  
  373. // Function finding and returning results table (records/ranks) via jQuery selector.
  374. // The only unique attribute of that table is it's hard-coded width of 70% (no special classes and/or ids.
  375. function findResultsTableJQ(){
  376. return $('table[width="70%"]');
  377. }
  378.  
  379. // Function finding the correct ID of s
  380. function findStage(name){
  381. for (var i = 0, len = stagesDict.length; i < len; i++)
  382. {
  383. if (stagesDict[i][stageName] === name)
  384. {
  385. return stagesDict[i]; // Return as soon as the object is found with the matching name
  386. }
  387. }
  388. return 0; // If the loop does not find a matching name - return 0
  389. }
  390.  
  391. // Function changing the contents of the stages table (Country converted to the 2digit country codes according to the dictionary
  392. function convertStageCountries(){
  393. console.log("Processing stage countries (dict)...");
  394. var currCountry = 0; // Current, processed country.
  395. var targetCountry = 0; // Our target, a two-digit code.
  396. $.each((stagesDict), function (key, value){
  397. // console.log("Processing " + value);
  398. currCountry = value[stageCountry]; // Read from the object with 2-digit codes.
  399. if (currCountry !== undefined){
  400. targetCountry = countriesDict[currCountry];
  401. if (targetCountry !== undefined && targetCountry !== 0){
  402. value[stageCountry] = targetCountry;
  403. } else {value[stageCountry] = "ROW";} // Default fallback for non-defined countrynames, Rest-of-World.
  404. } else {value[stageCountry] = "ROW"};
  405. // console.log("Results: " + value + " // Country: " + currCountry + "->" + targetCountry);
  406. });
  407. console.log("Stage countries processed!");
  408. }
  409.  
  410. // Function sorting the stagesDict alphabetically (due to automatization reasons will not be done by hand)
  411. // NOT USED due to performance issues. Insead all list are sorted during adding to table/selector.
  412. function sortStagesDict(){
  413. stagesDict.sort(function (a, b) {
  414.  
  415. var A = a[stageName].toUpperCase();
  416. var B = b[stageName].toUpperCase();
  417. console.log(A + " i " + B);
  418.  
  419. if(A < B) {console.log("-1"); return -1;}
  420. if(A > B) {console.log("1"); return 1;}
  421. return 0;
  422. });
  423. $.each((stagesDict), function (key, value){ console.log(key + "//" + value);});
  424. }
  425.  
  426. // Apllying correct classes (country codes) to all rows of the table + removing empty rows (containing blank names).
  427. function classifyStages(stagesToClassify){
  428. var currStage = 0;
  429. var stageData = 0;
  430. console.log(stagesToClassify);
  431. $.each($(stagesToClassify), function (key, value) {
  432. currStage = $("td:first-of-type", this).text(); // Picking the name of the stage
  433. if (currStage === ""){ // Checking wheter or not the name is blank to avoid unnecessary processing.
  434. $(this).remove();
  435. console.warn("Removing empty line at row " + key);
  436. }else {
  437. stageData = findStage(currStage); // Finding the data for the stage (in stagesDict)
  438. // console.log("Processing stage: " + currStage + " // results: [" + stageData + "]");
  439. if(stageData !== 0){ // Processing further only if some data is found.
  440. if (stageData[stageCountry] !== undefined){ // Checking if the country from stageData exists, if not -> apply ROW class)
  441. $(this).addClass(stageData[stageCountry]);
  442. // console.log("%cProcessing succesful, added class: " + countriesDict[stageData[stageCountry]], 'background: #B2FFB2; color: #233323');
  443. $("td:first", this).append("<br/> (" + stageData[stageSurface] + ", " + stageData[stageDistance] + ")");
  444. }else{ // No country data found - apllying the default class (ROW)
  445. console.warn("No country entry for: " + stageData[stageCountry]);
  446. $(this).addClass("ROW");
  447. }
  448. // Adding a unique id to every stage to quickly find it by id later.
  449. $(this).attr("id", "trid" + stageData[stageID]);
  450. isStageOnThePage[stageData[stageID]] = "true"; // Adding the info about the stage being on the page to the array. TODO: Find out why its not applying.
  451.  
  452. }else { // No stage data found - applying the default class (ROW)
  453. console.warn("No stage data for: " + currStage);
  454. $(this).addClass("ROW");
  455. }
  456. }
  457. });
  458. // Diagnostic - pring the list of stages visible on the page.
  459. console.log(isStageOnThePage);
  460. }
  461.  
  462. // Function searching for and adding missing stages.
  463. function addMissingStages(){
  464. var selectedStage;
  465. var tableType = parseurl("type"); // Parsing url for "type".
  466. if ( tableType === "3" || whereAmI == "urank" ) {
  467. tableType = "ranks"; // "ranks" are on "tstats" with type 3 and uranks.
  468. } else {
  469. tableType = "records"; // otherwise: default to records.
  470. }
  471. console.log("Adding missing stages...");
  472. var topnavBar = $(".topnav");
  473. $(topnavBar[0]).attr("width", "13%");
  474. $(topnavBar[2]).attr("width","13%");
  475. $(topnavBar[0]).html('<div class="meter"><span style="width: 1%"></span></div>');
  476. progressBar.location = $(".meter");
  477. for (var index = 0; index < stagesDict.length; index++){
  478. selectedStage = stagesDict[index];
  479. if (selectedStage[stageCountry] !== "Test"){ // Not adding the "test" stages.
  480. if ($("#trid"+selectedStage[stageID]).length === 0){
  481. console.log("Found missing stage. Adding info.");
  482. console.log(selectedStage);
  483. resultsTable.append(createStage(selectedStage[stageName], selectedStage[stageID], linkBase, "row2")); // Adding the row with the missing stage info + links.
  484. progressBar.requests++;
  485. getStageData(selectedStage[stageID], tableType); // Filling with data. TODO: add a switch to turn off filling with data.
  486. } else {
  487. // console.log("Stage "+ selectedStage[stageName] +" already exists. Skipping!");
  488. }
  489. }
  490. }
  491. console.log("Finished adding missing stages.");
  492. }
  493.  
  494. // Function creatin a row for the stage in the stages table. Takes innerHTML for each column.
  495. function createStage( nazwaos , stageid , base , rowstyl , column2 , column3 , column4 , column5 ) {
  496. var rowTemp = document.createElement("tr");
  497. var komTemp = document.createElement("td");
  498. rowTemp.setAttribute("class", rowstyl);
  499. komTemp.innerHTML = '<a href="http://rbr.onlineracing.cz/index.php?act=' + base[0] + '&stageid=' + stageid + '&classid=' + base[1] + '&state=' + base[2] + '">' + nazwaos + '</a>';
  500. rowTemp.appendChild(komTemp);
  501. var tdcount;
  502. var tempTD;
  503. if (whereAmI == "urec" || whereAmI == "urank") {tdcount = 5; } else {tdcount = 4;}
  504. for(var i = 1; i<tdcount; i++) {
  505. tempTD = document.createElement("td");
  506. tempTD.innerHTML = "";
  507. if (i === 1 && typeof column2 !== "undefined") { tempTD.innerHTML = column2; }
  508. if (i === 2) {
  509. tempTD.setAttribute("align", "center"); // 3, 4 and 5th column are aligned to the center of the cell.
  510. if (typeof column3 !== "undefined") { tempTD.innerHTML = column3; }; // Fill with data only if data was provided.
  511. }
  512. if (i === 3) {
  513. tempTD.setAttribute("align", "center");
  514. if (typeof column4 !== "undefined") { tempTD.innerHTML = column4; };
  515. }
  516. if (i === 4) {
  517. tempTD.setAttribute("align", "center");
  518. if (typeof column5 !== "undefined") { tempTD.innerHTML = column5; };
  519. }
  520. rowTemp.appendChild(tempTD);
  521. }
  522. return rowTemp;
  523. }
  524.  
  525. // Function creating a "subtitle", coloured breaker in the stages list descripting the country/region.
  526. function createSub ( subtitle , country, flag ) {
  527. var rowSub = document.createElement("tr");
  528. rowSub.setAttribute("class", country + " header");
  529. //Adding an option to collapse rows until next country
  530. $(rowSub).click(function(){
  531. if ($(this).next().hasClass("hidden")){
  532. $(this).nextUntil('tr.header').removeClass("hidden");
  533. countriesHidden[country] = "false";
  534. // $(this).nextUntil('tr.header').toggle();
  535. } else {
  536. $(this).nextUntil('tr.header').addClass("hidden");
  537. countriesHidden[country] = "true";
  538. // console.log(countriesHidden);
  539. }
  540. GM_setValue("countriesHiddenSaved", JSON.stringify(countriesHidden));
  541. console.log(JSON.stringify(countriesHidden));
  542. });
  543. var thSub = document.createElement("th");
  544. if (whereAmI === "tstats") { thSub.setAttribute("colspan", "4"); // On global stats page the table has one less column.
  545. } else {thSub.setAttribute("colspan", "5");} // Merge 5 columns on users stat page.
  546. thSub.innerHTML = subtitle;
  547. if (flag !== undefined) {
  548. var flagIcon = document.createElement("IMG");
  549. flagIcon.src = flag;
  550. flagIcon.setAttribute("style", "float: left; margin-left: 3px; border: 1px solid black;");
  551. flagIcon.setAttribute("alt", subtitle);
  552. flagIcon.setAttribute("title", subtitle);
  553. var flagIcon2 = $(flagIcon).clone();
  554. flagIcon.style="float: right; margin-right: 3px; border: 1px solid black;";
  555. thSub.appendChild(flagIcon);
  556. $(thSub).append(flagIcon2);
  557. }
  558. // Appending contents to the row
  559. rowSub.appendChild(thSub);
  560.  
  561. return rowSub;
  562. }
  563.  
  564. // A simple function adding a missing GT class to filter list in results of events using legacy physics.
  565. function addGTLink(){
  566. var links = document.getElementsByTagName("a");
  567.  
  568. for (var i = 0; i < links.length; i++) {
  569. var isLastClass = links[i].href.indexOf("&class=11");
  570.  
  571. if (isLastClass > -1 && links[i].text == "H"){
  572. var gt = document.createElement("a");
  573. gt.href = links[i].href.replace("&class=11","&class=14");
  574. gt.innerHTML = "GT";
  575. gt.style.position = "relative";
  576. gt.style.left = "-5px";
  577. gt.style.top = "0px";
  578. // Appending the creating link to the end of the listed classes.
  579. links[i].parentNode.appendChild(gt);
  580. }
  581. }
  582. }
  583.  
  584. // Function getting the missing data for a stage.
  585. // TODO: rewrite the function so that it returns the result instead of writing to global var.
  586. function getStageData( idToCheck, tableType ){
  587. var myResult = {}; // Object with "my result", to be populated with data.
  588. var bestTime;
  589. var regexCar = /.*<br>\s*([^\n\r]*)/;
  590. var regexPlace = /[(](\d*)\//;
  591. var carCell, myTimeCell, bestTimeCell, myPlaceCell;
  592. var resultsTableXHR, bestTimeRow;
  593. var selectedClass = $("#classid").val(); // Read the value of currently selected class.
  594. var selectedState = $("#state").val(); // Read the value of currently selected country.
  595. if (selectedState === undefined) {selectedState = ""}; // If state is undefined - replace it with an empty string (for safety).
  596. if (tableType == "records"){
  597. var act = "stagerec";
  598. } else { var act = "stagerank"; }
  599.  
  600. GM_xmlhttpRequest ({
  601. method: "GET",
  602. url: "http://rbr.onlineracing.cz/index.php?act="+ act + "&stageid=" + idToCheck + "&classid=" + selectedClass + "&state=" + selectedState,
  603. headers: {
  604. "User-Agent": "Mozilla/5.0", // If not specified, navigator.userAgent will be used.
  605. "Accept": "text/xml" // If not specified, browser defaults will be used.
  606. },
  607. timeout: 15000,
  608. ontimeout: function(){
  609. console.log("Request timed out! ID: " + idToCheck);
  610. progressBar.increment(); // TODO: log errors/timeouts and display info in progressbar.
  611. },
  612. onload: function(response) {
  613. var stageRow = {};
  614. // Finding the stagerow that we are processing.
  615. stageRow.full = $("#trid"+idToCheck, resultsTable); // Finding the row.
  616. // Handling the case of world rankings (not user's).
  617. if (whereAmI === "tstats") {
  618. // Finding the target cells with the car name, time and place.
  619. stageRow.car = $("td:nth-of-type(3)", stageRow.full);
  620. stageRow.myTime = null; // No place for user's time.
  621. stageRow.myPlace = null;
  622. stageRow.bestDriver = $("td:nth-of-type(2)", stageRow.full);
  623. stageRow.bestTime = $("td:nth-of-type(4)", stageRow.full);
  624. } else {
  625. // User rankings (non-tstats)
  626. // Finding the target cells with the car name, time and place.
  627. stageRow.car = $("td:nth-of-type(2)", stageRow.full);
  628. stageRow.myTime = $("td:nth-of-type(3)", stageRow.full);
  629. stageRow.bestTime = $("td:nth-of-type(4)", stageRow.full);
  630. stageRow.myPlace = $("td:nth-of-type(5)", stageRow.full);
  631. stageRow.bestDriver = null; // No place for the best driver on the page.
  632. // Finding the box with users results.
  633. var resultBox;
  634. resultBox = $('span[style="font-size:14px;"]', response.responseText); // Found by unique combination of span attributes.
  635. if (resultBox.length === 0) { // If user is not logged in there is no data to traverse.
  636. // console.log("User not logged in! ");
  637. myResult.car = "";
  638. myResult.myTime = "--:--:--";
  639. myResult.place = "---";
  640. } else { // Fill out the "My result" data only if user is logged in.
  641. // Finding the car name via regex (string after <br> in the span)
  642. myResult.carFull = regexCar.exec( resultBox[0].innerHTML ); // Holding full regex results in an object.
  643. if (myResult.carFull !== null) { // Add car only if a record is present. Otherwise -> skip this and the time+place steps.
  644. // Finding the link with the time in the resultbox
  645. myResult.car = myResult.carFull[1]; // Easily outputable car name (plaintext).
  646. myResult.myTime = $("a", resultBox); // Easily appendable link with best time.
  647. // Finding the place in the ranking via regex.
  648. myResult.place = regexPlace.exec( resultBox[0].innerHTML)[1] + ".";
  649. } else {
  650. myResult.myTime = "--:--:--";
  651. myResult.place = "---";
  652. myResult.car = "";
  653. }
  654. }
  655. }
  656. // Finding the row with the record time + cell with the time (link) itself.
  657. resultsTableXHR = $("#state", response.responseText).nextUntil("table").next(); // Results table, beginning with the enclosing table.
  658. // Cannot be done simpler, because the table we're looking for does not have any unique attributes.
  659. resultsTableXHR = $('table[width="100%"]', resultsTableXHR); // Finding the correct table inside it's parent.
  660. bestTimeRow = $("tr:nth-of-type(2)", resultsTableXHR); // Second row of the table is the one we are looking for.
  661. // Adding the world's best time to the myResult object.
  662. myResult.bestDriver = $("td:nth-of-type(2)", bestTimeRow).children();
  663. myResult.bestCar = $("td:nth-of-type(3)", bestTimeRow).html(); // Not children, because the car is in plaintext.
  664. myResult.bestTime = $("td:nth-of-type(4)", bestTimeRow).children();
  665. // If no best time set - fill the object with the "no time" text. The rest of the fields will just remain empty.
  666. if (myResult.bestTime.length === 0){ myResult.bestTime = "--:--:--";}
  667. console.log(myResult);
  668. if (myResult.myTime === null || myResult.myTime === undefined) {
  669. //Tstats page (world records)
  670. stageRow.bestDriver.append(myResult.bestDriver);
  671. stageRow.car.removeAttr("align");
  672. stageRow.car.append(myResult.bestCar);
  673. } else {
  674. // User records page.
  675. stageRow.myTime.append(myResult.myTime);
  676. stageRow.myPlace.append(myResult.place);
  677. stageRow.car.append(myResult.car);
  678. }
  679. stageRow.bestTime.append(myResult.bestTime); // Appending the insides of the 4th column to the cell.
  680. //Increment the count of coompleted requests (for visuals).
  681. progressBar.increment();
  682. }
  683. });
  684. }
  685.  
  686. // Changes the default "Records" and "Ranks" links to include class set as favourite. Used also to redraw the link after setting a new favourite class.
  687. function replaceDefaultRecordLinks(){
  688. var leftMenuTable = $('table[style="width: 170px; vertical-align: top;"]'); // Finding the side menus-table with the links.
  689. var recordsLink = $('a:contains("Records")'); // Finding the "Records" links.
  690. // favClass = 102;
  691. if (recordsLink.length === 0) {recordsLink = $('a:contains("Rekordy")');} // If not found, looking for the Czech link names.
  692. if (recordsLink.length === 0) {return false;} // Breaking out of the function if no links found anyway.
  693. for (var i=0; i<recordsLink.length; i++){ // Iterating through the array of links.
  694. if (recordsLink[i].href.indexOf("http://rbr.onlineracing.cz/index.php?act=urec") > -1){ // Check fo left-menu link.
  695. if (favClass !== undefined && favClass !== 0){ // Check if favClass set.
  696. recordsLink[i].href = "http://rbr.onlineracing.cz/index.php?act=urec&classid=" + favClass; // Appending the favClass to the link.
  697. } else {recordsLink[i].href = "http://rbr.onlineracing.cz/index.php?act=urec";} // If no favClass = inserting the vanilla link.
  698. } else {
  699. if (recordsLink[i].href.indexOf("http://rbr.onlineracing.cz/index.php?act=tstats&type=1") > -1){ // Checking for the right-menu link if not left-menu.
  700. if (favClass !== undefined && favClass !== 0){ // favClass check.
  701. recordsLink[i].href = "http://rbr.onlineracing.cz/index.php?act=tstats&type=1&classid=" + favClass; // Changing to the favClass link.
  702. } else {recordsLink[i].href = "http://rbr.onlineracing.cz/index.php?act=tstats&type=1";} // No favClass - vanilla link.
  703. }
  704. }
  705. }
  706. // console.log(recordsLink);
  707. }
  708.  
  709. // Function adding the newer classes to the selector. Uses JQUERY!
  710. function appendClasses(){
  711. //Finding the selector with id classid
  712. var classBox = $("#classid");
  713. // Clearing the default options
  714. $("option", classBox).remove();
  715. // Populating the box with the new layout.
  716. classBox.append($('<option>', {value: 0,text: "Select class", disabled: true}));
  717. classBox.append($('<option>', {value: 0,text: "-- NGP --", disabled: true}));
  718. classBox.append($('<option>', {value: classesDict["RC1"],text: "RC1"}));
  719. classBox.append($('<option>', {value: classesDict["RC2"],text: "RC2"}));
  720. classBox.append($('<option>', {value: classesDict["RGT"],text: "RGT"}));
  721. classBox.append($('<option>', {value: classesDict["RC3"],text: "RC3"}));
  722. classBox.append($('<option>', {value: classesDict["RC4"],text: "RC4"}));
  723. classBox.append($('<option>', {value: classesDict["RC5"],text: "RC5"}));
  724. classBox.append($('<option>', {value: classesDict["WRC"],text: "WRC"}));
  725. classBox.append($('<option>', {value: classesDict["H/B"],text: "H/B"}));
  726. classBox.append($('<option>', {value: classesDict["H/A"],text: "H/A"}));
  727. classBox.append($('<option>', {value: classesDict["H/4"],text: "H/4"}));
  728. classBox.append($('<option>', {value: classesDict["H/2"],text: "H/2"}));
  729. classBox.append($('<option>', {value: 0,text: "-- Legacy --", disabled: true}));
  730. classBox.append($('<option>', {value: classesDict["WRC legacy"],text: "WRC legacy"}));
  731. classBox.append($('<option>', {value: classesDict["N4"],text: "N4"}));
  732. classBox.append($('<option>', {value: classesDict["S2000"],text: "S2000"}));
  733. classBox.append($('<option>', {value: classesDict["S1600"],text: "S1600"}));
  734. classBox.append($('<option>', {value: classesDict["A8"],text: "A8"}));
  735. classBox.append($('<option>', {value: classesDict["A7"],text: "A7"}));
  736. classBox.append($('<option>', {value: classesDict["A6"],text: "A6"}));
  737. classBox.append($('<option>', {value: classesDict["A5"],text: "A5"}));
  738. classBox.append($('<option>', {value: classesDict["A7"],text: "A7"}));
  739. classBox.append($('<option>', {value: classesDict["N3"],text: "N3"}));
  740. classBox.append($('<option>', {value: classesDict["H"],text: "H"}));
  741. classBox.append($('<option>', {value: classesDict["GT"],text: "GT"}));
  742.  
  743. // Checking what class should be set as currently selected in the selectbox.
  744. var currClass = parseurl("classid");
  745. // If current url without a specified class - revert to the default, WRC legacy.
  746. // Not very efficient, as it does not break after finding a correct match (iterates through the entire list every time.
  747. if (currClass === "") { classBox.val(classesDict["WRC legacy"]); }
  748. else {
  749. for(var classKey in classesDict){
  750. if (classesDict[classKey] == currClass) {
  751. classBox.val(classesDict[classKey]);
  752. }
  753. }
  754. }
  755. }
  756.  
  757. // Function sorting the stages according to the country code
  758. function sortStagesByCountry(){
  759. console.log("Starting sorting...");
  760. var currentSelection = 0;
  761. for (var i = 0; i < countriesSortOrder.length; i++) {
  762. console.log("Moving " + countriesSortOrder[i][1] + "...");
  763. currentSelection = $("."+countriesSortOrder[i][0], resultsTable);
  764. currentSelection.sort(function (a, b) {
  765.  
  766. var A = $(a).find('td').eq(0).text().toUpperCase();
  767. /*console.log($(a).find('td').eq(0).text().toUpperCase());*/
  768. var B = $(b).find('td').eq(0).text().toUpperCase();
  769.  
  770. if(A < B) {return -1;}
  771. if(A > B) {return 1;}
  772. return 0;
  773. });
  774. resultsTable.append(currentSelection);
  775. }
  776. console.log("Sorting finished!");
  777. }
  778.  
  779. // Inserting the colorfull "subtitles" with country names and flags in front of the stages.
  780. // Using the rbr-czech flags to minimize overhead where possible :)
  781. function addSubtitles(){
  782. console.log("Adding subtitles...");
  783. var newSub = 0;
  784. for (var i = 0; i < countriesSortOrder.length; i++) {
  785. console.log("Subbing " + countriesSortOrder[i][1] + "...");
  786. newSub = createSub(countriesSortOrder[i][1], countriesSortOrder[i][0], countriesSortOrder[i][2]);
  787. $(newSub).insertBefore($("."+countriesSortOrder[i][0]+":first", resultsTable));
  788. }
  789. console.log("Subtitles added!");
  790. }
  791.  
  792. // Applying correct classes to all the rows (row2/row3) to "paint" them the correct colour after sorting.
  793. function reClassifyRows(){
  794. console.log("Repainting the rows...");
  795. $.each($(".row2, .row3", resultsTable), function (key, value) {
  796. $(this).removeClass("row2");
  797. $(this).removeClass("row3");
  798. $(this).addClass("row"+ (2+key%2));
  799. });
  800. console.log("Repainting finished!");
  801. }
  802.  
  803. // Checking and hiding the rows according to the saved data. Done at the begining of the pageload + on demand (via a button).
  804. function reHideStageRows(){
  805. console.log("Hiding the hidden rows...");
  806. var stagesRowsOnly = $(".row2, .row3", resultsTable); // Selecting only the inside rows with stages.
  807. var currentCountry = 0;
  808. var currentCountryName = 0;
  809. $.each(stagesRowsOnly, function (key, value) { // First un-hiding all the rows (resetting the field).
  810. $(this).removeClass("hidden");
  811. });
  812. for (var i = 0; i < countriesSortOrder.length; i++) { // Iterating through all the countries to check if they should be hidden or not.
  813. currentCountry = countriesSortOrder[i][0];
  814. currentCountryName = countriesSortOrder[i][1];
  815. // console.log("Current country: " + currentCountry + ". Hidden: "+ countriesHidden[currentCountry]);
  816. if(countriesHidden[currentCountry] === "true") { // If the country is marked as hidden - we hide it.
  817. $.each(stagesRowsOnly.filter("." + currentCountry), function (key, value){ // Filter the stage rows by current country
  818. $(this).addClass("hidden"); // Hide all rows with country class.
  819. });
  820. }
  821. }
  822. console.log("Hiding finished!");
  823. }
  824.  
  825. // Creating a drop-down menu with full list of stages available.
  826. function createStageDropdown(){
  827. var newSelect = document.createElement("select");
  828. newSelect.setAttribute("id", "stageid");
  829. newSelect.setAttribute("name", "stageid");
  830. newSelect.setAttribute("style", "width: 200px;");
  831. newSelect.setAttribute("onChange", "document.getElementById('records').submit()");
  832. var newSelectJQ = $(newSelect); // Just to easily use jQuery
  833. newSelectJQ.append($('<option>', {value: 0, text: "Choose another stage", disabled: true}));
  834. var currentCountry = 0;
  835. var currentCountryName = 0;
  836. var currentSelection = 0;
  837. for (var i = 0; i < countriesSortOrder.length; i++) {
  838. currentCountry = countriesSortOrder[i][0];
  839. currentCountryName = countriesSortOrder[i][1];
  840. console.log("Selector country: " + currentCountryName);
  841. currentSelection = $.grep(stagesDict, function( n ) {
  842. return ( n[stageCountry] == currentCountry);
  843. });
  844. if(currentSelection.length !== 0){
  845. newSelectJQ.append($('<option>', {value: 0, text: currentCountryName, disabled: true}));
  846. console.log("Found " + currentSelection.length + " stages [" + currentCountry + "]. Sorting and adding!");
  847. } else {
  848. console.log("No entries found for [" + currentCountry + "], skipping!");
  849. }
  850. currentSelection.sort(function (a, b) {
  851. var A = a[stageName].toUpperCase();
  852. var B = b[stageName].toUpperCase();
  853. if(A < B) {return -1;}
  854. if(A > B) {return 1;}
  855. return 0;
  856. });
  857. $.each($(currentSelection), function (key, value) {
  858. newSelectJQ.append($('<option>', {value: value[stageID],text: value[stageName]}));
  859. });
  860. }
  861. var currStage = parseurl("stageid");
  862. console.log("Current stage ID: " + currStage);
  863. // If current url without a specified track - revert to the default, "Select stage".
  864. // Not very efficient, as it does not break after finding a correct match (iterates through the entire list every time.
  865. if (currStage === "") { newSelectJQ.val(0); }
  866. else {
  867. for(var stageKey in stagesDict){
  868. if (stagesDict[stageKey][stageID] == currStage) {
  869. console.log(stagesDict[stageKey]);
  870. newSelectJQ.val(stagesDict[stageKey][stageID]);
  871. }
  872. }
  873. }
  874. //newSelect.selectedIndex = 0;
  875. console.log("Selector completed, returning!");
  876. var stateSelector = $("#state");
  877. console.log(stateSelector);
  878. console.log(newSelectJQ);
  879. $("input[name=stageid]", "#records").remove();
  880. $(newSelectJQ).insertBefore(stateSelector);
  881. //return newSelect;
  882. }
  883.  
  884. // Injecting styling into the document's head (for new classes and to override some older ones).
  885. function addStyling(){
  886. var css = document.createElement('style');
  887. css.type = 'text/css';
  888.  
  889. // var styles = 'tr.FI>td:first-of-type { background-color: yellow }';
  890. // styles += ' tr.CZ>td:first-of-type { text-align: right; }';
  891.  
  892. var styles = "tr.hidden { display: none }" +
  893. " #btnTopHolder {display: block; width: 300px; /*background-color: white;*/ min-height: 10px; overflow: hidden; margin-top: -10px;}" +
  894. " .btnTop { padding: 10px 5px; display:block; border: 2px solid #840000; color: white; float: left; width: calc(50% - 18px); margin: 0 2px; border-radius: 4px; cursor:pointer;}" +
  895. " .btnFAV { display: flex; width: 20px; height: 17px; padding: 0px 1px 2px 0px; align-items: center; justify-content: center; border: 2px solid #840000; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;color: white; border-radius: 4px; cursor:pointer;}" +
  896. " #btnSetFavClass { position: relative; left: 70px; top: -17px; }" +
  897. " .btnFAV.selected { border-color: #840000 !important; background-color: #840000; color: gold;}" +
  898. " .btnTop:active, .btnFAV:active, tr.header:active { transition: all 100ms ease; transform: scale(.95); opacity: .75;}" +
  899. " .btnTop:hover, .btnFAV:hover { transition: all 60ms ease; opacity: .85; background-color:#840000;}" +
  900. " .btnFAV.selected:hover {background-color: #840000; border-color: white !important; color: white;}" +
  901. " tr.header>th { cursor:pointer; background-color: #840000; background-image: none !important; color: white; font-weight: normal;}" +
  902. " tr.header:hover { transition: all 120ms ease; opacity: .85;}" +
  903. " .row2:hover, .row3:hover { background-color: #404040; transition: all 120ms ease; opacity: .85;}" +
  904. " .meter { color: black; text-align: center; height: 15px; position: relative;background: #555; -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; padding: 3px; margin: 0px 5px}" +
  905. " .meter > span { background-color: #f2b63c; border: 1px solid #f0ad24 #eba310 #c5880d; display: block; text-align: right; font-size: 11px; line-height: 15px; height: 100%; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-top-left-radius: 4px; border-bottom-left-radius: 4px; position: relative; overflow: hidden;}" +
  906. " span.meterdone { background-color: #85c440; border: 1px solid #78b337 #6ba031 #568128;}"
  907. "";
  908.  
  909.  
  910. if (css.styleSheet) css.styleSheet.cssText = styles;
  911. else css.appendChild(document.createTextNode(styles));
  912.  
  913. document.getElementsByTagName("head")[0].appendChild(css); // Apppending the style to the head.
  914. }
  915.  
  916. function addHiderButtons(){
  917. $("<div id=\"btnTopHolder\"/>").insertBefore(resultsTable);
  918. $("<br/>").insertBefore(resultsTable);
  919. // Creating a div that will serve as a button.
  920. var btnHideAll = document.createElement("div");
  921. btnHideAll.setAttribute("id", "btnHideAllStages"); // Unique ID for the button.
  922. btnHideAll.setAttribute("class", "btnTop"); // Styleable class.
  923. btnHideAll.setAttribute("title", "Click to collapse all countries"); // Hover text.
  924. btnHideAll.innerHTML = "Collapse all"; // Button text.
  925. var currentCountry = 0;
  926. // var currentCountryName = 0; // Temp vars to iterate easier.
  927. $(btnHideAll).click(function(){
  928. console.log("Collapsing all countries!");
  929. for (var i = 0; i < countriesSortOrder.length; i++) { // Iterating through every possible country.
  930. currentCountry = countriesSortOrder[i][0];
  931. // currentCountryName = countriesSortOrder[i][1]; // Not used ATM
  932. countriesHidden[currentCountry]="true"; // Changing the object parameter for each country.
  933. }
  934. GM_setValue("countriesHiddenSaved", JSON.stringify(countriesHidden)); // Saving the current state to JSON string.
  935. reHideStageRows(); // Basically adding the "hidden" class to all the rows.
  936. });
  937. var btnShowAll = document.createElement("div");
  938. btnShowAll.setAttribute("id", "btnHideAllStages"); // Unique ID for the button.
  939. btnShowAll.setAttribute("class", "btnTop"); // Styleable class.
  940. btnShowAll.setAttribute("title", "Click to expand all countries"); // Hover text.
  941. btnShowAll.innerHTML = "Expand all"; // Button text.
  942. $(btnShowAll).click(function(){ // Quick function cleaning the hide/expand data.
  943. console.log("Expanding all countries!");
  944. countriesHidden ={}; // Empty the object.
  945. // console.log(countriesHidden);
  946. GM_setValue("countriesHiddenSaved", "{}"); // Empty the saved value (replace with empty JSON string).
  947. reHideStageRows(); // Remove "hidden" class from all the rows.
  948. });
  949. // Inserting the buttons in front of the table, inside the holder-container..
  950. $("#btnTopHolder").prepend(btnHideAll);
  951. $("#btnTopHolder").prepend(btnShowAll);
  952. }
  953.  
  954. // Simple function adding "set as favourite" buttons
  955. function addFavouriteButtons(){
  956. var selectedClass = $("#classid").val(); // Checking the value of the selected car class.
  957. var btnSetFavClass = document.createElement("div");
  958. btnSetFavClass.setAttribute("id", "btnSetFavClass"); // Unique ID for the button.
  959. btnSetFavClass.setAttribute("class", "btnFAV"); // Styleable class.
  960. if (selectedClass == favClass) {
  961. $(btnSetFavClass).addClass("selected"); // If the currently selected class is set as favourite - paint the button accordingly.
  962. btnSetFavClass.setAttribute("title", "Click to remove favourite class"); // Hover text, favourite class.
  963. btnSetFavClass.innerHTML = "★"; // Button text for the selected favourite.
  964. } else {
  965. btnSetFavClass.setAttribute("title", "Click to set favourite class");// Hover text, non-favourite.
  966. btnSetFavClass.innerHTML = "⭐";// Button text
  967. }
  968. $(btnSetFavClass).click(function(){ // Quick function cleaning the hide/expand data.
  969. if (favClass != selectedClass) {
  970. console.log("Setting a favourite class! [" + selectedClass + "]");
  971. favClass = selectedClass;
  972. GM_setValue("favClass", favClass); // Save the favourite class value.
  973. $(btnSetFavClass).addClass("selected");
  974. btnSetFavClass.innerHTML = "★";
  975. btnSetFavClass.setAttribute("title", "Click to remove favourite class");
  976. } else {
  977. console.log("Removing favourite class! [" + favClass + "]");
  978. favClass = 0;
  979. GM_setValue("favClass", favClass);
  980. $(btnSetFavClass).removeClass("selected");
  981. btnSetFavClass.innerHTML = "⭐"; // Button text
  982. btnSetFavClass.setAttribute("title", "Click to set favourite class");
  983. }
  984. replaceDefaultRecordLinks(); // Replace the href of left- and right-side menu links.
  985. });
  986. $("#classid").after(btnSetFavClass);
  987. $(btnSetFavClass).next().remove();
  988. $(btnSetFavClass).next().remove();
  989. }
  990.  
  991. // ************************************ END OF FUNCTIONS ************************************
  992. // Processing the document itself
  993. // TODO: cleanup this section and organize it better.
  994. addStyling(); // Adding special styling
  995. console.log("Favourite class is: " + favClass + " // " + typeof(favClass));
  996. replaceDefaultRecordLinks();
  997.  
  998. // Checking for car class selector (drop-down). Appears on records and ranks sites.
  999. // If exists: appending the additional car classes and add the "Favourite" button.
  1000. if (document.getElementById("classid")) {
  1001. appendClasses();
  1002. addFavouriteButtons();
  1003. }
  1004.  
  1005. // Checking the type of handled site we are on (act=).
  1006. var whereAmI = parseurl("act");
  1007.  
  1008. // If we are NOT on the tournament results page: proceed with finding the results table, preparing the linkbase etc.
  1009. if (whereAmI.indexOf("tourmntres") == -1 && (whereAmI === "urank" || whereAmI == "urec" || whereAmI == "stagerec" || whereAmI == "stagerank" || whereAmI == "tstats")) {
  1010. var resultsTable = findResultsTableJQ();
  1011. addHeadAndBody();
  1012. convertStageCountries();
  1013. var countriesHidden = JSON.parse(GM_getValue("countriesHiddenSaved", "{}"));
  1014. console.log(typeof(countriesHidden));
  1015. console.log(countriesHidden);
  1016. var linkBase = makeLinkBase();
  1017. } else { addGTLink(); } // If on a page with records -> add the missing GT link
  1018.  
  1019. switch(whereAmI) {
  1020. case "urank":
  1021. case "tstats":
  1022. if (parseurl("type") !="1"){
  1023. //addMissingRanks();
  1024. }
  1025. case "urec":
  1026. // Find the correct table - globally.
  1027. classifyStages($("tr[class=row2], tr[class=row3]",resultsTable).not("[id^=trid]")); // Classify all the rows with country codes and track ids
  1028. addMissingStages();
  1029. classifyStages($("tr[class=row2], tr[class=row3]",resultsTable).not("[id^=trid]")); // Classify again, this time the added stages.
  1030. sortStagesByCountry();
  1031. addSubtitles();
  1032. reClassifyRows();
  1033. reHideStageRows();
  1034. addHiderButtons();
  1035. break;
  1036. case "stagerec":
  1037. case "stagerank":
  1038. createStageDropdown();
  1039. document.getElementById("state").parentNode.insertBefore(document.createElement("br"), document.getElementById("state"));
  1040. document.getElementById("state").parentNode.insertBefore(document.createElement("br"), document.getElementById("state"));
  1041. break;
  1042. default:
  1043. break;
  1044. }