// ==UserScript==
// @name BT-QC Tools: Easy add Goofish to Basetao
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Adds a form and button to add items to Basetao on Goofish pages
// @author Kaj
// @match https://www.goofish.com/item*
// @grant GM_xmlhttpRequest
// @connect basetao.com
// @connect www.basetao.com
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=goofish.com
// ==/UserScript==
(function() {
'use strict';
const CSRF_REQUIRED_ERROR = "You need to be logged in on BaseTao to use this extension (CSRF required).";
class BaseTao {
constructor() {
this.parser = new DOMParser();
}
async getCSRFToken(domain) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `${domain}/best-taobao-agent-service/how_make/buy_order.html`,
onload: (response) => {
if (response.responseText.includes("please sign in again")) {
reject(new Error(CSRF_REQUIRED_ERROR));
return;
}
const doc = this.parser.parseFromString(response.responseText, "text/html");
const csrfToken = doc.querySelector("input[name=bt_sb_token]");
if (csrfToken && csrfToken.value.length !== 0) {
resolve(csrfToken.value);
} else {
reject(new Error(CSRF_REQUIRED_ERROR));
}
},
onerror: () => reject(new Error("Failed to get CSRF token"))
});
});
}
async getDomain() {
try {
await this.getCSRFToken("https://www.basetao.com");
return "https://www.basetao.com";
} catch (error) {
try {
await this.getCSRFToken("https://basetao.com");
return "https://basetao.com";
} catch (error) {
throw new Error(CSRF_REQUIRED_ERROR);
}
}
}
async submitOrder(formData) {
try {
const domain = await this.getDomain();
const csrf = await this.getCSRFToken(domain);
const orderData = {
addtime: Date.now(),
goodscolor: formData.color || "-",
goodsimg: formData.imageUrl || "",
goodsname: formData.itemName,
goodsnum: 1,
goodsprice: formData.price,
goodsremark: "",
goodsseller: formData.seller,
goodssite: "goofish",
goodssize: formData.size || "-",
goodsurl: formData.link,
item_id: formData.link.split("?id=")[1] || "",
sellerurl: "",
sendprice: formData.shippingPrice,
siteurl: window.location.hostname,
sku_id: 0,
type: 1
};
const requestData = {
bt_sb_token: csrf,
data: JSON.stringify(orderData)
};
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: `${domain}/best-taobao-agent-service/bt_action/add_cart`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Origin': domain,
'Referer': `${domain}/best-taobao-agent-service/how_make/buy_order.html`,
'X-Requested-With': 'XMLHttpRequest'
},
data: new URLSearchParams(requestData).toString(),
onload: (response) => {
try {
const responseData = JSON.parse(response.responseText);
if (responseData.value === "1") {
resolve();
} else {
reject(new Error("Item could not be added, make sure you are logged in"));
}
} catch (e) {
reject(new Error("Invalid response from BaseTao"));
}
},
onerror: () => reject(new Error("Failed to connect to BaseTao"))
});
});
} catch (error) {
throw error;
}
}
}
function getImageUrl() {
const imageElement = document.querySelector('#content > div.item-container--yLJD5VZj > div.item-main-container--jhpFKlaS > div.item-main-window--BgQbsIsU > div.item-main-window-carousel--OJQgNH3d > div.carousel-container--DdMer_ii > div.carousel--MBcZaegk > div > div > div > div > div.slick-slide.slick-active.slick-current > div > div > img');
if (imageElement) {
return imageElement.src;
}
return '';
}
function getAllImageUrls() {
const imageContainer = document.querySelector('.item-main-window-list--od7DK4Fm');
if (!imageContainer) return [];
const images = imageContainer.querySelectorAll('img.fadeInImg--DnykYtf4');
return Array.from(images).map(img => {
// Convert relative URLs to absolute URLs and remove webp extension
let url = img.src;
if (url.startsWith('//')) {
url = 'https:' + url;
}
return url.replace('_.webp', '');
});
}
function createImageSelector(images) {
const div = document.createElement('div');
div.style.marginBottom = '10px';
const label = document.createElement('label');
label.textContent = 'Select Image: ';
label.style.display = 'block';
label.style.marginBottom = '5px';
const imageSelect = document.createElement('div');
imageSelect.style.display = 'flex';
imageSelect.style.flexWrap = 'wrap';
imageSelect.style.gap = '10px';
imageSelect.style.maxHeight = '200px';
imageSelect.style.overflowY = 'auto';
images.forEach((imageUrl, index) => {
const imgContainer = document.createElement('div');
imgContainer.style.position = 'relative';
imgContainer.style.width = '80px';
imgContainer.style.height = '80px';
imgContainer.style.border = '2px solid transparent';
imgContainer.style.cursor = 'pointer';
const img = document.createElement('img');
img.src = imageUrl;
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'contain';
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = 'selectedImage';
radio.value = imageUrl;
radio.style.position = 'absolute';
radio.style.top = '5px';
radio.style.left = '5px';
if (index === 0) radio.checked = true;
imgContainer.appendChild(img);
imgContainer.appendChild(radio);
// Add click handler for the entire container
imgContainer.addEventListener('click', () => {
radio.checked = true;
});
imageSelect.appendChild(imgContainer);
});
div.appendChild(label);
div.appendChild(imageSelect);
return div;
}
function createFormField(label, id, value = '') {
const div = document.createElement('div');
div.style.marginBottom = '12px';
div.style.maxWidth = '600px'; // Added max-width for better alignment
const labelElement = document.createElement('label');
labelElement.textContent = label + ': ';
labelElement.htmlFor = id;
labelElement.style.display = 'block';
labelElement.style.marginBottom = '6px';
labelElement.style.fontSize = '14px';
labelElement.style.color = '#333';
const input = document.createElement('input');
input.type = 'text';
input.id = id;
input.value = value;
input.style.width = '100%';
input.style.padding = '8px 12px';
input.style.borderRadius = '6px';
input.style.border = '1px solid #d9d9d9';
input.style.fontSize = '14px';
input.style.transition = 'border-color 0.3s ease';
input.style.boxSizing = 'border-box'; // Added to include padding in width calculation
// Add hover and focus effects
input.addEventListener('hover', () => {
input.style.borderColor = '#40a9ff';
});
input.addEventListener('focus', () => {
input.style.borderColor = '#40a9ff';
input.style.outline = 'none';
input.style.boxShadow = '0 0 0 2px rgba(24, 144, 255, 0.2)';
});
div.appendChild(labelElement);
div.appendChild(input);
return div;
}
function showNotification(message, isError = false) {
const notification = document.createElement('div');
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.padding = '16px 24px';
notification.style.backgroundColor = isError ? '#ff4d4f' : '#52c41a';
notification.style.color = 'white';
notification.style.borderRadius = '8px';
notification.style.zIndex = '9999';
notification.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
notification.style.minWidth = '250px';
notification.style.maxWidth = '400px';
notification.style.fontSize = '14px';
notification.style.fontWeight = '500';
notification.style.display = 'flex';
notification.style.alignItems = 'center';
notification.style.opacity = '0';
notification.style.transform = 'translateY(-20px)';
notification.style.transition = 'all 0.3s ease-in-out';
// Add icon based on type
const icon = document.createElement('span');
icon.style.marginRight = '12px';
icon.style.fontSize = '18px';
icon.textContent = isError ? '✕' : '✓';
notification.appendChild(icon);
notification.appendChild(document.createTextNode(message));
document.body.appendChild(notification);
// Trigger animation
setTimeout(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateY(0)';
}, 10);
// Remove notification with fade out
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateY(-20px)';
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// Get data functions
function getPrice() {
const priceElement = document.querySelector('.price--OEWLbcxC');
if (priceElement) {
return priceElement.textContent.trim();
}
return '';
}
function getItemName() {
const nameElement = document.querySelector('.main--Nu33bWl6.open--gEYf_BQc span span span');
if (nameElement) {
return nameElement.textContent.trim();
}
return '';
}
function getSeller() {
const sellerElement = document.querySelector('.item-user-info-nick--rtpDhkmQ');
if (sellerElement) {
return sellerElement.textContent.trim();
}
return '';
}
function findContainer() {
const containers = [
'.notLoginContainer--hQCDYhxp',
'.item-main-info--ExVwW2NW',
'.value--EyQBSInp'
];
for (const selector of containers) {
const container = document.querySelector(selector);
if (container) {
return container;
}
}
return null;
}
function addBasetaoForm() {
const container = findContainer();
if (container && !document.querySelector('#basetaoForm')) {
// Create form container
const formContainer = document.createElement('div');
formContainer.id = 'basetaoForm';
formContainer.style.padding = '20px';
formContainer.style.marginBottom = '15px';
formContainer.style.backgroundColor = '#f5f5f5';
formContainer.style.borderRadius = '8px';
formContainer.style.maxWidth = '600px';
// Add form fields
const itemName = getItemName();
const seller = getSeller();
const price = getPrice();
formContainer.appendChild(createFormField('Item Name', 'basetaoItemName', itemName));
formContainer.appendChild(createFormField('Seller', 'basetaoSeller', seller));
formContainer.appendChild(createFormField('Link', 'basetaoLink', window.location.href));
formContainer.appendChild(createFormField('Size', 'basetaoSize'));
formContainer.appendChild(createFormField('Color', 'basetaoColor'));
formContainer.appendChild(createFormField('Price', 'basetaoPrice', price));
formContainer.appendChild(createFormField('Shipping Price', 'basetaoShipping', '10'));
const basetao = new BaseTao();
// Create button
const basetaoButton = document.createElement('button');
basetaoButton.id = 'basetaoButton';
basetaoButton.textContent = 'Add to Basetao';
basetaoButton.style.padding = '10px 20px';
basetaoButton.style.marginTop = '12px';
basetaoButton.style.backgroundColor = '#FFA500';
basetaoButton.style.color = '#fff';
basetaoButton.style.border = 'none';
basetaoButton.style.borderRadius = '6px';
basetaoButton.style.cursor = 'pointer';
basetaoButton.style.width = '100%';
basetaoButton.style.fontSize = '14px';
basetaoButton.style.fontWeight = '500';
basetaoButton.style.transition = 'background-color 0.3s ease';
basetaoButton.addEventListener('mouseenter', () => {
basetaoButton.style.backgroundColor = '#ff9000';
});
basetaoButton.addEventListener('mouseleave', () => {
basetaoButton.style.backgroundColor = '#FFA500';
});
basetaoButton.addEventListener('click', async () => {
try {
const formData = {
itemName: document.getElementById('basetaoItemName').value,
seller: document.getElementById('basetaoSeller').value,
link: document.getElementById('basetaoLink').value,
size: document.getElementById('basetaoSize').value,
color: document.getElementById('basetaoColor').value,
price: document.getElementById('basetaoPrice').value,
shippingPrice: document.getElementById('basetaoShipping').value,
imageUrl: getImageUrl() // Add the image URL to the form data
};
await basetao.submitOrder(formData);
showNotification('Successfully added to Basetao!');
} catch (error) {
showNotification(error.message, true);
}
});
formContainer.appendChild(basetaoButton);
container.appendChild(formContainer);
}
}
// Try to add the form multiple times as the page loads
const attemptToAdd = () => {
addBasetaoForm();
};
// Initial attempts with delays
setTimeout(attemptToAdd, 1000);
setTimeout(attemptToAdd, 2000);
setTimeout(attemptToAdd, 3000);
// Use MutationObserver for dynamic updates
const observer = new MutationObserver((mutations) => {
attemptToAdd();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial attempt
attemptToAdd();
})();