Greasy Fork

Greasy Fork is available in English.

FR:ES - BaseTao

Upload Taobao and Yupoo QCs from BaseTao to Imgur

当前为 2021-05-25 提交的版本,查看 最新版本

// ==UserScript==
// @name        FR:ES - BaseTao
// @namespace   https://www.reddit.com/user/RobotOilInc
// @author      RobotOilInc
// @version     1.0.0
// @description Upload Taobao and Yupoo QCs from BaseTao to Imgur
// @match       https://www.basetao.net/index/myhome/myorder/*
// @match       https://basetao.net/index/myhome/myorder/*
// @match       https://www.basetao.com/index/myhome/myorder/*
// @match       https://basetao.com/index/myhome/myorder/*
// @include     https://www.basetao.net/index/orderphoto/itemimg/*
// @include     https://basetao.net/index/orderphoto/itemimg/*
// @include     https://www.basetao.com/index/orderphoto/itemimg/*
// @include     https://basetao.com/index/orderphoto/itemimg/*
// @connect     self
// @connect     imgur.com
// @connect     fashionreps.tools
// @connect     127.0.0.1
// @connect     localhost
// @require     https://code.jquery.com/jquery-3.4.1.min.js
// @require     http://greasyfork.icu/scripts/401399-gm-xhr/code/GM%20XHR.js
// @require     http://greasyfork.icu/scripts/426288-webptojpg/code/WebpToJpg.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js
// @require     http://greasyfork.icu/scripts/426983-gm-fetch/code/GM%20Fetch.js
// @require     https://unpkg.com/swagger-client
// @grant       GM_openInTab
// @grant       GM_notification
// @grant       GM_xmlhttpRequest
// @run-at      document-end
// @icon        https://i.imgur.com/1aQAxbC.png
// ==/UserScript==

/* jshint esversion: 8 */
/* globals $:false, SparkMD5:false, GM_XHR: false, WebpToJpg: false, SwaggerClient: false */

const _version = '1.0.0';
const _url = 'https://localhost:8000/';
const _swagger_doc_url = `${_url}api/doc.json`;

const ClientIds = ['97a5f748b20b0ad', '1fb6eded0178bb6', '4d01890bba4949c', 'fe9f4d770f42e53', '0999af252aaaba1', '6870f32f716ff3b', 'bf02cd90c8a4f1a', '5ed540f56122e1c'];
const ActiveClientId = ClientIds[Math.floor(Math.random() * ClientIds.length)];

// eslint-disable-next-line func-names
(async function () {
  // Create Swagger client from our own documentation
  const client = await new SwaggerClient({ url: _swagger_doc_url, requestContentType: 'application/json', responseContentType: 'application/json' });

  const username = $('#dropdownMenu1').text();
  const userHash = SparkMD5.hash(username);

  const toDataURL = (url) => fetch(url)
    .then((response) => response.blob())
    .then((blob) => new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    }));

  // Setup GM_XHR
  $.ajaxSetup({
    xhr() {
      return new GM_XHR();
    },
  });

  const CreateOptions = async ($this) => {
    const options = await CreateBasicOptions($this);

    const sizingInfo = $.trim(`Color: ${options.color}${(options.size !== 'NO') ? ` - Size: ${options.size}` : ''}`);
    const originalSizing = $.trim(`Color: ${options.color}${(options.size !== 'NO') ? ` - Size: ${options.size}` : ''}`);
    const existingAlbum = await ExistingAlbum(options.w2c, sizingInfo, originalSizing);

    options.sizing = sizingInfo;
    options.originalSizing = originalSizing;
    options.existingAlbum = existingAlbum;

    return options;
  };

  const CreateBasicOptions = async ($this) => {
    const $baseElement = $this.parents('tr').find('td[colspan=\'2\']').first();

    const w2cLink = $baseElement.find('.goodsname_color').first().attr('href').replace('http://', 'https://');
    const title = $baseElement.find('.goodsname_color').first().text();

    const color = $baseElement.find('.size_color_color:nth-child(1) > u').text();
    const size = $baseElement.find('.size_color_color:nth-child(2) > u').text();

    const orderId = parseInt($this.parents('tr').prev().find('td.tdpd > span:nth-child(2)').text());

    return {
      orderId, title, object: $this, w2c: w2cLink, color, size,
    };
  };

  const ExistingAlbum = async (w2c, originalSizing) => {
    let existingAlbum;

    try {
      existingAlbum = await $.get(`${_url}qcdb/hasUploadedBefore.php?userhash=${userHash}&w2c=${encodeURI(w2c)}&sizing=${encodeURI(originalSizing)}`);

      // Connection failed, means an error on FR:ES. Return -1 to indicate issue.
      if (existingAlbum.indexOf('Connection failed') !== -1) {
        return -1;
      }

      if (existingAlbum !== 0) {
        return existingAlbum;
      }

      return 0;
    } catch (error) {
      console.error(`An error happened: ${error.statusText}`, error);
    }
  };

  const HasExistingQc = async (w2c) => {
    try {
      const data = await $.get(`${_url}qcdb/qcExists.php`, { w2c });

      // No return equals no QC
      if (typeof data === 'undefined' || data === '') {
        return '0';
      }

      // Connection failed, means an error on FR:ES. Return -1 to indicate issue.
      if (data.indexOf('Connection failed') !== -1) {
        return '-1';
      }

      // Try to parse JSON, if it fails, issue with FR:ES. Return -1 to indicate issue.
      try {
        return JSON.parse(data).exists;
      } catch (error) {
        console.error('An error happened when parsing JSON', error);

        return '-1';
      }
    } catch (error) {
      // No actual error, means no QC
      if (error.responseText === '') {
        return '0';
      }

      // Log error and return -1 to indicate issue.
      console.error(`An error happened: ${error.statusText}`, error);

      return '-1';
    }
  };

  const CreateAlbum = async (options) => {
    let result;

    try {
      result = await $.ajax({
        url: 'https://api.imgur.com/3/album',
        headers: { Authorization: `Client-ID ${ActiveClientId}` },
        type: 'POST',
        data: {
          title: options.title,
          privacy: 'hidden',
          description: `Auto uploaded using BaseTao to Imgur (v${_version}): http://greasyfork.icu/scripts/387421-fr-es-basetao-extension`,
        },
      });

      return result;
    } catch (error) {
      // Log the error somewhere
      console.error(`Could not make an ablbum: ${error.statusText}`, error);

      // If we uploaded too fast, tell the user
      if (error.responseJSON.data.error.code === 429) {
        GM_notification(`Imgur is telling us to slow down: ${error.responseJSON.data.error.message}`, 'FR:ES - BaseTao');
        return;
      }

      // Tell the user that "something" is wrong
      GM_notification('Could not make an ablbum, please try again later...', 'FR:ES - BaseTao');
    }
  };

  const AddImageToAlbum = async (base64Image, deleteHash, w2c) => {
    try {
      await $.ajax({
        url: 'https://api.imgur.com/3/image',
        headers: { Authorization: `Client-ID ${ActiveClientId}` },
        type: 'POST',
        data: {
          image: base64Image, type: 'base64', album: deleteHash, description: `W2C: ${w2c}`,
        },
      });

      return true;
    } catch (error) {
      // If we uploaded too many files, tell the user
      if (error.responseJSON.data.error.code === 429) {
        GM_notification(`Imgur is telling us to slow down: ${error.responseJSON.data.error.message}`, 'FR:ES - BaseTao');
      }

      // Log errors somewhere
      console.error(`An error happened when uploading the image: ${error.responseJSON.data.error.message}`, error);
      return false;
    }
  };

  const UploadToImgur = async (options) => {
    const $processing = $('<ul><li><span style="cursor: progress;margin-left:-29px;"><img src="https://i.imgur.com/lnFQTQz.gif" title="Processing..."></span></li></ul>');
    const $base = options.object;

    $base.after($processing);
    $base.hide();

    GM_notification('Pictures are being uploaded....', 'FR:ES - BaseTao');

    // Create the album
    const response = await CreateAlbum(options);
    const deleteHash = response.data.deletehash;
    const albumId = response.data.id;

    const AlbumLink = `https://imgur.com/a/${albumId}`;

    // Upload all QC images
    let uploadedImages = 0;
    const promises = [];
    $.each(options.imageUrls, (key, imageUrl) => {
      // Convert to base64, since Imgur cannot access our images
      promises.push(toDataURL(imageUrl).then(async (data) => {
        // Store our base64 and if the file is WEBP, convert it to JPG
        let base64Image = data;
        if (base64Image.indexOf('image/webp') !== -1) {
          base64Image = await WebpToJpg(base64Image);
        }

        // Remove the unnecassary `data:` part
        const cleanedData = base64Image.replace(/(data:image\/.*;base64,)/, '');

        // Upload the image to the album
        return AddImageToAlbum(cleanedData, deleteHash, options.w2c);
      }).then((uploaded) => {
        if (uploaded === false) {
          return;
        }

        uploadedImages++;
      }));
    });

    // Wait until everything has been tried to be uploaded
    await Promise.all(promises);

    // If not all images have been uploaded, abort everything
    if (uploadedImages !== options.imageUrls.length) {
      GM_notification('Error: Image upload failed. Try again later.', 'FR:ES - BaseTao');
      $.ajax({ headers: { Authorization: `Client-ID ${ActiveClientId}` }, url: `https://api.imgur.com/3/album/${deleteHash}`, type: 'DELETE' });

      $processing.remove();
      $base.show();

      return;
    }

    // Tell the user it was uploaded and open the album in the background
    GM_notification('Pictures have been uploaded!', 'FR:ES - BaseTao');
    GM_openInTab(AlbumLink, true);

    // Tell QC Suite about our uploaded QC's (if it's from TaoBao)
    if (options.w2c.indexOf('item.taobao.com') !== -1) {
      $.post(`${_url}qcdb/qcdb.php`, {
        userhash: userHash,
        imgur: albumId,
        w2c: options.w2c,
        sizing: options.sizing,
        source: 'baseTaoToImgur',
      });
    }

    // Wrap the logo in a href to the new album
    const $image = $base.find('img');
    $image.wrap(`<a href='${AlbumLink}' target='_blank' title='Go to album'></a>`);
    $image.removeAttr('title');

    // Remove processing
    $processing.remove();

    // Update the marker
    const $qcMarker = $base.find('.qc-marker').first();
    $qcMarker.attr('title', 'You have uploaded your QC');
    $qcMarker.css('cursor', 'help');
    $qcMarker.css('color', 'green');
    $qcMarker.text('✓');

    // Remove the click handler
    $base.off();

    // Show it again
    $base.show();
  };

  const basicUploadHandler = async function () {
    const $this = $(this);
    const itemUrl = $this.data('item-url');
    const options = await CreateBasicOptions($this);

    // Go to the QC pictures URL and grab all image src's
    $.get(itemUrl, (data) => {
      const imageUrls = [];
      $('<div/>').html(data).find('div.container.container-top60 > img').each(function () {
        imageUrls.push($(this).attr('src'));
      });

      options.imageUrls = imageUrls;

      UploadToImgur(options);
    });
  };

  const uploadHandler = async function () {
    const $this = $(this);
    const itemUrl = $this.data('item-url');
    const options = await CreateOptions($this);

    // This is a safe-guard. The uploadHandler should not be assigned to anything that already has an album
    if (options.existingAlbum != 0 && options.existingAlbum != -1) {
      const $base = options.object;
      $base.off();

      const $image = $base.find('img');
      $image.wrap(`<a href='https://imgur.com/a/${options.existingAlbum}' target='_blank' title='Go to album'></a>`);
      $image.removeAttr('title');

      const $qcMarker = $base.find('.qc-marker:nth-child(2)');
      $qcMarker.css('cursor', 'help');
      $qcMarker.css('color', 'green');
      $qcMarker.text('✓');

      GM_openInTab(`https://imgur.com/a/${options.existingAlbum}`, true);

      return;
    }

    // Go to the QC pictures URL and grab all image src's
    $.get(itemUrl, (data) => {
      const imageUrls = [];
      $('<div/>').html(data).find('div.container.container-top60 > img').each(function () {
        imageUrls.push($(this).attr('src'));
      });

      options.imageUrls = imageUrls;

      UploadToImgur(options);
    });
  };

  try {
    await $.ajax(`${_url}qcdb/qcExists.php`);
  } catch (error) {
    GM_notification('We are unable to connect to FR:ES. Please try again later.', 'FR:ES - BaseTao');
    console.error(`An error happened: ${error.statusText}`, error);

    return;
  }

  $('.myparcels-ul').find('span.glyphicon.glyphicon-picture').each(async function () {
    const $this = $(this);
    const basicOption = await CreateBasicOptions($this);

    // This plugin only works for TaoBao, anything else will be ignored, so only allow a basic upload
    if (basicOption.w2c.indexOf('item.taobao.com') === -1) {
      const $upload = $('<ul><li><span style="cursor: pointer;margin-left:-29px;"><img src="https://s.imgur.com/images/favicon-16x16.png" title="Create a basic album"></span></li></ul>');
      $upload.find('span').first().after($('<span class="qc-marker" style="cursor:help;margin-left:5px;color:red;font-weight: bold;" title="Not a supported URL, but you can still create an album. The QC\'s are not stored and you\'ll have to create a new album if you lose the link.">✖</span>'));
      $upload.data('item-url', $(this).parent().attr('href'));
      $upload.click(basicUploadHandler);

      $this.parents('td').first().append($upload);

      return;
    }

    // Get all information for this item (including existing albums, translations, etc)
    const option = await CreateOptions($(this));

    // Get exisiting data from FR:ES
    const existsData = await HasExistingQc(option.w2c);

    // Define basic upload object
    const $upload = $('<ul><li><span class="qc-marker" style="cursor: pointer;margin-left:-29px;"><img src="https://s.imgur.com/images/favicon-16x16.png" title="Upload your QC"></span></li></ul>');
    $upload.data('item-url', $(this).parent().attr('href'));

    // If we couldn't talk to FR:ES, assume everything is dead and use the basic uploader.
    if (existsData === '-1') {
      $upload.find('span').first().after($('<span class="qc-marker" style="cursor:help;margin-left:5px;color:red;font-weight: bold;" title="FR:ES returned an error, but you can still create an album. The QC\'s are not stored and you\'ll have to create a new album if you lose the link.">⚠️</span>'));
      $upload.data('item-url', $(this).parent().attr('href'));
      $upload.click(basicUploadHandler);

      $this.parents('td').first().append($upload);

      return;
    }

    // Has anyone ever uploaded a QC, if not, show a red marker
    if (existsData === '0') {
      $upload.find('span').first().after($('<span class="qc-marker" style="cursor:help;margin-left:5px;color:red;font-weight: bold;" title="No QC in database, please upload.">(!)</span>'));
      $upload.click(uploadHandler);

      $this.parents('td').first().append($upload);

      return;
    }

    // Have you ever uploaded a QC? If so, link to that album
    const $image = $upload.find('img');
    if (option.existingAlbum !== 0 && option.existingAlbum !== -1) {
      $upload.find('span').first().after($('<span class="qc-marker" style="cursor:help;margin-left:5px;color:green;font-weight: bold;" title="You have uploaded a QC">✓</span>'));
      $image.wrap(`<a href='https://imgur.com/a/${option.existingAlbum}' target='_blank' title='Go to album'></a>`);
      $image.removeAttr('title');

      $this.parents('td').first().append($upload);

      return;
    }

    // A previous QC exists, but you haven't uploaded yours yet, show orange marker
    $upload.find('span').first().after($('<span class="qc-marker" style="cursor:help;margin-left:5px;color:orange;font-weight: bold;" title="Your QC is not yet in the database, please upload.">(!)</span>'));
    $upload.click(uploadHandler);

    $this.parents('td').first().append($upload);
  });
}());