PixHost Enhanced

Drag-and-drop & Ctrl+V

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

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