MCP :: SRG Mapper

Apply SRG mappings to code.

  1. /*jslint devel: true, browser: true, regexp: true, continue: true, plusplus: true, maxerr: 50, indent: 4 */
  2. /*global unsafeWindow: false, $: false, toastr: false, JSZip: false */
  3. /*global GM_setValue: false, GM_getValue: false, GM_listValues: false, GM_deleteValue: false, GM_addStyle: false, GM_getResourceText: false, GM_xmlhttpRequest: false */
  4.  
  5. // ==UserScript==
  6. // @name MCP :: SRG Mapper
  7. // @namespace Lunatrius
  8. // @description Apply SRG mappings to code.
  9. // @author Lunatrius <lunatrius@gmail.com>
  10. // @include https://github.com/MinecraftForge/FML*
  11. // @include https://github.com/MinecraftForge/MinecraftForge*
  12. // @include https://github.com/MinecraftForge/FML/pull/*/files
  13. // @include https://github.com/MinecraftForge/MinecraftForge/pull/*/files
  14. // @include https://gist.github.com/*/*
  15. // @include http://openeye.openmods.info/crashes/*
  16. // @include https://paste.ee/*
  17. // @include http://paste.ee/*
  18. // @include http://paste.feed-the-beast.com/view/*
  19. // @include http://pastebin.com/*
  20. // @exclude https://paste.ee/
  21. // @exclude http://paste.ee/
  22. // @exclude http://pastebin.com/
  23. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js
  24. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.pjax/1.9.2/jquery.pjax.min.js
  25. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js
  26. // @require https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.0/js/toastr.min.js
  27. // @require https://cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.jquery.min.js
  28. // @resource toast_css https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.0/css/toastr.css
  29. // @resource chosen_css https://cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.css
  30. // @version 0.1.11
  31. // @grant GM_setValue
  32. // @grant GM_getValue
  33. // @grant GM_listValues
  34. // @grant GM_deleteValue
  35. // @grant GM_addStyle
  36. // @grant GM_getResourceText
  37. // @grant GM_xmlhttpRequest
  38. // @grant unsafeWindow
  39. // @license MIT
  40. // ==/UserScript==
  41.  
  42. (function (window) {
  43. "use strict";
  44.  
  45. try {
  46. if (window !== window.top) {
  47. return;
  48. }
  49.  
  50. $.fn.outerHtml = function () {
  51. return $("<tmp></tmp>").append(this).html();
  52. };
  53.  
  54. var mapper = {
  55. baseList: "http://export.mcpbot.bspk.rs/",
  56. baseMapping: "http://files.minecraftforge.net/maven/de/oceanlabs/mcp/",
  57. regexParse: /(func_[0-9]+_[a-z]+_?|field_[0-9]+_[a-z]+_?|p_i?[0-9]+_[0-9]+_?),([^,]+),/i,
  58. regexReplace: /\b(?:func_[0-9]+_[a-z]+_?|field_[0-9]+_[a-z]+_?|p_i?[0-9]+_[0-9]+_?)\b/gi,
  59.  
  60. regexMapping: /^(\d+(?:\.\d+)+):((stable)(?:_nodoc)?_(\d+)|(snapshot)(?:_nodoc)?_(\d{8}))$/,
  61.  
  62. mappingList: [],
  63. currentMapping: null,
  64. mappings: {},
  65.  
  66. updateIntervalList: 1000 * 3600 * 12,
  67. updateIntervalMapping: 1000 * 3600 * 24 * 365,
  68.  
  69. settings: {
  70. "github.com": {
  71. tag: "li",
  72. insert: "div.container ul.pagehead-actions",
  73. container: "#files table tr td.blob-code, .file .type-diff table tr td.blob-code, .inline-review-comment table tr td.blob-code"
  74. },
  75. "gist.github.com": {
  76. tag: "li",
  77. insert: "div.container>ul.pagehead-actions",
  78. container: "div.file>div.data table tr td.blob-code"
  79. },
  80. "openeye.openmods.info": {
  81. tag: "div",
  82. insert: "div.page-header",
  83. container: "span.highlight_method",
  84. css: [
  85. "#remap_container { float: right; }",
  86. "#remap_container a.minibutton { padding: 3px; border: 1px outset #DDDDDD; border-radius: 4px; cursor: pointer; }",
  87. "#remap_container a.minibutton.selected { padding: 3px; border: 1px inset #DDDDDD; border-radius: 4px; cursor: pointer; }"
  88. ].join("\n")
  89. },
  90. "paste.ee": {
  91. tag: "span",
  92. insert: ".container>.row>.row>.col-sm-12:has(#download)",
  93. container: "#paste-content ol li div",
  94. css: [
  95. "#remap_container { float: right; }",
  96. "#remap_container a.minibutton { padding: 3px; border: 1px outset #DDDDDD; border-radius: 4px; cursor: pointer; }",
  97. "#remap_container a.minibutton.selected { padding: 3px; border: 1px inset #DDDDDD; border-radius: 4px; cursor: pointer; }"
  98. ].join("\n")
  99. },
  100. "paste.feed-the-beast.com": {
  101. tag: "span",
  102. insert: "div.col-lg-12>div.detail.by:eq(0)",
  103. container: ".CodeMirror ol li div",
  104. css: [
  105. "#remap_container { float: right; }",
  106. "#remap_container a.minibutton { padding: 3px; border: 1px outset #DDDDDD; border-radius: 4px; cursor: pointer; }",
  107. "#remap_container a.minibutton.selected { padding: 3px; border: 1px inset #DDDDDD; border-radius: 4px; cursor: pointer; }"
  108. ].join("\n")
  109. },
  110. "pastebin.com": {
  111. tag: "span",
  112. insert: "#code_frame #code_buttons",
  113. container: "#code_frame ol li span, #code_frame ol li div",
  114. css: [
  115. "#remap_container { float: right; margin-top: 4pt; }",
  116. "#remap_container a.minibutton { padding: 3px; border: 1px outset #DDDDDD; border-radius: 4px; cursor: pointer; }",
  117. "#remap_container a.minibutton.selected { padding: 3px; border: 1px inset #DDDDDD; border-radius: 4px; cursor: pointer; }"
  118. ].join("\n")
  119. }
  120. },
  121.  
  122. setCurrentMapping: function (mc, type, version) {
  123. if (type === undefined && version === undefined) {
  124. var mapping = this.parseMapping(mc);
  125. this.currentMapping = mapping[0] ? mapping[0] + ":" + mapping[1] + "_" + mapping[2] : null;
  126. } else {
  127. this.currentMapping = mc + ":" + type + "_" + version;
  128. }
  129.  
  130. return this.currentMapping;
  131. },
  132.  
  133. parseMapping: function (mapping) {
  134. var m = mapping && mapping.match(/^(\d+(?:\.\d+)+):((stable)(?:_nodoc)?_(\d+)|(snapshot)(?:_nodoc)?_(\d{8}))$/);
  135. return m ? [m[1], m[3] || m[5], m[4] || m[6]] : [null, null, null];
  136. },
  137.  
  138. set: function (key, val) {
  139. GM_setValue(key, JSON.stringify(val));
  140. },
  141.  
  142. get: function (key, def) {
  143. try {
  144. return JSON.parse(GM_getValue(key, def)) || def;
  145. } catch (ignore) {}
  146. return def;
  147. },
  148.  
  149. list: function () {
  150. return GM_listValues();
  151. },
  152.  
  153. del: function (key) {
  154. GM_deleteValue(key);
  155. },
  156.  
  157. clean: function () {
  158. $.each(this.list(), function (index, value) {
  159. this.del(value);
  160. }.bind(this));
  161. },
  162.  
  163. time: function () {
  164. return new Date().getTime();
  165. },
  166.  
  167. init: function () {
  168. var css = [
  169. GM_getResourceText("toast_css"),
  170. GM_getResourceText("chosen_css")
  171. ];
  172.  
  173. this.settings = this.settings[location.host];
  174. if (!this.settings) {
  175. GM_addStyle(css.join("\n"));
  176. toastr.info("No settings for " + location.host + "... :(");
  177. return this;
  178. }
  179.  
  180. if (this.settings.css) {
  181. css = css.concat(this.settings.css);
  182. }
  183.  
  184. GM_addStyle(css.join("\n"));
  185.  
  186. toastr.options.closeButton = true;
  187. toastr.options.showMethod = "slideDown";
  188. toastr.options.hideMethod = "slideUp";
  189. toastr.options.preventDuplicates = true;
  190.  
  191. this.setCurrentMapping(this.get("selected", ""));
  192.  
  193. this.mappingList = this.get("mappings", []);
  194.  
  195. try {
  196. var match = $("body").text().match(/(?:Minecraft|Minecraft Version:) (\d+\.\d+(?:\.\d+)?)/);
  197. if (match) {
  198. var split = this.currentMapping.split(/:/);
  199.  
  200. if (match[1] !== split[0]) {
  201. var found = false;
  202. $.each(this.mappingList, function (index, version) {
  203. var split = version.split(/:/);
  204. if (match[1] === split[0]) {
  205. found = version;
  206. return false;
  207. }
  208. });
  209.  
  210. if (found !== false) {
  211. toastr.info("Switching mappings to " + found);
  212. this.setCurrentMapping(found);
  213. this.set("selected", this.currentMapping);
  214. } else {
  215. toastr.warning("Detected Minecraft " + match[1] + " but no mappings available!");
  216. }
  217. }
  218. }
  219. } catch (ignore) {}
  220.  
  221. $.each(this.list(), function (index, version) {
  222. if (this.regexMapping.test(version) && $.inArray(version, this.mappingList) === -1) {
  223. toastr.warning("Removing old mappings...<br/>" + version);
  224. this.del(version);
  225. this.del(version + "_update");
  226. }
  227. }.bind(this));
  228.  
  229. try {
  230. this.main();
  231. } catch (e) {
  232. console.log(e);
  233. }
  234.  
  235. delete this.init;
  236.  
  237. return this;
  238. },
  239.  
  240. main: function () {
  241. this.update();
  242.  
  243. $("<" + this.settings.tag + ">")
  244. .attr("id", "remap_container")
  245. .append($("<select></select>").change(this.changeMapping.bind(this)))
  246. .append(" ")
  247. .append($("<a></a>").addClass("minibutton btn btn-sm").text("Remap").click(this.remap.bind(this)))
  248. .prependTo(this.settings.insert);
  249.  
  250. this.populateMappings();
  251. },
  252.  
  253. update: function () {
  254. if (this.get("mappings_update", 0) + this.updateIntervalList < this.time()) {
  255. toastr.info("Fetching mapping list...");
  256. this.query(this.baseList + "versions.json?limit=5", this.saveMappingList.bind(this), this.updateMapping.bind(this));
  257. return true;
  258. } else {
  259. return this.updateMapping();
  260. }
  261. },
  262.  
  263. updateMapping: function () {
  264. if (this.setCurrentMapping(this.currentMapping) !== null) {
  265. if (this.get(this.currentMapping + "_update", 0) + this.updateIntervalMapping < this.time()) {
  266. toastr.info("Fetching mappings: " + this.currentMapping);
  267. var mapping = this.parseMapping(this.currentMapping);
  268. this.query(this.mappingUrl(mapping[0], mapping[1], mapping[2]), this.saveMappings.bind(this), function () {
  269. $(this.settings.container).each(this.doRemap.bind(this));
  270. }.bind(this));
  271. return true;
  272. } else {
  273. this.mappings = this.get(this.currentMapping, []);
  274. return false;
  275. }
  276. } else {
  277. toastr.info("No mappings selected.");
  278. }
  279. return false;
  280. },
  281.  
  282. changeMapping: function (event) {
  283. this.setCurrentMapping($(event.target).val());
  284. this.set("selected", this.currentMapping);
  285. },
  286.  
  287. remap: function (event) {
  288. var target = $(event.target);
  289.  
  290. if (target.hasClass("selected")) {
  291. target.removeClass("selected");
  292.  
  293. $(".processed").removeClass("processed").find("u[title]").each(this.undoRemap);
  294. } else {
  295. target.addClass("selected");
  296.  
  297. this.setCurrentMapping($("#remap_container select").val());
  298. this.mappings = this.get(this.currentMapping, []);
  299.  
  300. if (!this.update()) {
  301. $(this.settings.container).each(this.doRemap.bind(this));
  302. }
  303. }
  304.  
  305. $("#remap_container .chosen-container").css("z-index", 9001).css("display", target.hasClass("selected") ? "none" : "");
  306. },
  307.  
  308. undoRemap: function (index, node) {
  309. $(node).replaceWith($(node).attr("title"));
  310. },
  311.  
  312. doRemap: function (index, node) {
  313. node = $(node);
  314. if (!node.hasClass("processed")) {
  315. var html = node.html();
  316. if (this.regexReplace.test(html)) {
  317. node.html(html.replace(this.regexReplace, this.applyRemap.bind(this))).addClass("processed");
  318. }
  319. }
  320. },
  321.  
  322. applyRemap: function (token) {
  323. var mcpname, node;
  324.  
  325. mcpname = this.mappings[token];
  326. node = $("<u></u>").attr("title", token);
  327.  
  328. if (!mcpname) {
  329. node.css("font-style", "italic");
  330. }
  331.  
  332. return node.text(mcpname || token).outerHtml();
  333. },
  334.  
  335. populateMappings: function () {
  336. $("#remap_container select").html("");
  337. this.mappingList.sort(this.cmpMapping.bind(this));
  338. $.each(this.mappingList, function (index, version) {
  339. $("<option></option>").val(version).text(version).appendTo("#remap_container select");
  340. });
  341. $("#remap_container select").val(this.currentMapping).chosen();
  342. },
  343.  
  344. cmpMapping: function (a, b) {
  345. a = a.split(/[:_]/i);
  346. b = b.split(/[:_]/i);
  347.  
  348. if (a[0] != b[0]) {
  349. return this.cmpVersion(a[0], b[0]);
  350. }
  351.  
  352. if (a[1] != b[1]) {
  353. return a[1] < b[1];
  354. }
  355.  
  356. if (a[2] != b[2]) {
  357. return a[2] < b[2];
  358. }
  359.  
  360. return 0;
  361. },
  362.  
  363. cmpVersion: function (a, b) {
  364. a = a.split(/\./gi);
  365. b = b.split(/\./gi);
  366.  
  367. for (var i = 0 ; i < a.length && i < b.length; i++) {
  368. if (a[i] !== b[i]) {
  369. return parseInt(a[i]) < parseInt(b[i]);
  370. }
  371. }
  372.  
  373. return a.length < b.length;
  374. },
  375.  
  376. saveMappingList: function (data, callback) {
  377. var json = JSON.parse(data);
  378. this.mappingList = [];
  379.  
  380. $.each(json, function (mc, data) {
  381. $.each(data, function (type, versions) {
  382. $.each(versions, function (index, version) {
  383. this.mappingList.push(mc + ":" + type + "_" + version);
  384. }.bind(this));
  385. }.bind(this));
  386. }.bind(this));
  387.  
  388. this.set("mappings", this.mappingList);
  389. this.set("mappings_update", this.time());
  390.  
  391. toastr.success("Saved mapping list.");
  392.  
  393. this.populateMappings();
  394.  
  395. if (callback) {
  396. callback();
  397. }
  398. },
  399.  
  400. mappingUrl: function (mc, type, version) {
  401. return this.baseMapping + "mcp_" + type + "_nodoc/" + version + "-" + mc + "/mcp_" + type + "_nodoc-" + version + "-" + mc + ".zip";
  402. },
  403.  
  404. saveMappings: function (data, callback) {
  405. var zip, parse;
  406.  
  407. zip = JSZip(data);
  408.  
  409. parse = function (index, line) {
  410. var m = line.match(this.regexParse);
  411. if (m && m.length === 3) {
  412. this.mappings[m[1]] = m[2];
  413. }
  414. };
  415.  
  416. this.mappings = {};
  417. $.each(zip.file("methods.csv").asText().split("\n"), parse.bind(this));
  418. $.each(zip.file("fields.csv").asText().split("\n"), parse.bind(this));
  419. $.each(zip.file("params.csv").asText().split("\n"), parse.bind(this));
  420.  
  421. this.set(this.currentMapping, this.mappings);
  422. this.set(this.currentMapping + "_update", this.time());
  423.  
  424. toastr.success("Saved mappings.");
  425.  
  426. if (callback) {
  427. callback();
  428. }
  429. },
  430.  
  431. query: function (url, callback, innerCallback) {
  432. GM_xmlhttpRequest({
  433. url: url,
  434. method: "GET",
  435. // should work but doesn't...
  436. // responseType: "arraybuffer",
  437. overrideMimeType: "text/plain; charset=x-user-defined",
  438. onload: function (response) {
  439. try {
  440. callback(response.response, innerCallback);
  441. } catch (e) {
  442. toastr.error("Something went wrong!<br/>" + e.message);
  443. }
  444. }.bind(this)
  445. });
  446. }
  447. }.init();
  448. } catch (e) {
  449. toastr.error("Something went wrong!<br/>" + e.message + "<br/>" + e.fileName.replace(/^.*[\\\/]/, "") + ":" + e.lineNumber);
  450. console.error(e);
  451. }
  452. }(unsafeWindow));