您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Upload multiple T-Shirts/Decals easily with AnnaUploader
当前为
- // ==UserScript==
- // @name AnnaUploader (Roblox Multi-File Uploader)
- // @namespace https://www.guilded.gg/u/AnnaBlox
- // @version 3.4
- // @description Upload multiple T-Shirts/Decals easily with AnnaUploader
- // @match https://create.roblox.com/*
- // @grant GM_getValue
- // @grant GM_setValue
- // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- const ROBLOX_UPLOAD_URL = "https://apis.roblox.com/assets/user-auth/v1/assets";
- const ASSET_TYPE_TSHIRT = 11;
- const ASSET_TYPE_DECAL = 13;
- const UPLOAD_RETRY_DELAY = 2000;
- const MAX_RETRIES = 3;
- // ========== PERSISTENT USER CONFIG ========== //
- // Will default to this value only once, then store whatever you enter
- let USER_ID = GM_getValue('userId', 32456865);
- // ============================================ //
- let uploadQueue = [];
- let isUploading = false;
- let csrfToken = null;
- async function fetchCSRFToken() {
- try {
- const response = await fetch(ROBLOX_UPLOAD_URL, {
- method: 'POST',
- credentials: 'include',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({})
- });
- if (response.status === 403) {
- const token = response.headers.get('x-csrf-token');
- if (token) {
- console.log('[CSRF] Token fetched:', token);
- csrfToken = token;
- return token;
- }
- }
- throw new Error('Failed to fetch CSRF token');
- } catch (error) {
- console.error('[CSRF] Fetch error:', error);
- throw error;
- }
- }
- async function uploadFile(file, assetType, retries = 0) {
- if (!csrfToken) {
- await fetchCSRFToken();
- }
- const formData = new FormData();
- formData.append("fileContent", file, file.name);
- formData.append("request", JSON.stringify({
- displayName: file.name.split('.')[0],
- description: "Uploaded Using AnnaUploader",
- assetType: assetType === ASSET_TYPE_TSHIRT ? "TShirt" : "Decal",
- creationContext: {
- creator: { userId: USER_ID },
- expectedPrice: 0
- }
- }));
- try {
- const response = await fetch(ROBLOX_UPLOAD_URL, {
- method: "POST",
- credentials: "include",
- headers: { "x-csrf-token": csrfToken },
- body: formData
- });
- if (response.ok) {
- console.log(`✅ Uploaded (${assetType === ASSET_TYPE_TSHIRT ? "TShirt" : "Decal"}): ${file.name}`);
- } else {
- const responseText = await response.text();
- console.error(`❌ Upload failed for ${file.name}: [${response.status}]`, responseText);
- if (response.status === 403 && retries < MAX_RETRIES) {
- console.warn(`Fetching new CSRF and retrying ${file.name}...`);
- csrfToken = null;
- await new Promise(res => setTimeout(res, UPLOAD_RETRY_DELAY));
- await uploadFile(file, assetType, retries + 1);
- } else {
- throw new Error(`Failed to upload after ${retries} retries.`);
- }
- }
- } catch (error) {
- console.error(`Upload error for ${file.name}:`, error);
- throw error;
- }
- }
- async function processUploadQueue() {
- if (isUploading || uploadQueue.length === 0) return;
- isUploading = true;
- const { file, assetType } = uploadQueue.shift();
- try {
- await uploadFile(file, assetType);
- } catch (error) {
- console.error('Queue error:', error);
- } finally {
- isUploading = false;
- processUploadQueue();
- }
- }
- function handleFileSelect(files, assetType, uploadBoth = false) {
- if (!files || files.length === 0) {
- console.warn('No files selected.');
- return;
- }
- for (let i = 0; i < files.length; i++) {
- if (uploadBoth) {
- uploadQueue.push({ file: files[i], assetType: ASSET_TYPE_TSHIRT });
- uploadQueue.push({ file: files[i], assetType: ASSET_TYPE_DECAL });
- console.log(`Queued (Both): ${files[i].name}`);
- } else {
- uploadQueue.push({ file: files[i], assetType });
- console.log(`Queued (${assetType === ASSET_TYPE_TSHIRT ? "TShirt" : "Decal"}): ${files[i].name}`);
- }
- }
- processUploadQueue();
- }
- function createUploaderUI() {
- const container = document.createElement('div');
- container.style.position = 'fixed';
- container.style.top = '10px';
- container.style.right = '10px';
- container.style.backgroundColor = '#fff';
- container.style.border = '2px solid #000';
- container.style.padding = '15px';
- container.style.zIndex = '10000';
- container.style.borderRadius = '8px';
- container.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
- container.style.display = 'flex';
- container.style.flexDirection = 'column';
- container.style.gap = '10px';
- container.style.fontFamily = 'Arial, sans-serif';
- const title = document.createElement('h3');
- title.textContent = 'Multi-File Uploader';
- title.style.margin = '0';
- title.style.fontSize = '16px';
- container.appendChild(title);
- // Upload buttons
- const uploadTShirtBtn = document.createElement('button');
- uploadTShirtBtn.textContent = 'Upload T-Shirts';
- uploadTShirtBtn.style.padding = '8px';
- uploadTShirtBtn.style.cursor = 'pointer';
- uploadTShirtBtn.addEventListener('click', () => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.multiple = true;
- input.addEventListener('change', e => handleFileSelect(e.target.files, ASSET_TYPE_TSHIRT));
- input.click();
- });
- const uploadDecalBtn = document.createElement('button');
- uploadDecalBtn.textContent = 'Upload Decals';
- uploadDecalBtn.style.padding = '8px';
- uploadDecalBtn.style.cursor = 'pointer';
- uploadDecalBtn.addEventListener('click', () => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.multiple = true;
- input.addEventListener('change', e => handleFileSelect(e.target.files, ASSET_TYPE_DECAL));
- input.click();
- });
- const uploadBothBtn = document.createElement('button');
- uploadBothBtn.textContent = 'Upload Both';
- uploadBothBtn.style.padding = '8px';
- uploadBothBtn.style.cursor = 'pointer';
- uploadBothBtn.addEventListener('click', () => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.multiple = true;
- input.addEventListener('change', e => handleFileSelect(e.target.files, null, true));
- input.click();
- });
- // Change ID button
- const changeIdBtn = document.createElement('button');
- changeIdBtn.textContent = 'Change ID';
- changeIdBtn.style.padding = '8px';
- changeIdBtn.style.cursor = 'pointer';
- changeIdBtn.addEventListener('click', () => {
- const newId = prompt("Enter your Roblox User ID:", USER_ID);
- if (newId && !isNaN(newId)) {
- USER_ID = Number(newId);
- GM_setValue('userId', USER_ID);
- alert(`User ID updated to ${USER_ID}`);
- } else {
- alert("Invalid ID. Please enter a numeric value.");
- }
- });
- const pasteHint = document.createElement('div');
- pasteHint.textContent = 'Paste images (Ctrl+V) to upload as decals!';
- pasteHint.style.fontSize = '12px';
- pasteHint.style.color = '#555';
- // Append everything
- container.appendChild(uploadTShirtBtn);
- container.appendChild(uploadDecalBtn);
- container.appendChild(uploadBothBtn);
- container.appendChild(changeIdBtn);
- container.appendChild(pasteHint);
- document.body.appendChild(container);
- }
- function handlePaste(event) {
- const items = event.clipboardData?.items;
- if (!items) return;
- let blob = null;
- for (let i = 0; i < items.length; i++) {
- if (items[i].type.indexOf('image') === 0) {
- blob = items[i].getAsFile();
- break;
- }
- }
- if (blob) {
- event.preventDefault();
- const now = new Date();
- const filename = `pasted_image_${now.toISOString().replace(/[^a-z0-9]/gi, '_')}.png`;
- const file = new File([blob], filename, { type: blob.type });
- handleFileSelect([file], ASSET_TYPE_DECAL);
- }
- }
- function init() {
- createUploaderUI();
- document.addEventListener('paste', handlePaste);
- console.log('[Uploader] Initialized with User ID:', USER_ID);
- }
- window.addEventListener('load', init);
- })();