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

Installa questo script?
Script suggerito dall'autore

Potresti essere interessato/a anche a YouTubeTV Volume Control with Memory

Installa questo script
// ==UserScript==
// @name         Merge Dependabot PRs Automatically on GitHub with UI and Selection Options
// @namespace    typpi.online
// @version      2.2
// @description  Automatically clicks the merge button on Dependabot PRs and "Done" button on the notification bar
// @author       Nick2bad4u
// @match        https://github.com/*/*/pull/*
// @grant        none
// @license      UnLicense
// @icon         https://www.google.com/s2/favicons?sz=64&domain=github.com
// @homepageURL  https://github.com/Nick2bad4u/UserStyles
// @supportURL   https://github.com/Nick2bad4u/UserStyles/issues
// ==/UserScript==

(function () {
	'use strict';

	const CHECK_INTERVAL = 1000; // Interval between checks in milliseconds
	let lastCheck = 0;
	let observer;
	let token = getTokenFromCookies() || promptForToken();
	if (!token) {
		while (!token) {
			token = prompt('Please enter your GitHub token:');
			if (token) {
				document.cookie = `github_token=${token}; path=/; max-age=${60 * 60 * 24 * 365}`;
			} else {
				alert('GitHub token is required.');
			}
		}
	}

	if (!token) {
		alert('GitHub token is required for this script to work.');
		return;
	}

	function getTokenFromCookies() {
		const match = document.cookie.match(/(^| )\s*github_token\s*=\s*([^;]+)/);
		return match ? match[2] : null;
	}

	function promptForToken() {
		let token = prompt('Please enter your GitHub token. You can generate a token at https://github.com/settings/tokens');
		if (token) {
			document.cookie = `github_token=${token}; path=/; max-age=${60 * 60 * 24 * 365}`;
		}
		return token;
	}

	const debounce = (func, delay) => {
		let debounceTimer;
		return function () {
			const context = this;
			const args = arguments;
			clearTimeout(debounceTimer);
			debounceTimer = setTimeout(() => func.apply(context, args), delay);
		};
	};

	const debouncedCheckAndMerge = debounce(checkAndMerge, 300);

	async function checkAndMerge() {
		const now = Date.now();
		if (now - lastCheck < CHECK_INTERVAL) return;
		lastCheck = now;

		console.log('checkAndMerge function called');

		try {
			const authorElement = document.querySelector('.author');
			if (authorElement && /^(dependabot\[bot\]|Nick2bad4u)$/.test(authorElement.textContent.trim())) {
				const prNumber = window.location.pathname.split('/').pop();
				const repoPath = window.location.pathname.split('/').slice(1, 3).join('/');

				console.log('PR is created by dependabot or specified user, attempting to merge via API');

				const mergeSuccess = await mergePR(repoPath, prNumber);
				if (mergeSuccess) {
					console.log('PR merged successfully');
					markAsDone();
				} else {
					console.log('Failed to merge PR');
				}
			} else {
				console.log('PR is not created by dependabot or specified user');
			}
		} catch (error) {
			console.error('Error in checkAndMerge:', error);
		}
	}

	async function mergePR(repoPath, prNumber) {
		try {
			const response = await fetch(`https://api.github.com/repos/${repoPath}/pulls/${prNumber}/merge`, {
				method: 'PUT',
				headers: {
					Authorization: `token ${token}`,
					'Content-Type': 'application/json',
				},
				body: JSON.stringify({
					commit_title: `Merge PR #${prNumber}`,
					merge_method: 'merge',
				}),
			});

			if (response.ok) {
				return true;
			} else {
				const errorData = await response.json();
				console.error('Failed to merge PR:', errorData);
				return false;
			}
		} catch (error) {
			console.error('Error in mergePR:', error);
			return false;
		}
	}

	async function markAsDone() {
		try {
			const notificationBar = document.querySelector('.js-flash-container');
			if (!notificationBar) {
				console.log('Notification bar not found');
				return;
			}

			let doneButton = document.querySelector('button[aria-label="Done"].btn.btn-sm');
			if (!doneButton) {
				doneButton = Array.from(document.querySelectorAll('button.btn.btn-sm')).find(button => button.textContent.trim() === 'Done');
			}
			if (doneButton) {
				console.log('Done button found, clicking it');
				doneButton.click();
			} else {
				console.log('Done button not found, attempting to mark as done via API');

				const notificationId = getNotificationId();
				if (notificationId) {
					const response = await fetch(`https://api.github.com/notifications/threads/${notificationId}`, {
						method: 'PATCH',
						headers: {
							Authorization: `token ${token}`,
							'Content-Type': 'application/json',
						},
						body: JSON.stringify({
							state: 'done',
						}),
					});

					if (response.ok) {
						console.log('Notification marked as done via API');
					} else {
						const errorData = await response.json();
						console.error('Failed to mark notification as done via API:', errorData);
					}
				} else {
					console.log('Notification ID not found');
				}
			}
		} catch (error) {
			console.error('Error in markAsDone:', error);
		}
	}

	/**
	 * Retrieves the notification ID from the URL parameters.
	 */
	function getNotificationId() {
		const urlParams = new URLSearchParams(window.location.search);
		return urlParams.get('notification_id');
	}

	globalThis.addEventListener(
		'load',
		function () {
			console.log('Page loaded');

			const targetNode = document.querySelector('.gh-header-meta');
			if (!targetNode) {
				console.log('Target node for observation not found');
				return;
			}

			observer = new MutationObserver(() => {
				console.log('Relevant DOM mutation detected');
				debouncedCheckAndMerge();
			});

			observer.observe(targetNode, {
				childList: true,
				subtree: true,
			});
			checkAndMerge();
		},
		false,
	);
})();