// ==UserScript== // @name Weidian to Agent // @namespace https://www.reddit.com/user/RobotOilInc // @version 1.1.1 // @description Adds an order directly from Weidian to your agent // @author RobotOilInc // @match https://weidian.com/item.html* // @match https://*.weidian.com/item.html* // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @license MIT // @homepageURL https://greasyfork.org/en/scripts/427774-weidian-to-basetao // @supportURL https://greasyfork.org/en/scripts/427774-weidian-to-basetao // @require https://unpkg.com/js-logger@1.6.1/src/logger.min.js // @require https://unpkg.com/jquery@3.6.0/dist/jquery.min.js // @require https://greasyfork.org/scripts/401399-gm-xhr/code/GM%20XHR.js?version=938754 // @require https://greasyfork.org/scripts/11562-gm-config-8/code/GM_config%208+.js?version=66657 // @connect basetao.com // @connect superbuy.com // @connect wegobuy.com // @run-at document-end // @icon https://assets.geilicdn.com/fxxxx/favicon.ico // @downloadURL none // ==/UserScript== /** * Creates a SKU toast, which is shown because of Weidians CSS * * @param toast {string} */ const Snackbar = function (toast) { const $toast = $(`
${toast}
`).css('font-size', '20px'); // Append the toast to the body $('.sku-body').append($toast); // Set a timeout to remove it setTimeout(() => $toast.fadeOut('slow', () => { $toast.remove(); }), 2000); }; class Order { /** * @param itemName {string} * @param shopName {string} * @param price {number} * @param imageUrl {string} * @param color {string|null} * @param size {string|null} * @param model {string|null} */ constructor(itemName, shopName, price, imageUrl, color, size, model) { this.itemName = itemName; this.color = color; this.size = size; this.shopName = shopName; this.price = price; this.imageUrl = imageUrl; this.model = model; } } // Chinese SKU names for colors const chineseForColors = ['颜色', '彩色', '色', '色彩']; // Chinese SKU names for sizing const chineseForSizing = ['尺寸', '尺码', '型号尺寸', '大小', '浆液']; // Chinese SKU names for model const chineseForModel = ['型号', '模型', '模型']; /** * Trims the input text and removes all inbetween spaces as well. * * @param string {string} */ const removeWhitespaces = (string) => string.trim().replace(/\s(?=\s)/g, ''); /** * @returns {Order} */ const buildOrder = () => { // Items from Weidian const price = Number(removeWhitespaces($('.sku-cur-price').text()).replace(/(\D+)/, '')); const shopName = removeWhitespaces($('.shop-toggle-header-name').text()); const itemName = removeWhitespaces($('.item-title').text()); const imageUrl = $('img#skuPic').attr('src'); // Create dynamic items let selectedColor = null; let selectedSize = null; let selectedModel = null; // Try and find the proper SKU rows $('.sku-content .sku-row').each((key, value) => { const rowTitle = $(value).find('.row-title').text(); const selectedItem = $(value).find('.sku-item.selected'); // Check if this is model if (chineseForModel.includes(rowTitle)) { if (selectedItem.length === 0) { throw new Error('Model is missing'); } selectedModel = removeWhitespaces(selectedItem.text()); } // Check if this is color if (chineseForColors.includes(rowTitle)) { if (selectedItem.length === 0) { throw new Error('Color is missing'); } selectedColor = removeWhitespaces(selectedItem.text()); } // Check if this is size if (chineseForSizing.includes(rowTitle)) { if (selectedItem.length === 0) { throw new Error('Sizing is missing'); } selectedSize = removeWhitespaces(selectedItem.text()); } }); return new Order(itemName, shopName, price, imageUrl, selectedColor, selectedSize, selectedModel); }; class BaseTao { /** * @returns {string} */ async getCsrf() { // Grab data required to add the order const data = await $.get('https://www.basetao.com/index/selfhelporder.html'); // Check if user is actually logged in if (data.indexOf('long time no operation ,please sign in again') !== -1) { throw new Error('You need to be logged in on BaseTao to use this extension (CSRF).'); } // Convert into jQuery object const $data = $(data); // Get the username const username = $data.find('#dropdownMenu1').text(); if (typeof username === 'undefined' || username == null || username === '') { throw new Error('You need to be logged in on BaseTao to use this extension (CSRF).'); } // Return CSRF return $data.find('input[name=csrf_test_name]').first().val(); } /** * @param order {Order} */ async send(order) { // Build some extra stuff we'll need const csrf = await this.getCsrf(); const modelNote = order.model !== null ? `Model: ${order.model}` : null; // Build the data we will send const purchaseData = { csrf_test_name: csrf, color: order.color, size: order.size, number: 1, pric: order.price, shipping: 10, totalpric: order.price + 10, t_title: order.itemName, t_seller: order.shopName, t_img: order.imageUrl, t_href: window.location.href, s_url: window.location.href, buyyourself: 1, note: modelNote, site: null, }; Logger.info('Sending order to BaseTao...', purchaseData); // Do the actual call await $.ajax({ url: 'https://www.basetao.com/index/Ajax_data/buyonecart', data: purchaseData, type: 'POST', headers: { origin: 'https://www.basetao.com', referer: 'https://www.basetao.com/index/selfhelporder.html', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36', 'x-requested-with': 'XMLHttpRequest', }, }).then((response) => { if (removeWhitespaces(response) !== '1') { Logger.error('Item could not be added', response); throw new Error('Item could not be added, make sure you are logged in'); } }).catch((err) => { Logger.error('An error happened when uploading the order', err); throw new Error('An error happened when adding the order'); }); } } /** * @param input {string} * @param maxLength {number} must be an integer * @returns {string} */ const truncate = function (input, maxLength) { function isHighSurrogate(codePoint) { return codePoint >= 0xd800 && codePoint <= 0xdbff; } function isLowSurrogate(codePoint) { return codePoint >= 0xdc00 && codePoint <= 0xdfff; } function getLength(segment) { if (typeof segment !== 'string') { throw new Error('Input must be string'); } const charLength = segment.length; let byteLength = 0; let codePoint = null; let prevCodePoint = null; for (let i = 0; i < charLength; i++) { codePoint = segment.charCodeAt(i); // handle 4-byte non-BMP chars // low surrogate if (isLowSurrogate(codePoint)) { // when parsing previous hi-surrogate, 3 is added to byteLength if (prevCodePoint != null && isHighSurrogate(prevCodePoint)) { byteLength += 1; } else { byteLength += 3; } } else if (codePoint <= 0x7f) { byteLength += 1; } else if (codePoint >= 0x80 && codePoint <= 0x7ff) { byteLength += 2; } else if (codePoint >= 0x800 && codePoint <= 0xffff) { byteLength += 3; } prevCodePoint = codePoint; } return byteLength; } if (typeof input !== 'string') { throw new Error('Input must be string'); } const charLength = input.length; let curByteLength = 0; let codePoint; let segment; for (let i = 0; i < charLength; i += 1) { codePoint = input.charCodeAt(i); segment = input[i]; if (isHighSurrogate(codePoint) && isLowSurrogate(input.charCodeAt(i + 1))) { i += 1; segment += input[i]; } curByteLength += getLength(segment); if (curByteLength === maxLength) { return input.slice(0, i + 1); } if (curByteLength > maxLength) { return input.slice(0, i - segment.length + 1); } } return input; }; class WeGoBuy { /** * @param host {string} */ constructor(host) { this.host = host; } /** * @param order {Order} * @returns {string|null} */ _buildDescription(order) { const descriptionParts = []; if (order.color !== null) descriptionParts.push(`Color: ${order.color}`); if (order.size !== null) descriptionParts.push(`Size: ${order.size}`); if (order.model !== null) descriptionParts.push(`Model: ${order.model}`); let description = null; if (descriptionParts.length !== 0) { description = descriptionParts.join(' / '); } return description; } /** * @param order {Order} */ _buildPurchaseData(order) { // Build the description const description = this._buildDescription(order); // Create the purchasing data return { type: 1, shopItems: [{ shopLink: '', shopSource: 'NOCRAWLER', shopNick: '', shopId: '', goodsItems: [{ goodsId: window.location.href, goodsPrifex: 'NOCRAWLER', sku: order.imageUrl, goodsCode: '', serviceCharge: 0, freightServiceCharge: 0, price: order.price, priceNote: '', freight: 10, count: 1, picture: order.imageUrl, desc: description, spm: '', platForm: 'pc', guideGoodsId: '', is1111Yushou: 'no', goodsName: truncate(order.itemName, 100), goodsLink: window.location.href, goodsRemark: '', goodsAddTime: Math.floor(Date.now() / 1000), beginCount: 0, warehouseId: '1', }], }], }; } /** * @param order {Order} */ async send(order) { // Build the purchase data const purchaseData = this._buildPurchaseData(order); Logger.info('Sending order to WeGoBuy...', purchaseData); // Do the actual call await $.ajax({ url: `https://front.${this.host}/cart/add-cart`, data: JSON.stringify(purchaseData), type: 'POST', headers: { origin: `https://www.${this.host}`, referer: `https://www.${this.host}/`, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36', }, }).then((response) => { if (response.state !== 0 || response.msg !== 'Success') { Logger.error('Item could not be added', response.msg); throw new Error('Item could not be added'); } }).catch((err) => { Logger.error('An error happened when uploading the order', err); throw new Error('An error happened when adding the order'); }); } } /** * @param agentSelection * @returns {*} */ const getAgent = (agentSelection) => { switch (agentSelection) { case 'basetao': return new BaseTao(); case 'wegobuy': return new WeGoBuy('wegobuy.com'); case 'superbuy': return new WeGoBuy('superbuy.com'); default: throw new Error(`Agent '${agentSelection}' is not implemented`); } }; // Inject config styling GM_addStyle('div.config-dialog.config-dialog-ani { z-index: 2147483647; }'); // Setup proper settings menu GM_config.init('Settings', { serverSection: { label: 'Select your agent', type: 'section', }, agentSelection: { label: 'Your agent', type: 'select', default: 'empty', options: { empty: 'Select your agent...', basetao: 'BaseTao', superbuy: 'SuperBuy', wegobuy: 'WeGoBuy', }, }, }); // Reload page if config changed GM_config.onclose = (saveFlag) => { if (saveFlag) { window.location.reload(); } }; // Register menu within GM GM_registerMenuCommand('Settings', GM_config.open); // eslint-disable-next-line func-names (async function () { // Setup the logger. Logger.useDefaults(); // Log the start of the script. Logger.info(`Starting extension '${GM_info.script.name}', version ${GM_info.script.version}`); // Setup GM_XHR $.ajaxSetup({ xhr() { return new GM_XHR(); } }); const $button = $(``) .css('background', '#f29800') .css('color', '#FFFFFF') .css('font-size', '15px') .css('text-align', 'center') .css('padding', '15px 0') .css('width', '100%') .css('height', '100%') .css('cursor', 'pointer'); $button.on('click', async () => { // Disable button to prevent double clicks and show clear message $button.attr('disabled', true).text('Processing...'); // Get the agent related to our config const agent = getAgent(GM_config.get('agentSelection')); // Try to build and send the order try { await agent.send(buildOrder()); } catch (err) { $button.attr('disabled', false).text(`Add to ${GM_config.get('agentSelection')}`); return Snackbar(err); } // Remove button once done $button.fadeOut('slow', () => $button.remove()); // Success, tell the user return Snackbar('Item has been added, be sure to double check it'); }); // Setup for when someone presses the buy button $('.buy-now').on('click', () => { // Force someone to select an agent if (GM_config.get('agentSelection') === 'empty') { alert('Please select what agent you use'); GM_config.open(); return; } $('.sku-footer').before($button); }); }());