Merge Dependabot PRs Automatically on GitHub with UI and Selection Options

Automatically clicks the merge button on Dependabot PRs and "Done" button on the notification bar

Szkript telepítése?
A szerző által javasolt szkript

Ez is érdekelhet: YouTubeTV Volume Control with Memory

Szkript telepítése
  1. // ==UserScript==
  2. // @name Merge Dependabot PRs Automatically on GitHub with UI and Selection Options
  3. // @namespace typpi.online
  4. // @version 2.3
  5. // @description Automatically clicks the merge button on Dependabot PRs and "Done" button on the notification bar
  6. // @author Nick2bad4u
  7. // @match https://github.com/*/*/pull/*
  8. // @grant none
  9. // @license UnLicense
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
  11. // @homepageURL https://github.com/Nick2bad4u/UserStyles
  12. // @supportURL https://github.com/Nick2bad4u/UserStyles/issues
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. const CHECK_INTERVAL = 1000; // Interval between checks in milliseconds
  19. let lastCheck = 0;
  20. let observer;
  21. let token = getTokenFromCookies() || promptForToken();
  22. if (!token) {
  23. while (!token) {
  24. token = prompt('Please enter your GitHub token:');
  25. if (token) {
  26. document.cookie = `github_token=${token}; path=/; max-age=${60 * 60 * 24 * 365}`;
  27. } else {
  28. alert('GitHub token is required.');
  29. }
  30. }
  31. }
  32.  
  33. if (!token) {
  34. alert('GitHub token is required for this script to work.');
  35. return;
  36. }
  37.  
  38. function getTokenFromCookies() {
  39. const match = document.cookie.match(/(^| )\s*github_token\s*=\s*([^;]+)/);
  40. return match ? match[2] : null;
  41. }
  42.  
  43. function promptForToken() {
  44. let token = prompt('Please enter your GitHub token. You can generate a token at https://github.com/settings/tokens');
  45. if (token) {
  46. document.cookie = `github_token=${token}; path=/; max-age=${60 * 60 * 24 * 365}`;
  47. }
  48. return token;
  49. }
  50.  
  51. const debounce = (func, delay) => {
  52. let debounceTimer;
  53. return function () {
  54. clearTimeout(debounceTimer);
  55. debounceTimer = setTimeout(() => func.apply(this, arguments), delay);
  56. };
  57. };
  58.  
  59. const debouncedCheckAndMerge = debounce(checkAndMerge, 300);
  60.  
  61. async function checkAndMerge() {
  62. const now = Date.now();
  63. if (now - lastCheck < CHECK_INTERVAL) return;
  64. lastCheck = now;
  65.  
  66. console.log('checkAndMerge function called');
  67.  
  68. try {
  69. const authorElement = document.querySelector('.author');
  70. if (authorElement && /^(dependabot\[bot\]|Nick2bad4u)$/.test(authorElement.textContent.trim())) {
  71. const prNumber = window.location.pathname.split('/').pop();
  72. const repoPath = window.location.pathname.split('/').slice(1, 3).join('/');
  73.  
  74. console.log('PR is created by dependabot or specified user, attempting to merge via API');
  75.  
  76. const mergeSuccess = await mergePR(repoPath, prNumber);
  77. if (mergeSuccess) {
  78. console.log('PR merged successfully');
  79. markAsDone();
  80. } else {
  81. console.log('Failed to merge PR');
  82. }
  83. } else {
  84. console.log('PR is not created by dependabot or specified user');
  85. }
  86. } catch (error) {
  87. console.error('Error in checkAndMerge:', error);
  88. }
  89. }
  90.  
  91. async function mergePR(repoPath, prNumber) {
  92. try {
  93. const response = await fetch(`https://api.github.com/repos/${repoPath}/pulls/${prNumber}/merge`, {
  94. method: 'PUT',
  95. headers: {
  96. Authorization: `token ${token}`,
  97. 'Content-Type': 'application/json',
  98. },
  99. body: JSON.stringify({
  100. commit_title: `Merge PR #${prNumber}`,
  101. merge_method: 'merge',
  102. }),
  103. });
  104.  
  105. if (response.ok) {
  106. return true;
  107. } else {
  108. const errorData = await response.json();
  109. console.error('Failed to merge PR:', errorData);
  110. return false;
  111. }
  112. } catch (error) {
  113. console.error('Error in mergePR:', error);
  114. return false;
  115. }
  116. }
  117.  
  118. async function markAsDone() {
  119. try {
  120. const notificationBar = document.querySelector('.js-flash-container');
  121. if (!notificationBar) {
  122. console.log('Notification bar not found');
  123. return;
  124. }
  125.  
  126. let doneButton = document.querySelector('button[aria-label="Done"].btn.btn-sm');
  127. if (!doneButton) {
  128. doneButton = Array.from(document.querySelectorAll('button.btn.btn-sm')).find(button => button.textContent.trim() === 'Done');
  129. }
  130. if (doneButton) {
  131. console.log('Done button found, clicking it');
  132. doneButton.click();
  133. } else {
  134. console.log('Done button not found, attempting to mark as done via API');
  135.  
  136. const notificationId = getNotificationId();
  137. if (notificationId) {
  138. const response = await fetch(`https://api.github.com/notifications/threads/${notificationId}`, {
  139. method: 'PATCH',
  140. headers: {
  141. Authorization: `token ${token}`,
  142. 'Content-Type': 'application/json',
  143. },
  144. body: JSON.stringify({
  145. state: 'done',
  146. }),
  147. });
  148.  
  149. if (response.ok) {
  150. console.log('Notification marked as done via API');
  151. } else {
  152. const errorData = await response.json();
  153. console.error('Failed to mark notification as done via API:', errorData);
  154. }
  155. } else {
  156. console.log('Notification ID not found');
  157. }
  158. }
  159. } catch (error) {
  160. console.error('Error in markAsDone:', error);
  161. }
  162. }
  163.  
  164. /**
  165. * Retrieves the notification ID from the URL parameters.
  166. */
  167. function getNotificationId() {
  168. const urlParams = new URLSearchParams(window.location.search);
  169. return urlParams.get('notification_id');
  170. }
  171.  
  172. globalThis.addEventListener(
  173. 'load',
  174. function () {
  175. console.log('Page loaded');
  176.  
  177. const targetNode = document.querySelector('.gh-header-meta');
  178. if (!targetNode) {
  179. console.log('Target node for observation not found');
  180. return;
  181. }
  182.  
  183. observer = new MutationObserver(() => {
  184. console.log('Relevant DOM mutation detected');
  185. debouncedCheckAndMerge();
  186. });
  187.  
  188. observer.observe(targetNode, {
  189. childList: true,
  190. subtree: true,
  191. });
  192. checkAndMerge();
  193. },
  194. false,
  195. );
  196. })();