// ==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!');
})();