- // ==UserScript==
- // @name PixHost Drag-and-Drop Uploader
- // @namespace https://pixhost.to/
- // @version 0.1
- // @description Adds drag-and-drop image upload functionality to PixHost with grouped URLs output
- // @author You
- // @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;
- }
-
- #drop-zone.drag-over {
- background: #e1f5fe;
- border-color: #2196F3;
- }
-
- .url-output {
- margin-top: 10px;
- max-height: 300px;
- overflow-y: auto;
- }
-
- .url-group {
- margin-bottom: 15px;
- border-bottom: 1px solid #eee;
- padding-bottom: 10px;
- }
-
- .url-type {
- font-weight: bold;
- margin-bottom: 5px;
- }
-
- .url-textarea {
- width: 100%;
- min-height: 100px;
- margin: 5px 0;
- font-family: monospace;
- font-size: 12px;
- resize: vertical;
- }
-
- .copy-btn {
- background: #2196F3;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 4px;
- cursor: pointer;
- margin: 2px;
- width: 100%;
- }
-
- .copy-btn:hover {
- background: #1976D2;
- }
-
- .status {
- margin-top: 10px;
- padding: 10px;
- border-radius: 4px;
- }
-
- .success {
- background: #E8F5E9;
- color: #2E7D32;
- }
-
- .error {
- background: #FFEBEE;
- color: #C62828;
- }
-
- .progress {
- margin-top: 10px;
- font-size: 0.9em;
- color: #666;
- }
- `);
-
- // Create upload interface
- const uploadInterface = document.createElement('div');
- uploadInterface.id = 'custom-upload-zone';
- uploadInterface.innerHTML = `
- <div id="drop-zone">
- Drag & Drop Images Here<br>
- <small>or click to select files</small>
- <input type="file" id="file-input" multiple style="display: none">
- </div>
- <div class="progress"></div>
- <div class="url-output"></div>
- `;
-
- document.body.appendChild(uploadInterface);
-
- // Setup drag and drop handlers
- const dropZone = document.getElementById('drop-zone');
- const fileInput = document.getElementById('file-input');
- const urlOutput = document.querySelector('.url-output');
- const progressDiv = document.querySelector('.progress');
-
- let uploadQueue = [];
- let uploadResults = [];
- let isUploading = false;
-
- 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', handleFiles, false);
-
- 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) {
- const dt = e.dataTransfer;
- const files = dt.files;
- handleFiles({ target: { files: files } });
- }
-
- function handleFiles(e) {
- const files = [...e.target.files];
- uploadQueue = uploadQueue.concat(files);
- 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) {
- displayGroupedUrls(uploadResults);
- uploadResults = [];
- }
- isUploading = false;
- updateProgress();
- return;
- }
-
- isUploading = true;
- const file = uploadQueue.shift();
-
- if (!file.type.startsWith('image/')) {
- showStatus(`${file.name} is not an image file`, 'error');
- processQueue();
- return;
- }
-
- 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);
- const directUrl = await extractDirectUrl(data.show_url);
- uploadResults.push({
- name: data.name,
- directUrl: directUrl,
- showUrl: data.show_url
- });
- updateProgress();
- showStatus(`${file.name} uploaded successfully!`, 'success');
- resolve();
- } catch (error) {
- reject(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 find direct image URL');
- }
- },
- onerror: function(error) {
- reject(error.statusText);
- }
- });
- });
- }
-
- function displayGroupedUrls(results) {
- const urlGroup = document.createElement('div');
- urlGroup.className = 'url-group';
-
- const formats = {
- 'Direct URLs': results.map(r => r.directUrl).join('\n'),
- 'BBCode': results.map(r => `[img]${r.directUrl}[/img]`).join('\n'),
- 'Markdown': results.map(r => ``).join('\n')
- };
-
- Object.entries(formats).forEach(([formatType, text]) => {
- const formatSection = document.createElement('div');
- formatSection.innerHTML = `
- <div class="url-type">${formatType}</div>
- <textarea class="url-textarea" readonly>${text}</textarea>
- <button class="copy-btn" data-clipboard-text="${text}">Copy ${formatType}</button>
- `;
- urlGroup.appendChild(formatSection);
- });
-
- // Clear previous results
- urlOutput.innerHTML = '';
- urlOutput.appendChild(urlGroup);
-
- // Add click handlers for copy buttons
- urlGroup.querySelectorAll('.copy-btn').forEach(btn => {
- btn.addEventListener('click', function() {
- const text = this.getAttribute('data-clipboard-text');
- navigator.clipboard.writeText(text).then(() => {
- const originalText = this.textContent;
- this.textContent = 'Copied!';
- setTimeout(() => {
- this.textContent = originalText;
- }, 1000);
- });
- });
- });
- }
-
- function showStatus(message, type) {
- const statusDiv = document.createElement('div');
- statusDiv.className = `status ${type}`;
- statusDiv.textContent = message;
- urlOutput.insertBefore(statusDiv, urlOutput.firstChild);
-
- setTimeout(() => {
- statusDiv.remove();
- }, 3000);
- }
- })();