Skip to content

Instantly share code, notes, and snippets.

@navinor
Last active May 28, 2025 21:03
Show Gist options
  • Save navinor/d295b298f026134ae979f94517e98f8b to your computer and use it in GitHub Desktop.
Save navinor/d295b298f026134ae979f94517e98f8b to your computer and use it in GitHub Desktop.
mclo.gs Log Uploader
// ==UserScript==
// @name mclo.gs Log Uploader
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Upload log files to mclo.gs.
// @author Navinor
// @match *://*/*
// @grant GM_setClipboard
// @grant GM.xmlHttpRequest
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @connect api.mclo.gs
// @connect *
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
let lastLogLink = null;
// Add custom CSS for our upload button and notifications
GM_addStyle(`
.mclogs-notification {
position: fixed;
top: 20px;
right: 20px;
background: #2d3142;
color: #ffffff;
padding: 16px 20px;
border-radius: 8px;
border-left: 4px solid #4CAF50;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 10001;
max-width: 350px;
font-family: Arial, sans-serif;
font-size: 14px;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
}
.mclogs-notification.show {
opacity: 1;
transform: translateX(0);
}
.mclogs-notification.error {
border-left-color: #f44336;
}
.mclogs-notification-title {
font-weight: bold;
margin-bottom: 4px;
}
.mclogs-notification-message {
font-size: 12px;
line-height: 1.4;
color: #d1d5db;
}
.mclogs-link-indicator {
position: relative;
}
.mclogs-link-indicator::after {
content: "📤";
position: absolute;
top: -5px;
right: -15px;
font-size: 10px;
opacity: 0.6;
pointer-events: none;
}
`);
// Check if URL appears to be a log file
function isLogFile(url) {
if (!url) return false;
const logExtensions = ['.log', '.txt', '.out', '.crash'];
const urlLower = url.toLowerCase();
return logExtensions.some(ext => urlLower.includes(ext)) ||
urlLower.includes('log') ||
urlLower.includes('crash') ||
urlLower.includes('latest');
}
// Create and show custom notification
function showNotification(title, message, isError = false) {
// Remove existing notifications
const existing = document.querySelectorAll('.mclogs-notification');
existing.forEach(n => n.remove());
const notification = document.createElement('div');
notification.className = `mclogs-notification ${isError ? 'error' : ''}`;
notification.innerHTML = `
<div class="mclogs-notification-title">${title}</div>
<div class="mclogs-notification-message">${message}</div>
`;
document.body.appendChild(notification);
// Trigger animation
setTimeout(() => notification.classList.add('show'), 10);
// Auto remove after 5 seconds
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 5000);
}
// Send content to mclo.gs API
function sendToMclogs(content) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'POST',
url: 'https://api.mclo.gs/1/log',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: `content=${encodeURIComponent(content)}`,
onload: function(response) {
try {
const status = response.status;
const result = JSON.parse(response.responseText);
if (status === 200 && result.success) {
resolve({
success: true,
url: result.url,
id: result.id
});
} else if (status === 429) {
reject(new Error('Rate limit exceeded. Try again in a few minutes.'));
} else {
reject(new Error(result?.error || `Upload failed (HTTP ${status})`));
}
} catch (e) {
reject(new Error('Invalid response from server'));
}
},
onerror: function() {
reject(new Error('Network error occurred'));
}
});
});
}
// Fetch file content
function fetchFileContent(url) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: url,
onload: function(response) {
if (response.status === 200) {
resolve(response.responseText);
} else {
reject(new Error(`HTTP error! status: ${response.status}`));
}
},
onerror: function(response) {
reject(new Error('Failed to fetch file'));
}
});
});
}
// Handle the upload process
async function handleUpload(linkUrl) {
try {
showNotification(
'⏳ Uploading to mclo.gs',
'Downloading and uploading log file...'
);
// Fetch the file content
const content = await fetchFileContent(linkUrl);
// Send to mclo.gs
const result = await sendToMclogs(content);
if (result.success) {
// Copy URL to clipboard
GM_setClipboard(result.url);
// Build the <a> element with DOM methods
const linkEl = document.createElement('a');
linkEl.href = result.url; // href is already a safe, encoded string
linkEl.textContent = result.url; // avoid innerHTML injection
linkEl.target = '_blank'; // optional: open in new tab
showNotification(
'✅ Upload Successful!',
'Log uploaded to mclo.gs! URL copied to clipboard:<br>',
);
} else {
throw new Error('Upload failed');
}
} catch (error) {
console.error('Error uploading to mclo.gs:', error);
showNotification(
'❌ Upload Failed',
`Failed to upload log: ${error.message}`,
true
);
}
}
// Track last right-clicked link
document.addEventListener('contextmenu', function(e) {
const link = e.target.closest('a');
if (link && link.href && isLogFile(link.href)) {
lastLogLink = link.href;
}
});
// Register menu command for uploading right-clicked log link
GM_registerMenuCommand("📤 Upload to mclo.gs", function() {
if (lastLogLink) {
handleUpload(lastLogLink);
} else {
showNotification(
'ℹ️ No Log Link Found',
'Right-click on a log file link first, then use this menu option.'
);
}
});
console.log('mclo.gs Log Uploader registered.');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment