Weidian to Agent

Adds an order directly from Weidian to your agent

当前为 2021-06-12 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Weidian to Agent
// @namespace    https://www.reddit.com/user/RobotOilInc
// @version      1.2.4
// @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-agent
// @supportURL   https://greasyfork.org/en/scripts/427774-weidian-to-agent
// @require      https://unpkg.com/[email protected]/src/logger.min.js
// @require      https://unpkg.com/[email protected]/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
// ==/UserScript==

// 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, '');

class Order {
  constructor() {
    // Build order price
    this.price = Number(removeWhitespaces($('.sku-cur-price').text()).replace(/(\D+)/, ''));

    // Build shop information
    const $shop = $('.shop-name-str');
    this.shopUrl = $shop.parents('a').first().attr('href').replace('//weidian.com', 'https://weidian.com');
    this.shopId = this.shopUrl.replace(/^\D+/g, '');
    this.shopName = removeWhitespaces($shop.text());

    // Build item information
    this.itemName = removeWhitespaces($('.item-title').text());
    this.itemId = window.location.href.match(/[?&]itemId=(\d+)/i)[1];
    this.itemImageUrl = $('img#skuPic').attr('src');

    // Decide on shipping (if we can't find any numbers, assume free)
    const postageMatches = removeWhitespaces($('.postage-block').text()).match(/([\d.]+)/);
    this.shipping = postageMatches !== null ? Number(postageMatches[0]) : 0;

    // Create dynamic items
    this.model = null;
    this.color = null;
    this.size = null;

    // Load dynamic items
    $('.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');
        }

        this.model = removeWhitespaces(selectedItem.text());
      }

      // Check if this is color
      if (chineseForColors.includes(rowTitle)) {
        if (selectedItem.length === 0) {
          throw new Error('Color is missing');
        }

        this.color = removeWhitespaces(selectedItem.text());
      }

      // Check if this is size
      if (chineseForSizing.includes(rowTitle)) {
        if (selectedItem.length === 0) {
          throw new Error('Sizing is missing');
        }

        this.size = removeWhitespaces(selectedItem.text());
      }
    });
  }
}

/**
 * Creates a SKU toast, which is shown because of Weidians CSS
 *
 * @param toast {string}
 */
const Snackbar = function (toast) {
  const $toast = $(`<div class="sku-toast">${toast}</div>`).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);
};

/**
 * Waits for an element satisfying selector to exist, then resolves promise with the element.
 * Useful for resolving race conditions.
 *
 * @param selector
 * @returns {Promise}
 */
const elementReady = function (selector) {
  return new Promise((resolve) => {
    const el = document.querySelector(selector);
    if (el) {
      resolve(el);
    }

    new MutationObserver((mutationRecords, observer) => {
      // Query for elements matching the specified selector
      Array.from(document.querySelectorAll(selector)).forEach((element) => {
        resolve(element);
        // Once we have resolved we don't need the observer anymore.
        observer.disconnect();
      });
    }).observe(document.documentElement, {
      childList: true,
      subtree: true,
    });
  });
};

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: order.shipping,
      totalpric: order.price + 10,
      t_title: order.itemName,
      t_seller: order.shopName,
      t_img: order.itemImageUrl,
      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');
    });
  }
}

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: [{
        shopSource: 'Weidian',
        shopLink: order.shopUrl,
        shopNick: order.shopName,
        shopId: order.shopId,
        goodsItems: [{
          count: 1,
          beginCount: 0,
          sku: '3780769661172',
          freight: order.shipping,
          freightServiceCharge: 0,
          goodsAddTime: Math.floor(Date.now() / 1000),
          goodsId: order.itemId,
          goodsPrifex: 'Weidian',
          goodsCode: `Weidian--${order.itemId}-${order.model}-${order.color}-${order.size}`,
          goodsLink: window.location.href,
          goodsName: order.itemName,
          goodsRemark: description,
          desc: description,
          picture: order.itemImageUrl,
          platForm: 'pc',
          price: order.price,
          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(); } });

  // Setup for when someone presses the buy button
  $('.footer-btn-container > span').add('.item-container > .sku-button').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;
    }

    // Attach button the the footer
    elementReady('.sku-footer').then((element) => {
      const $button = $(`<button>Add to ${GM_config.get('agentSelection')}</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(new Order());
        } catch (err) {
          $button.attr('disabled', false).text(`Add to ${GM_config.get('agentSelection')}`);
          return Snackbar(err);
        }

        // Success, tell the user
        return Snackbar('Item has been added, be sure to double check it');
      });

      $(element).before($button);
    });
  });
}());