GreasyFork 脚本更新对比工具

在 GreasyFork 脚本更新页面显示新旧版本差异

  1. // ==UserScript==
  2. // @name GreasyFork Script Diff Tool
  3. // @name:zh-CN GreasyFork 脚本更新对比工具
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.0
  6. // @description A tool to compare script versions on GreasyFork's update page
  7. // @description:zh-CN 在 GreasyFork 脚本更新页面显示新旧版本差异
  8. // @author ommo
  9. // @match https://greasyfork.org/*/scripts/*/versions/new
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_addStyle
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.2.0/diff.min.js
  13. // @require https://unpkg.com/diff2html/bundles/js/diff2html-ui.min.js
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. // 添加全局样式
  21. const link = document.createElement('link');
  22. link.rel = 'stylesheet';
  23. link.href = 'https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css';
  24. document.head.appendChild(link);
  25. GM_addStyle(`
  26. .diff-container {
  27. margin-top: 20px;
  28. max-height: 500px;
  29. overflow-y: auto;
  30. position: relative;
  31. }
  32. `);
  33.  
  34. // 获取当前最新版代码
  35. function fetchLatestCode(scriptId, callback) {
  36. const url = `https://greasyfork.org/scripts/${scriptId}/code`;
  37. GM_xmlhttpRequest({
  38. method: 'GET',
  39. url: url,
  40. onload: function (response) {
  41. const parser = new DOMParser();
  42. const doc = parser.parseFromString(response.responseText, 'text/html');
  43. const codeContainer = doc.querySelector('.code-container');
  44. if (!codeContainer) {
  45. console.error('Code container not found');
  46. alert('Failed to fetch latest code. Page structure may have changed.');
  47. return;
  48. }
  49. const code = codeContainer.textContent.trim();
  50. callback(code);
  51. },
  52. onerror: function (error) {
  53. console.error('Failed to fetch latest code:', error);
  54. alert('Failed to fetch latest code. Check script ID or network connection.');
  55. }
  56. });
  57. }
  58.  
  59. // 添加对比按钮
  60. function addCompareButton(scriptId) {
  61. const textarea = document.querySelector('textarea');
  62. const button = document.createElement('button');
  63. button.textContent = 'Diff';
  64. button.className = 'diff-button';
  65. button.style.marginTop = '10px';
  66. const diffContainer = document.createElement('div');
  67. diffContainer.className = 'diff-container';
  68.  
  69. button.addEventListener('click', function (event) {
  70. event.preventDefault();
  71.  
  72. button.disabled = true;
  73. button.textContent = 'Loading...';
  74.  
  75. const newCode = textarea.value.trim();
  76. if (!newCode) {
  77. alert('Please enter new code.');
  78. button.disabled = false;
  79. button.textContent = 'Diff';
  80. return;
  81. }
  82.  
  83. fetchLatestCode(scriptId, function (latestCode) {
  84. const fileName = `${document.querySelector('header > h2').textContent}.js`;
  85. const diffString = Diff.createPatch(fileName, latestCode, newCode);
  86. const targetElement = diffContainer;
  87. const configuration = { drawFileList: false, fileListToggle: false };
  88. const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
  89. diff2htmlUi.draw();
  90.  
  91. button.disabled = false;
  92. button.textContent = 'Diff';
  93. });
  94. });
  95.  
  96. textarea.parentNode.append(button, diffContainer);
  97. }
  98.  
  99. // 主逻辑
  100. function main() {
  101. const scriptId = window.location.pathname.match(/\/scripts\/(\d+)\/versions\/new/)[1];
  102. if (!scriptId) {
  103. console.error('Script ID not found');
  104. return;
  105. }
  106. addCompareButton(scriptId);
  107. }
  108.  
  109. // 启动
  110. main();
  111. })();