Greasy Fork is available in English.

PixHost Enhanced

Drag-and-drop & Ctrl+V

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         PixHost Enhanced
// @namespace    https://pixhost.to/
// @version      0.2
// @description  Drag-and-drop & Ctrl+V
// @author       Colder
// @match        https://pixhost.to/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Add styles
    GM_addStyle(`
        #custom-upload-zone {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 300px;
            background: white;
            border: 2px solid #ccc;
            border-radius: 8px;
            padding: 15px;
            z-index: 9999;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }

        #drop-zone {
            border: 2px dashed #ccc;
            border-radius: 4px;
            padding: 20px;
            text-align: center;
            margin-bottom: 10px;
            background: #f9f9f9;
            transition: all 0.3s ease;
            cursor: pointer;
        }

        #drop-zone.drag-over {
            background: #e1f5fe;
            border-color: #2196F3;
        }

        .url-output {
            margin-top: 10px;
        }

        .url-textarea {
            width: 100%;
            min-height: 120px;
            margin: 5px 0;
            font-family: monospace;
            font-size: 12px;
            resize: vertical;
            white-space: pre;
        }

        .action-buttons {
            display: flex;
            gap: 5px;
            margin-top: 5px;
        }

        .copy-btn {
            background: #2196F3;
            color: white;
            border: none;
            padding: 8px 5px;
            border-radius: 4px;
            cursor: pointer;
            flex: 1;
            font-size: 11px;
            font-weight: bold;
            text-align: center;
            transition: background 0.2s;
        }

        .copy-btn:hover {
            background: #1976D2;
        }

        #status-container {
            margin-top: 10px;
            padding: 10px;
            border-radius: 4px;
            display: none;
            font-size: 13px;
            text-align: center;
        }

        .success {
            background: #E8F5E9;
            color: #2E7D32;
        }

        .error {
            background: #FFEBEE;
            color: #C62828;
        }

        .progress {
            margin-top: 10px;
            font-size: 0.9em;
            color: #666;
            text-align: center;
        }
    `);

    // Create upload interface
    const uploadInterface = document.createElement('div');
    uploadInterface.id = 'custom-upload-zone';
    uploadInterface.innerHTML = `
        <div id="drop-zone" title="Click to browse files">
            Drag & Drop Images Here<br>
            <small>or click / Ctrl+V to paste</small>
            <input type="file" id="file-input" multiple style="display: none" accept="image/*">
        </div>
        <div class="progress"></div>
        <div id="status-container"></div>
        <div class="url-output"></div>
    `;

    document.body.appendChild(uploadInterface);

    // Setup elements
    const dropZone = document.getElementById('drop-zone');
    const fileInput = document.getElementById('file-input');
    const urlOutput = document.querySelector('.url-output');
    const progressDiv = document.querySelector('.progress');
    const statusContainer = document.getElementById('status-container');

    let uploadQueue = [];
    let uploadResults = [];
    let isUploading = false;
    let statusTimeout;

    // Event Listeners
    dropZone.addEventListener('click', () => fileInput.click());

    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        dropZone.addEventListener(eventName, preventDefaults, false);
        document.body.addEventListener(eventName, preventDefaults, false);
    });

    ['dragenter', 'dragover'].forEach(eventName => {
        dropZone.addEventListener(eventName, highlight, false);
    });

    ['dragleave', 'drop'].forEach(eventName => {
        dropZone.addEventListener(eventName, unhighlight, false);
    });

    dropZone.addEventListener('drop', handleDrop, false);
    fileInput.addEventListener('change', (e) => handleFilesArray([...e.target.files]), false);

    // Ctrl+V Paste Listener
    document.addEventListener('paste', (e) => {
        if (!e.clipboardData) return;
        const items = e.clipboardData.items;
        const files = [];
        for (let i = 0; i < items.length; i++) {
            if (items[i].type.indexOf('image') !== -1) {
                const file = items[i].getAsFile();
                if (file) files.push(file);
            }
        }
        if (files.length > 0) {
            e.preventDefault();
            handleFilesArray(files);
        }
    });

    function preventDefaults(e) {
        e.preventDefault();
        e.stopPropagation();
    }

    function highlight(e) {
        dropZone.classList.add('drag-over');
    }

    function unhighlight(e) {
        dropZone.classList.remove('drag-over');
    }

    function handleDrop(e) {
        handleFilesArray([...e.dataTransfer.files]);
    }

    // Core file handler (sorts and queues)
    function handleFilesArray(filesArray) {
        // Filter out non-images and sort alphabetically by filename
        const validFiles = filesArray
            .filter(f => f.type.startsWith('image/'))
            .sort((a, b) => a.name.localeCompare(b.name));

        if (validFiles.length === 0) {
            showStatus('No valid images found.', 'error');
            return;
        }

        uploadQueue = uploadQueue.concat(validFiles);
        // Re-sort the queue in case multiple batches were dropped/pasted before processing finished
        uploadQueue.sort((a, b) => a.name.localeCompare(b.name));

        updateProgress();
        if (!isUploading) {
            processQueue();
        }
    }

    function updateProgress() {
        const total = uploadQueue.length + uploadResults.length;
        const completed = uploadResults.length;
        if (total > 0) {
            progressDiv.textContent = `Progress: ${completed}/${total} files`;
        } else {
            progressDiv.textContent = '';
        }
    }

    async function processQueue() {
        if (uploadQueue.length === 0) {
            if (uploadResults.length > 0) {
                displayUrls(uploadResults);
                uploadResults = []; // reset for the next batch
            }
            isUploading = false;
            updateProgress();
            return;
        }

        isUploading = true;
        const file = uploadQueue.shift();

        const formData = new FormData();
        formData.append('img', file);
        formData.append('content_type', '0');
        formData.append('max_th_size', '420');

        try {
            await uploadFile(file, formData);
        } catch (error) {
            showStatus(`Error uploading ${file.name}: ${error}`, 'error');
        }

        processQueue();
    }

    function uploadFile(file, formData) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: 'https://api.pixhost.to/images',
                data: formData,
                headers: {
                    'Accept': 'application/json'
                },
                onload: async function(response) {
                    try {
                        const data = JSON.parse(response.responseText);

                        if (!data.show_url) {
                            throw new Error(data.error || "API did not return a valid URL.");
                        }

                        const directUrl = await extractDirectUrl(data.show_url);
                        uploadResults.push({
                            name: data.name || file.name,
                            directUrl: directUrl
                        });

                        updateProgress();
                        showStatus(`Uploaded: ${file.name}`, 'success');
                        resolve();
                    } catch (error) {
                        reject(error.message || error);
                    }
                },
                onerror: function(error) {
                    reject(error.statusText);
                }
            });
        });
    }

    function extractDirectUrl(showUrl) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: showUrl,
                onload: function(response) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');
                    const imgElement = doc.querySelector('#image');
                    if (imgElement && imgElement.src) {
                        resolve(imgElement.src);
                    } else {
                        reject('Could not scrape direct image URL');
                    }
                },
                onerror: function(error) {
                    reject(error.statusText);
                }
            });
        });
    }

    function displayUrls(results) {
        const rawUrls = results.map(r => r.directUrl).join('\n');
        const bbcode = results.map(r => `[img]${r.directUrl}[/img]`).join('\n');
        const markdown = results.map(r => `![${r.name}](${r.directUrl})`).join('\n');

        // Create the UI with single textarea and action buttons
        urlOutput.innerHTML = `
            <textarea class="url-textarea" readonly spellcheck="false">${rawUrls}</textarea>
            <div class="action-buttons">
                <button class="copy-btn" data-clipboard-text="${encodeURIComponent(rawUrls)}">Copy URLs</button>
                <button class="copy-btn" data-clipboard-text="${encodeURIComponent(bbcode)}">Copy BBCode</button>
                <button class="copy-btn" data-clipboard-text="${encodeURIComponent(markdown)}">Copy MD</button>
            </div>
        `;

        // Bind copy functionality
        urlOutput.querySelectorAll('.copy-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                const text = decodeURIComponent(this.getAttribute('data-clipboard-text'));
                navigator.clipboard.writeText(text).then(() => {
                    const originalText = this.textContent;
                    this.textContent = 'Copied!';
                    setTimeout(() => {
                        this.textContent = originalText;
                    }, 1200);
                });
            });
        });
    }

    function showStatus(message, type) {
        statusContainer.className = `status ${type}`;
        statusContainer.textContent = message;
        statusContainer.style.display = 'block';

        clearTimeout(statusTimeout);
        statusTimeout = setTimeout(() => {
            statusContainer.style.display = 'none';
        }, 3000);
    }
})();