Skip to content

Instantly share code, notes, and snippets.

@2u841r
Created June 29, 2025 10:12
Show Gist options
  • Save 2u841r/1875c60a9409289f83d6bc66000b37da to your computer and use it in GitHub Desktop.
Save 2u841r/1875c60a9409289f83d6bc66000b37da to your computer and use it in GitHub Desktop.
deletething
// ==UserScript==
// @name DeleteThing - Auto-fill Deletion Fields
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Auto-fills deletion confirmation fields on various platforms
// @author You
// @match https://vercel.com/*/*/settings*
// @match https://app.netlify.com/projects/*/configuration/general*
// @match https://dash.cloudflare.com/*/pages/view/*/settings/production*
// @match https://dash.cloudflare.com/*/workers/services/view/*/production/settings*
// @match https://github.com/*/*/settings*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// Vercel Platform Configuration
const vercelPlatform = {
domain: 'vercel.com',
urlPattern: /vercel\.com\/[^\/]+\/[^\/]+\/settings/,
selectors: {
modal: '[data-geist-modal-body]',
projectNameInput: 'input[name="resourceName"]',
confirmationInput: 'input[name="verificationText"]',
projectNameText: 'b[style*="overflow-wrap"]'
},
confirmationText: 'delete my project'
};
// Netlify Platform Configuration
const netlifyPlatform = {
domain: 'app.netlify.com',
urlPattern: /app\.netlify\.com\/projects\/[^\/]+\/configuration\/general/,
selectors: {
modal: '.modal-content',
projectNameInput: 'input[name="Name"]',
projectNameCode: 'code'
}
};
// Cloudflare Platform Configuration
const cloudflarePlatform = {
domain: 'dash.cloudflare.com',
urlPattern: /dash\.cloudflare\.com\/[^\/]+\/pages\/view\/[^\/]+\/settings\/production/,
selectors: {
modal: 'form',
confirmationInput: 'input[data-testid="deletionChallenge"]',
projectNameText: 'strong span'
}
};
// Cloudflare Workers Platform Configuration
const cloudflareWorkersPlatform = {
domain: 'dash.cloudflare.com',
urlPattern: /dash\.cloudflare\.com\/[^\/]+\/workers\/services\/view\/[^\/]+\/production\/settings/,
selectors: {
modal: 'form',
confirmationInput: 'input[data-testid="deletionChallenge"]',
projectNameText: 'strong span'
}
};
// GitHub Platform Configuration
const githubPlatform = {
domain: 'github.com',
urlPattern: /github\.com\/[^\/]+\/[^\/]+\/settings/,
selectors: {
modal: 'dialog[id="repo-delete-menu-dialog"]',
firstConfirmButton: 'button[data-next-stage="2"]',
secondConfirmButton: 'button[data-next-stage="3"]',
confirmationInput: 'input[data-test-selector="repo-delete-proceed-confirmation"]',
finalDeleteButton: 'button[data-test-selector="repo-delete-proceed-button"]'
}
};
// Configuration for different platforms
const platforms = {
vercel: vercelPlatform,
netlify: netlifyPlatform,
cloudflare: cloudflarePlatform,
cloudflareWorkers: cloudflareWorkersPlatform,
github: githubPlatform
};
// Global state to track observers and prevent duplicates
let currentObserver = null;
let isInitialized = false;
let githubCheckInterval = null;
let currentPlatform = null;
// Auto-fill deletion fields for Vercel
function autoFillVercelFields(modal) {
let projectName = null;
let filled = false;
// Extract project name from DOM elements
const projectNameElement = modal.querySelector(vercelPlatform.selectors.projectNameText);
if (projectNameElement) {
projectName = projectNameElement.textContent.trim();
}
if (projectName) {
const projectNameInput = modal.querySelector(vercelPlatform.selectors.projectNameInput);
const confirmationInput = modal.querySelector(vercelPlatform.selectors.confirmationInput);
if (projectNameInput && !projectNameInput.value) {
projectNameInput.value = projectName;
projectNameInput.dispatchEvent(new Event('input', { bubbles: true }));
projectNameInput.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
if (confirmationInput && !confirmationInput.value) {
confirmationInput.value = vercelPlatform.confirmationText;
confirmationInput.dispatchEvent(new Event('input', { bubbles: true }));
confirmationInput.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
}
return filled;
}
// Auto-fill deletion fields for Netlify
function autoFillNetlifyFields(modal) {
let projectName = null;
let filled = false;
// Extract project name from DOM elements
const projectNameCode = modal.querySelector(netlifyPlatform.selectors.projectNameCode);
if (projectNameCode) {
projectName = projectNameCode.textContent.trim();
}
if (!projectName) {
// Try to get from URL
const urlParts = window.location.pathname.split('/');
projectName = urlParts[2]; // /projects/project-name/configuration/general
}
if (projectName) {
const projectNameInput = modal.querySelector(netlifyPlatform.selectors.projectNameInput);
if (projectNameInput && !projectNameInput.value) {
projectNameInput.value = projectName;
projectNameInput.dispatchEvent(new Event('input', { bubbles: true }));
projectNameInput.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
}
return filled;
}
// Auto-fill deletion fields for Cloudflare (Pages and Workers)
function autoFillCloudflareFields(modal, platform) {
let projectName = null;
let filled = false;
// Extract project name from DOM elements
const projectNameElements = modal.querySelectorAll(platform.selectors.projectNameText);
if (projectNameElements.length > 0) {
projectName = projectNameElements[0].textContent.trim();
}
if (!projectName) {
// Try to extract from URL based on platform type
const urlParts = window.location.pathname.split('/');
if (platform === cloudflarePlatform) {
projectName = urlParts[4]; // /account/pages/view/project-name/settings/production
} else if (platform === cloudflareWorkersPlatform) {
projectName = urlParts[5]; // /account/workers/services/view/worker-name/production/settings
}
}
if (projectName) {
const confirmationInput = modal.querySelector(platform.selectors.confirmationInput);
if (confirmationInput && !confirmationInput.value) {
confirmationInput.value = projectName;
confirmationInput.dispatchEvent(new Event('input', { bubbles: true }));
confirmationInput.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
}
return filled;
}
// Handle GitHub deletion process
function handleGitHubDeletion() {
const modal = document.querySelector(githubPlatform.selectors.modal);
if (!modal || !modal.hasAttribute('open')) return false;
console.log('DeleteThing: Checking GitHub deletion steps...');
// Step 1: Auto-click "I want to delete this repository" button
const firstConfirmButton = modal.querySelector(githubPlatform.selectors.firstConfirmButton);
if (firstConfirmButton && firstConfirmButton.textContent.includes('I want to delete this repository')) {
console.log('DeleteThing: Step 1 - Auto-clicking "I want to delete this repository"');
setTimeout(() => {
firstConfirmButton.click();
}, 100);
return true;
}
// Step 2: Auto-click "I have read and understand these effects" button
const secondConfirmButton = modal.querySelector(githubPlatform.selectors.secondConfirmButton);
if (secondConfirmButton && secondConfirmButton.textContent.includes('I have read and understand these effects')) {
console.log('DeleteThing: Step 2 - Auto-clicking "I have read and understand these effects"');
setTimeout(() => {
secondConfirmButton.click();
}, 100);
return true;
}
// Step 3: Auto-fill the confirmation input field
const confirmationInput = modal.querySelector(githubPlatform.selectors.confirmationInput);
if (confirmationInput && !confirmationInput.value) {
// Get repository name from the data attribute or URL
let repoName = confirmationInput.getAttribute('data-repo-nwo');
if (!repoName) {
// Fallback: get from URL
const urlParts = window.location.pathname.split('/');
if (urlParts.length >= 3) {
repoName = `${urlParts[1]}/${urlParts[2]}`;
}
}
if (!repoName) {
// Fallback: get from dialog title
const titleElement = modal.querySelector('#repo-delete-menu-dialog-title');
if (titleElement) {
const titleText = titleElement.textContent.trim();
const match = titleText.match(/Delete (.+)/);
if (match) {
repoName = match[1];
}
}
}
if (repoName) {
console.log('DeleteThing: Step 3 - Auto-filling confirmation field with:', repoName);
// Clear any existing focus to avoid autofocus blocking
if (document.activeElement) {
document.activeElement.blur();
}
setTimeout(() => {
confirmationInput.focus();
confirmationInput.value = repoName;
// Dispatch events to trigger validation
confirmationInput.dispatchEvent(new Event('input', { bubbles: true }));
confirmationInput.dispatchEvent(new Event('change', { bubbles: true }));
confirmationInput.dispatchEvent(new Event('keyup', { bubbles: true }));
setTimeout(() => {
confirmationInput.dispatchEvent(new Event('blur', { bubbles: true }));
setTimeout(() => {
confirmationInput.dispatchEvent(new Event('focus', { bubbles: true }));
}, 10);
}, 30);
}, 50);
return true;
} else {
console.log('DeleteThing: Could not determine repository name');
}
}
return false;
}
// Detect current platform
function getCurrentPlatform() {
const hostname = window.location.hostname;
const url = window.location.href;
for (const [key, platform] of Object.entries(platforms)) {
if (hostname.includes(platform.domain) && platform.urlPattern.test(url)) {
return { key, ...platform };
}
}
return null;
}
// Auto-fill deletion fields based on platform
function autoFillDeletionFields(platform) {
if (platform.key === 'github') {
return handleGitHubDeletion();
}
const modal = document.querySelector(platform.selectors.modal);
if (!modal) return false;
switch (platform.key) {
case 'vercel':
return autoFillVercelFields(modal);
case 'netlify':
return autoFillNetlifyFields(modal);
case 'cloudflare':
case 'cloudflareWorkers':
return autoFillCloudflareFields(modal, platform);
default:
return false;
}
}
// Clean up existing observers and intervals
function cleanup() {
if (currentObserver) {
currentObserver.disconnect();
currentObserver = null;
}
if (githubCheckInterval) {
clearInterval(githubCheckInterval);
githubCheckInterval = null;
}
isInitialized = false;
currentPlatform = null;
}
// Main function to watch for modals and GitHub deletion process
function watchForDeletionModals() {
// Clean up any existing observers first
cleanup();
const platform = getCurrentPlatform();
if (!platform) {
console.log('DeleteThing: No supported platform detected');
return;
}
// Don't reinitialize if we're already watching the same platform
if (isInitialized && currentPlatform && currentPlatform.key === platform.key) {
console.log('DeleteThing: Already initialized for platform:', platform.key);
return;
}
console.log('DeleteThing: Platform detected:', platform.key);
isInitialized = true;
currentPlatform = platform;
if (platform.key === 'github') {
// For GitHub, watch for the deletion dialog
currentObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
// Check if the GitHub deletion dialog appeared or was modified
const modal = node.matches && node.matches(githubPlatform.selectors.modal) ?
node : node.querySelector && node.querySelector(githubPlatform.selectors.modal);
if (modal) {
console.log('DeleteThing: GitHub deletion dialog detected');
setTimeout(() => {
handleGitHubDeletion();
}, 500);
}
}
});
}
});
});
// Start observing with comprehensive options
currentObserver.observe(document.body, {
childList: true,
subtree: true
});
// Set up periodic checks when dialog is open
githubCheckInterval = setInterval(() => {
const modal = document.querySelector(githubPlatform.selectors.modal);
if (modal && modal.hasAttribute('open')) {
handleGitHubDeletion();
}
}, 200);
return;
}
// For other platforms, use the existing modal detection logic
currentObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
const modal = node.matches && node.matches(platform.selectors.modal) ?
node : node.querySelector && node.querySelector(platform.selectors.modal);
if (modal) {
console.log('DeleteThing: Modal detected for platform:', platform.key);
setTimeout(() => {
const filled = autoFillDeletionFields(platform);
if (filled) {
console.log('DeleteThing: Fields auto-filled successfully');
}
}, 300);
}
}
});
}
});
});
currentObserver.observe(document.body, {
childList: true,
subtree: true
});
// Also check if modal is already present
setTimeout(() => {
autoFillDeletionFields(platform);
}, 500);
}
// Initialize the script
function initialize() {
console.log('DeleteThing: Initializing...');
watchForDeletionModals();
}
// Handle both initial load and navigation changes
function handlePageChange() {
console.log('DeleteThing: Page change detected, URL:', window.location.href);
// Wait a bit for the page to settle after navigation
setTimeout(() => {
const platform = getCurrentPlatform();
if (platform) {
console.log('DeleteThing: Reinitializing for platform:', platform.key);
initialize();
} else {
console.log('DeleteThing: No supported platform after navigation');
}
}, 1000);
}
// Listen for various page change events
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
// Listen for navigation events (covers most SPA frameworks)
document.addEventListener('turbo:load', handlePageChange);
document.addEventListener('turbo:render', handlePageChange);
window.addEventListener('popstate', handlePageChange);
// Listen for Next.js navigation (Vercel uses Next.js)
window.addEventListener('routeChangeComplete', handlePageChange);
// Listen for React Router navigation
window.addEventListener('locationchange', handlePageChange);
// Custom event that some SPAs dispatch
window.addEventListener('navigate', handlePageChange);
// URL change detection for SPAs that don't dispatch events
let lastUrl = window.location.href;
setInterval(() => {
if (window.location.href !== lastUrl) {
console.log('DeleteThing: URL change detected:', lastUrl, '->', window.location.href);
lastUrl = window.location.href;
handlePageChange();
}
}, 500); // Check every 500ms for URL changes
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment