Adds a button to upload any source files to Grok by renaming them to .txt so they are accepted.
Versión del día
// ==UserScript==
// @name Grok Source File Uploader
// @namespace https://greasyfork.org/users/your-username
// @version 1.1
// @description Adds a button to upload any source files to Grok by renaming them to .txt so they are accepted.
// @author Moore
// @match *://x.com/i/grok*
// @match *://*.x.com/i/grok*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
function injectUploadButton() {
const mediaBtn = document.querySelector('button[aria-label="Media"]');
if (!mediaBtn || document.getElementById('custom-grok-upload-wrapper')) return;
// Create wrapper div matching structure
const wrapperDiv = document.createElement("div");
wrapperDiv.className = mediaBtn.parentElement.className;
wrapperDiv.id = "custom-grok-upload-wrapper";
// Clone the original Media button
const customBtn = mediaBtn.cloneNode(true);
customBtn.id = "custom-grok-upload";
customBtn.setAttribute("aria-label", "Upload source files");
// Replace the SVG icon (styled like Grok's)
customBtn.querySelector('svg').outerHTML = `
<svg viewBox="0 0 24 24" aria-hidden="true"
class="r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-1tl9yi8 r-1janqcz"
style="color: rgb(182, 185, 188);">
<g>
<path d="M5 20h14v-2H5v2zm7-18L5.33 9h3.17v4h4.99v-4h3.17L12 2z"></path>
</g>
</svg>
`;
// Hidden file input for multi-upload
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = "*.*";
fileInput.multiple = true;
fileInput.style.display = "none";
customBtn.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", async (e) => {
const files = Array.from(e.target.files);
if (files.length === 0) return;
const nativeInput = document.querySelector('input[type="file"]');
if (!nativeInput) {
alert("❌ Grok file input not found. Try clicking the 📎 icon manually first.");
return;
}
const dt = new DataTransfer();
for (const file of files) {
const content = await file.text();
const renamedFile = new File(
[new Blob([content], { type: "text/plain" })],
file.name.replace(/\.[^/.]+$/, "") + ".txt",
{ type: "text/plain" }
);
dt.items.add(renamedFile);
}
nativeInput.files = dt.files;
nativeInput.dispatchEvent(new Event("change", { bubbles: true }));
fileInput.value = "";
});
wrapperDiv.appendChild(customBtn);
document.body.appendChild(fileInput);
// Insert before the 📎 Media button's wrapper
mediaBtn.parentElement.parentElement.insertBefore(wrapperDiv, mediaBtn.parentElement);
}
// Re-inject reliably on dynamic navigation
const observer = new MutationObserver(() => {
injectUploadButton();
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// One initial manual inject
setTimeout(injectUploadButton, 1000);
})();