// ==UserScript== // @name Single Pixel Modifier Tool // @namespace http://tampermonkey.net/ // @version 1.0 // @description Change exactly one pixel in images/GIFs before upload // @author You // @match *https://sneed.95.fyi/* // @match *https://ironcafe.org/* // @match *https://sidson.city/* // @match *https://gigacha.de/* // @match *https://www.28chan.org/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; console.log('Single Pixel Modifier loading...'); // Create the tool const tool = document.createElement('div'); tool.innerHTML = ` `; document.body.appendChild(tool); // State let files = []; let modifiedFiles = []; let changeAmount = 1; // Elements const toolDiv = document.getElementById('pixel-modifier-tool'); const openBtn = document.getElementById('open-tool'); const closeBtn = document.getElementById('close-tool'); const dropArea = document.getElementById('drop-area'); const fileInput = document.getElementById('file-input'); const fileList = document.getElementById('file-list'); const changeSlider = document.getElementById('change-amount'); const changeDisplay = document.getElementById('change-display'); const modifyBtn = document.getElementById('modify-btn'); const injectBtn = document.getElementById('inject-btn'); const status = document.getElementById('status'); // Open/Close openBtn.addEventListener('click', () => { toolDiv.style.display = 'block'; openBtn.style.display = 'none'; }); closeBtn.addEventListener('click', () => { toolDiv.style.display = 'none'; openBtn.style.display = 'flex'; }); // Change amount changeSlider.addEventListener('input', (e) => { changeAmount = parseInt(e.target.value); changeDisplay.textContent = `±${changeAmount}`; }); // File handling dropArea.addEventListener('click', () => fileInput.click()); dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.style.background = 'rgba(76, 175, 80, 0.15)'; dropArea.style.borderColor = '#2E7D32'; }); dropArea.addEventListener('dragleave', () => { dropArea.style.background = 'rgba(76, 175, 80, 0.05)'; dropArea.style.borderColor = '#4CAF50'; }); dropArea.addEventListener('drop', (e) => { e.preventDefault(); dropArea.style.background = 'rgba(76, 175, 80, 0.05)'; dropArea.style.borderColor = '#4CAF50'; handleFiles(e.dataTransfer.files); }); fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); function handleFiles(fileList) { const newFiles = Array.from(fileList); if (newFiles.length === 0) { showStatus('No files selected', 'error'); return; } // Filter for images const imageFiles = newFiles.filter(file => file.type.startsWith('image/') || file.name.match(/\.(jpg|jpeg|png|gif|webp|bmp)$/i) ); if (imageFiles.length === 0) { showStatus('No image files found', 'error'); return; } files.push(...imageFiles); modifiedFiles = files.map(() => null); // Reset modifications updateFileList(); showStatus(`Added ${imageFiles.length} image(s)`, 'success'); injectBtn.style.display = 'none'; modifyBtn.disabled = false; } function updateFileList() { if (files.length === 0) { fileList.innerHTML = '
No files
'; return; } let html = ''; files.forEach((file, index) => { const isModified = modifiedFiles[index] !== null; const fileType = file.type.split('/')[1] || file.name.split('.').pop(); const fileSize = formatFileSize(file.size); html += `
${isModified ? '✓' : index + 1} ${file.name}
${fileType.toUpperCase()} ${fileSize}
`; }); fileList.innerHTML = html; // Add remove button listeners fileList.querySelectorAll('.remove-btn').forEach(btn => { btn.addEventListener('click', (e) => { const index = parseInt(e.target.dataset.index); files.splice(index, 1); modifiedFiles.splice(index, 1); updateFileList(); showStatus('File removed', 'info'); }); }); } // Modify function - changes exactly ONE pixel async function modifySinglePixel(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Set canvas size canvas.width = img.width; canvas.height = img.height; // Draw image (works for GIFs too - will be static but that's fine) ctx.drawImage(img, 0, 0); // Get image data const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Choose ONE random pixel (avoid edges by 2 pixels) const x = Math.floor(Math.random() * (canvas.width - 4)) + 2; const y = Math.floor(Math.random() * (canvas.height - 4)) + 2; const pixelIndex = (y * canvas.width + x) * 4; // Choose ONE random channel (R, G, or B) const channel = Math.floor(Math.random() * 3); // Choose change direction (+ or -) const change = Math.random() > 0.5 ? changeAmount : -changeAmount; // Get original value const originalValue = data[pixelIndex + channel]; // Calculate new value (clamp to 0-255) let newValue = originalValue + change; if (newValue < 0) newValue = 0; if (newValue > 255) newValue = 255; // Apply the change to exactly ONE pixel data[pixelIndex + channel] = newValue; // Put modified data back ctx.putImageData(imageData, 0, 0); // Convert to blob with original format const mimeType = file.type === 'image/gif' ? 'image/gif' : 'image/jpeg'; const quality = file.type === 'image/gif' ? 1.0 : 0.95; canvas.toBlob(function(blob) { // Create new file with modified content const modifiedFile = new File([blob], file.name, { type: file.type, lastModified: Date.now() + Math.floor(Math.random() * 1000) // Ensure unique timestamp }); console.log(`Modified ${file.name}: Pixel(${x},${y}) ${['R','G','B'][channel]}: ${originalValue}→${newValue} (${change > 0 ? '+' : ''}${change})`); resolve(modifiedFile); }, mimeType, quality); }; img.onerror = reject; img.src = e.target.result; }; reader.onerror = reject; reader.readAsDataURL(file); }); } // Modify all files modifyBtn.addEventListener('click', async () => { if (files.length === 0) { showStatus('No files to modify', 'error'); return; } showStatus('Modifying files...', 'processing'); modifyBtn.disabled = true; modifyBtn.textContent = 'Modifying...'; let successCount = 0; let failCount = 0; for (let i = 0; i < files.length; i++) { try { showStatus(`Modifying ${i + 1}/${files.length}: ${files[i].name}`, 'processing'); modifiedFiles[i] = await modifySinglePixel(files[i]); successCount++; updateFileList(); } catch (error) { console.error(`Failed to modify ${files[i].name}:`, error); failCount++; modifiedFiles[i] = null; } } // Update UI modifyBtn.disabled = false; modifyBtn.textContent = 'Modify Files'; if (successCount > 0) { showStatus(`Modified ${successCount} file(s)${failCount > 0 ? `, ${failCount} failed` : ''}`, 'success'); injectBtn.style.display = 'block'; } else { showStatus('Failed to modify any files', 'error'); } }); // Inject to site injectBtn.addEventListener('click', () => { const readyFiles = modifiedFiles.filter(f => f !== null); if (readyFiles.length === 0) { showStatus('No modified files to inject', 'error'); return; } showStatus('Looking for upload form...', 'processing'); // Find ALL file inputs const fileInputs = document.querySelectorAll('input[type="file"]'); if (fileInputs.length === 0) { showStatus('No file upload found on page', 'error'); return; } console.log(`Found ${fileInputs.length} file input(s)`); // Try each input until one works let injected = false; for (const input of fileInputs) { try { // Create DataTransfer with modified files const dataTransfer = new DataTransfer(); readyFiles.forEach(file => dataTransfer.items.add(file)); // Assign to input input.files = dataTransfer.files; // Trigger events input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('input', { bubbles: true })); console.log(`Injected ${readyFiles.length} file(s) to input:`, input); injected = true; // If this is a single file input and we have multiple files, // try to find multiple file inputs if (!input.multiple && readyFiles.length > 1) { showStatus(`Warning: Input doesn't support multiple files. Only first file injected.`, 'warning'); } else { showStatus(`✓ Injected ${readyFiles.length} file(s)!`, 'success'); } break; // Stop after first successful injection } catch (error) { console.error('Failed to inject to input:', error); continue; // Try next input } } if (!injected) { showStatus('Failed to inject to any file inputs', 'error'); } else { // Auto-close after successful injection setTimeout(() => { toolDiv.style.display = 'none'; openBtn.style.display = 'flex'; showStatus('Ready', 'info'); }, 1500); } }); // Helper functions function showStatus(message, type = 'info') { status.textContent = message; switch(type) { case 'success': status.style.color = '#4CAF50'; status.style.background = 'rgba(76, 175, 80, 0.1)'; break; case 'error': status.style.color = '#f44336'; status.style.background = 'rgba(244, 67, 54, 0.1)'; break; case 'warning': status.style.color = '#FF9800'; status.style.background = 'rgba(255, 152, 0, 0.1)'; break; case 'processing': status.style.color = '#2196F3'; status.style.background = 'rgba(33, 150, 243, 0.1)'; break; default: status.style.color = '#aaa'; status.style.background = 'rgba(255, 255, 255, 0.05)'; } } function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(1) + ' MB'; } // Auto-hide when clicking outside document.addEventListener('click', (e) => { if (!toolDiv.contains(e.target) && e.target !== openBtn && toolDiv.style.display === 'block') { toolDiv.style.display = 'none'; openBtn.style.display = 'flex'; } }); console.log('Single Pixel Modifier ready!'); })();