MichaelPaulukonis/NotebookLM-Resource-Deleter
This script automates the cleanup of duplicate sources in Google NotebookLM by mimicking human interaction. Instead of trying to "bulk select" items (which failed due to UI state issues), this script performs the specific deletion workflow for every duplicate item found, one by one.
NotebookLM Duplicate Source Remover
A JavaScript automation script designed to identify and remove duplicate source files (PDFs, websites, text files) from a Google NotebookLM project.
๐ Overview
Google NotebookLM does not currently offer a native "deduplicate" feature. If you accidentally upload the same files multiple times, removing them manually is a tedious process requiring three clicks per item.
This script automates that workflow by simulating user interactions directly in the browser. It scans your source list, identifies duplicates based on their names, and performs the deletion sequence sequentially to ensure stability.
โ๏ธ How It Works
- Scanning: The script scans the DOM for all source containers and extracts filenames from the
aria-labelattributes. - Normalization: Filenames are converted to lowercase and trimmed of whitespace to ensure accurate matching.
- Sequential Processing: It creates a queue of duplicate items.
- UI Automation: For every duplicate, it performs the following "Robot Hand" actions:
- Clicks the "More Options" (three dots) menu on the specific row.
- Waits for the menu animation.
- Clicks the "Remove source" button.
- Waits for the confirmation dialog.
- Clicks the "Delete" confirmation button.
- Cool-down: It pauses for 1.5 seconds between items to allow the list to refresh and prevent UI glitches.
๐ Usage Instructions
Prerequisites
- A Google NotebookLM project open in a desktop web browser (Chrome, Edge, Firefox, Brave).
- Duplicate files present in the source list.
Steps to Run
- Open your NotebookLM project page.
- Open the Developer Tools:
- Windows/Linux: Press
F12orCtrl+Shift+J. - Mac: Press
Cmd+Option+J.
- Windows/Linux: Press
- Navigate to the Console tab.
- Copy and Paste the script below into the console.
- Press Enter.
โ ๏ธ IMPORTANT: Do not move your mouse or click elsewhere while the script is running. It relies on maintaining focus on the pop-up menus.
๐ป The Script
(async function deleteDuplicatesFinal() {
console.clear();
console.log("๐ Starting Automatic Duplicate Removal...");
// --- Configuration ---
const TIME_TO_OPEN_MENU = 500; // Time for menu to pop up (ms)
const TIME_TO_OPEN_DIALOG = 500; // Time for "Are you sure?" dialog (ms)
const TIME_AFTER_DELETE = 1500; // Time for item to disappear (ms)
// Helper: Pause execution
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// 1. Scan for Duplicates
const rows = Array.from(document.querySelectorAll('.single-source-container'));
const seenNames = new Set();
const itemsToDelete = [];
console.log(`Scanning ${rows.length} sources...`);
rows.forEach((row, index) => {
// Try to get name from Checkbox Label (most accurate)
const checkbox = row.querySelector('input[type="checkbox"]');
let name = "";
if (checkbox && checkbox.getAttribute('aria-label')) {
name = checkbox.getAttribute('aria-label').trim().toLowerCase();
} else {
// Fallback to text div
const titleDiv = row.querySelector('.source-title');
if (titleDiv) name = titleDiv.innerText.trim().toLowerCase();
}
if (!name) return;
if (seenNames.has(name)) {
// Found a duplicate! Save the menu button reference.
const menuBtn = row.querySelector('.source-item-more-button');
if (menuBtn) {
itemsToDelete.push({ name: name, btn: menuBtn });
}
} else {
seenNames.add(name);
}
});
console.log(`๐ฏ Found ${itemsToDelete.length} duplicates to remove.`);
if (itemsToDelete.length === 0) {
alert("No duplicates found to delete!");
return;
}
// 2. Execution Loop
for (let i = 0; i < itemsToDelete.length; i++) {
const item = itemsToDelete[i];
console.log(`[%c${i + 1}/${itemsToDelete.length}%c] Deleting: "${item.name}"`, 'color: blue; font-weight: bold', 'color: black');
try {
// STEP A: Open the Menu
item.btn.click();
await sleep(TIME_TO_OPEN_MENU);
// STEP B: Click "Remove source"
let removeBtn = document.querySelector('.more-menu-delete-source-button');
// Fallback selector
if (!removeBtn) {
const menuItems = document.querySelectorAll('button[role="menuitem"]');
for (let btn of menuItems) {
if (btn.innerText.includes('Remove source')) {
removeBtn = btn;
break;
}
}
}
if (removeBtn) {
removeBtn.click();
await sleep(TIME_TO_OPEN_DIALOG);
// STEP C: Click "Delete" in the confirmation dialog
const dialogButtons = document.querySelectorAll('mat-dialog-container button');
let clickedConfirm = false;
for (let btn of dialogButtons) {
const txt = btn.innerText.trim();
if (txt === 'Delete' || txt === 'Remove') {
btn.click();
clickedConfirm = true;
console.log(` โ
Confirmed deletion`);
break;
}
}
if (!clickedConfirm) {
console.warn(" โ ๏ธ Could not find confirmation button. Closing menu.");
document.body.click();
}
// Wait for UI to update
await sleep(TIME_AFTER_DELETE);
} else {
console.warn(" โ ๏ธ Could not find 'Remove source' menu item.");
document.body.click();
}
} catch (err) {
console.error(" โ Error processing item:", err);
}
}
console.log("๐ Process Complete.");
alert(`Finished removing ${itemsToDelete.length} duplicates.`);
})();