您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically delete multiple images from Google Photos. Source: https://gist.github.com/tranphuquy19/f8eeb02c7ca4b10f3baf02093eb80085
当前为
- // ==UserScript==
- // @name Google Photos Auto Delete
- // @namespace https://github.com/tranphuquy19
- // @version 1.0.1
- // @description Automatically delete multiple images from Google Photos. Source: https://gist.github.com/tranphuquy19/f8eeb02c7ca4b10f3baf02093eb80085
- // @author Quy (Christian) P. TRAN
- // @match https://photos.google.com/*
- // @grant none
- // @run-at document-end
- // ==/UserScript==
- if (window.trustedTypes && window.trustedTypes.createPolicy) {
- window.trustedTypes.createPolicy('default', {
- createHTML: (string, sink) => string
- });
- }
- class AutoDeleter {
- constructor(config = {}) {
- this.config = {
- MAX_RETRIES: 3,
- SCROLL_STEP: 1000,
- DELAY: 2000,
- SELECTORS: {
- checkboxes: '.QcpS9c.ckGgle',
- trashIcon: 'button[aria-label="Move to trash"]',
- confirmButton: 'button'
- },
- ...config
- };
- this.isRunning = false;
- this.currentIteration = 0;
- this.totalIterations = 0;
- }
- async smoothScroll() {
- return new Promise((resolve) => {
- const scrollHeight = document.documentElement.scrollHeight;
- let currentPosition = window.pageYOffset;
- const scrollStep = Math.max(scrollHeight / 10, this.config.SCROLL_STEP);
- const scroll = () => {
- currentPosition += scrollStep;
- window.scrollTo(0, currentPosition);
- if (currentPosition < scrollHeight) {
- setTimeout(scroll, 100);
- } else {
- resolve();
- }
- };
- scroll();
- });
- }
- delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- async findAndClick(selector, description) {
- const elements = selector === this.config.SELECTORS.confirmButton
- ? Array.from(document.querySelectorAll(selector)).filter(btn => btn.textContent === 'Move to trash')
- : document.querySelectorAll(selector);
- if (elements.length === 0) {
- console.log(`Not found: ${description}`);
- return false;
- }
- if (elements.length > 1) {
- elements.forEach(el => el.click());
- } else {
- elements[0].click();
- }
- return true;
- }
- async performSingleDeletion() {
- try {
- await this.smoothScroll();
- await this.delay(this.config.DELAY);
- const checkboxesClicked = await this.findAndClick(
- this.config.SELECTORS.checkboxes,
- 'checkboxes'
- );
- if (!checkboxesClicked) return false;
- await this.delay(this.config.DELAY);
- const trashIconClicked = await this.findAndClick(
- this.config.SELECTORS.trashIcon,
- 'trash icon'
- );
- if (!trashIconClicked) return false;
- await this.delay(this.config.DELAY);
- const confirmButtonClicked = await this.findAndClick(
- this.config.SELECTORS.confirmButton,
- 'confirm button'
- );
- if (!confirmButtonClicked) return false;
- return true;
- } catch (error) {
- console.error('Error during deletion:', error);
- return false;
- }
- }
- async start(times) {
- if (this.isRunning) {
- console.log('Already running!');
- return;
- }
- this.isRunning = true;
- this.currentIteration = 0;
- this.totalIterations = times;
- await this.runIteration();
- }
- stop() {
- this.isRunning = false;
- console.log('Stopping after current iteration...');
- }
- async runIteration(retryCount = 0) {
- if (!this.isRunning || this.currentIteration >= this.totalIterations) {
- this.isRunning = false;
- this.updateUI('complete');
- return;
- }
- if (retryCount >= this.config.MAX_RETRIES) {
- console.log(`Failed after ${this.config.MAX_RETRIES} retries, moving to next iteration`);
- this.currentIteration++;
- this.updateUI('running');
- await this.runIteration(0);
- return;
- }
- if (retryCount === 0) {
- this.currentIteration++;
- console.log(`Iteration ${this.currentIteration}/${this.totalIterations}`);
- } else {
- console.log(`Retry ${retryCount + 1} for iteration ${this.currentIteration}`);
- }
- const success = await this.performSingleDeletion();
- if (!success) {
- await this.delay(this.config.DELAY);
- await this.runIteration(retryCount + 1);
- return;
- }
- this.updateUI('running');
- await this.delay(this.config.DELAY);
- await this.runIteration(0);
- }
- updateUI(status) {
- const event = new CustomEvent('autoDeleterUpdate', {
- detail: {
- status,
- current: this.currentIteration,
- total: this.totalIterations
- }
- });
- window.dispatchEvent(event);
- }
- }
- class UIController {
- constructor() {
- this.autoDeleter = new AutoDeleter();
- this.setupUI();
- this.setupEventListeners();
- this.setupDraggable();
- }
- setupUI() {
- const container = document.createElement('div');
- Object.assign(container.style, {
- position: 'fixed',
- top: '20px',
- right: '20px',
- backgroundColor: 'white',
- padding: '20px',
- borderRadius: '8px',
- boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
- zIndex: '9999',
- width: '300px',
- fontFamily: 'Arial, sans-serif',
- cursor: 'move', // Thêm cursor move
- userSelect: 'none' // Prevent text selection while dragging
- });
- container.innerHTML = `
- <div id="dragHandle" style="
- padding: 10px;
- margin: -20px -20px 15px -20px;
- background: #f5f5f5;
- border-radius: 8px 8px 0 0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: move;
- ">
- <h3 style="margin: 0; font-size: 16px;">Google Photos Auto Delete</h3>
- <div style="display: flex; gap: 10px;">
- <button id="minimizeButton" style="
- padding: 4px 8px;
- background: #ddd;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- ">_</button>
- <button id="closeButton" style="
- padding: 4px 8px;
- background: #ff4444;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- ">×</button>
- </div>
- </div>
- <div id="contentPanel">
- <input type="number" id="iterationCount" min="1" value="5"
- style="width: 100%; padding: 8px; margin-bottom: 10px; box-sizing: border-box; border: 1px solid #ddd; border-radius: 4px;">
- <div style="display: flex; gap: 10px; margin-bottom: 15px;">
- <button id="startButton" style="
- flex: 1;
- padding: 8px;
- background: #4CAF50;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- transition: background 0.3s;
- ">Start</button>
- <button id="stopButton" style="
- flex: 1;
- padding: 8px;
- background: #f44336;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- transition: background 0.3s;
- " disabled>Stop</button>
- </div>
- <div style="
- background: #f5f5f5;
- padding: 10px;
- border-radius: 4px;
- font-size: 14px;
- ">
- <div>Status: <span id="status">Ready</span></div>
- <div>Progress: <span id="progress">0/0</span></div>
- </div>
- </div>
- `;
- document.body.appendChild(container);
- this.container = container;
- }
- setupDraggable() {
- const container = this.container;
- const dragHandle = container.querySelector('#dragHandle');
- let isDragging = false;
- let currentX;
- let currentY;
- let initialX;
- let initialY;
- let xOffset = 0;
- let yOffset = 0;
- // Lưu vị trí vào localStorage
- const savePosition = () => {
- const position = {
- x: xOffset,
- y: yOffset
- };
- localStorage.setItem('autoDeleterPosition', JSON.stringify(position));
- };
- // Khôi phục vị trí từ localStorage
- const loadPosition = () => {
- const savedPosition = localStorage.getItem('autoDeleterPosition');
- if (savedPosition) {
- const position = JSON.parse(savedPosition);
- xOffset = position.x;
- yOffset = position.y;
- setTranslate(xOffset, yOffset, container);
- }
- };
- const dragStart = (e) => {
- if (e.type === "touchstart") {
- initialX = e.touches[0].clientX - xOffset;
- initialY = e.touches[0].clientY - yOffset;
- } else {
- initialX = e.clientX - xOffset;
- initialY = e.clientY - yOffset;
- }
- if (e.target === dragHandle || e.target.parentElement === dragHandle) {
- isDragging = true;
- }
- };
- const dragEnd = () => {
- isDragging = false;
- savePosition(); // Lưu vị trí khi kết thúc kéo
- };
- const drag = (e) => {
- if (isDragging) {
- e.preventDefault();
- if (e.type === "touchmove") {
- currentX = e.touches[0].clientX - initialX;
- currentY = e.touches[0].clientY - initialY;
- } else {
- currentX = e.clientX - initialX;
- currentY = e.clientY - initialY;
- }
- xOffset = currentX;
- yOffset = currentY;
- setTranslate(currentX, currentY, container);
- }
- };
- const setTranslate = (xPos, yPos, el) => {
- el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
- };
- // Mouse events
- dragHandle.addEventListener('mousedown', dragStart);
- document.addEventListener('mousemove', drag);
- document.addEventListener('mouseup', dragEnd);
- // Touch events
- dragHandle.addEventListener('touchstart', dragStart);
- document.addEventListener('touchmove', drag);
- document.addEventListener('touchend', dragEnd);
- // Minimize/Maximize functionality
- const minimizeButton = container.querySelector('#minimizeButton');
- const contentPanel = container.querySelector('#contentPanel');
- let isMinimized = false;
- minimizeButton.addEventListener('click', () => {
- if (isMinimized) {
- contentPanel.style.display = 'block';
- minimizeButton.textContent = '_';
- } else {
- contentPanel.style.display = 'none';
- minimizeButton.textContent = '□';
- }
- isMinimized = !isMinimized;
- });
- // Close functionality
- const closeButton = container.querySelector('#closeButton');
- closeButton.addEventListener('click', () => {
- container.remove();
- });
- // Load saved position when initializing
- loadPosition();
- }
- setupEventListeners() {
- const startButton = this.container.querySelector('#startButton');
- const stopButton = this.container.querySelector('#stopButton');
- const iterationInput = this.container.querySelector('#iterationCount');
- startButton.addEventListener('click', () => {
- const count = parseInt(iterationInput.value);
- if (count > 0) {
- startButton.disabled = true;
- stopButton.disabled = false;
- this.autoDeleter.start(count);
- }
- });
- stopButton.addEventListener('click', () => {
- this.autoDeleter.stop();
- stopButton.disabled = true;
- });
- window.addEventListener('autoDeleterUpdate', (e) => {
- const statusElem = this.container.querySelector('#status');
- const progressElem = this.container.querySelector('#progress');
- const startButton = this.container.querySelector('#startButton');
- const stopButton = this.container.querySelector('#stopButton');
- progressElem.textContent = `${e.detail.current}/${e.detail.total}`;
- switch (e.detail.status) {
- case 'running':
- statusElem.textContent = 'Running';
- statusElem.style.color = '#4CAF50';
- break;
- case 'complete':
- statusElem.textContent = 'Complete';
- statusElem.style.color = '#2196F3';
- startButton.disabled = false;
- stopButton.disabled = true;
- break;
- }
- });
- }
- }
- // Initialize the UI
- const controller = new UIController();
- console.log(`
- Google Photos Auto Delete Script
- ------------------------------
- UI Controls have been added to the page.
- You can:
- 1. Set the number of iterations
- 2. Click Start to begin
- 3. Click Stop to pause after current iteration
- 4. Monitor progress in the UI panel
- `);