Export Apple Purchase History

Export Purchase History from Apple to a csv file

// ==UserScript==
// @name         Export Apple Purchase History
// @namespace    https://zhuzi.dev
// @version      v0.2
// @description  Export Purchase History from Apple to a csv file
// @author       Bambooom
// @homepageURL  https://zhuzi.dev
// @supportURL   https://zhuzi.dev
// @license      MIT
// @match        https://reportaproblem.apple.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=apple.com
// @grant        none
// ==/UserScript==

(function () {
  ('use strict');

  let count = 0;
  let timer = setInterval(() => {
    if (document.querySelector('.purchase.loaded.collapsed') || count > 50) {
      clearInterval(timer);
      init()
    }
    count++
  }, 200);

  let count2 = 0; // no loading-indicator count
  function waitLoading() {
    setTimeout(() => {
      if (document.querySelector('.purchases > .loading-indicator')) {
        console.log('still need loading: ');
        waitLoading();
      } else {
        autoScroll();
      }
    }, 100);
  }

  function autoScroll() {
    window.scrollTo(0, document.body.scrollHeight);

    setTimeout(() => {
      if (document.querySelector('.purchases > .loading-indicator')) {
        count2 = 0;
        console.log('wait for loading');
        waitLoading();
      } else {
        count2++;

        if (count2 < 5) {
          console.log('going to scroll again');
          autoScroll();
        } else {
          alert('All history loaded.');
        }
      }
    }, 100);
  }

  function getRows() {
    const blocks = Array.from(
      document.querySelectorAll('.purchase.loaded.collapsed')
    );
    const rows = [];

    for (const block of blocks) {
      const date = block
        .querySelector('.purchase-header .invoice-date')
        .textContent.trim(); // '22 Jun 2024'
      const webOrderId = block
        .querySelector('.purchase-header .second-element')
        .textContent.trim(); // 'R24Q46T28S16ZB'
      // const price = block
      //   .querySelector('.purchase-header .third-element span span')
      //   .textContent.trim(); //  total: '¥0.00'

      const list = Array.from(
        block.querySelectorAll('.pli-list.applicable-items .pli')
      );
      list.forEach((li) => {
        const orderId = li.querySelector('label').attributes.for.value; // '76084983461860'
        const name = li
          .querySelector('.pli-data-fields .pli-title')
          .textContent.trim();
        const publisher = li.querySelector('.pli-publisher')
          ? li.querySelector('.pli-publisher').textContent.trim()
          : '';
        const priceText = li.querySelector('.pli-price').textContent.trim();
        const price = priceText === 'Free' ? '0' : priceText;
        const icon = li.querySelector('.pli-artwork img').src;
        rows.push({
          date,
          webOrderId,
          orderId,
          name,
          publisher,
          price,
          icon,
          price,
        });
      });
    }

    return rows;
  }

  function exportData() {
    const rows = getRows();
    JsonToCSV.exportToCSV(
      rows,
      [
        { title: 'Name', key: 'name' },
        { title: 'Date', key: 'date' },
        { title: 'Web Order Id', key: 'webOrderId' },
        { title: 'Order Id', key: 'orderId' },
        { title: 'Publisher', key: 'publisher' },
        { title: 'Price', key: 'price' },
        { title: 'Icon', key: 'icon' },
      ],
      'purchase-history-' +
        new Date().toISOString().split('T')[0].replaceAll('-', '')
    );
  }

  function init() {
    const div1 = document.createElement('div');
    const btn1 = document.createElement('button');
    const btn2 = document.createElement('button');
    btn1.textContent = 'Load all';
    btn2.textContent = 'Export to csv';
    btn1.classList.add('button', 'button-block', 'load-all-btn');
    btn2.classList.add('button', 'button-block', 'load-all-btn');
    btn2.style.marginLeft = '10px';
    div1.appendChild(btn1);
    div1.appendChild(btn2);
    document.querySelector('.search-bar').after(div1);
    const div2 = div1.cloneNode(true);
    document.querySelector('.purchases').after(div2);

    btn1.onclick = autoScroll;
    btn2.onclick = exportData;
  }

  // https://github.com/liqingzheng/export-to-CSV/blob/master/export-to-CSV/export-to-CSV.js
  var JsonToCSV = {
    /*
     * exportToCSV 导出CSV
     * @ param  {Array} data 导出数据 必填项
     * @ param  {Array} columns 导出表头 必填项
     * @ param  {String} fileName 导出文件名称
     */
    exportToCSV(data = [], columns = [], fileName = 'userExportToCSV') {
      if (!data.length && !columns.length) {
        console.error(
          '\u5bfc\u51fa\u6570\u636e\u548c\u8868\u5934\u4e0d\u4e3a\u7a7a'
        );
        return this;
      }
      const bw = this.browser();
      if (bw.ie < 9) return this;
      let CSV = '',
        arr = [],
        colKey,
        curvalue;
      columns.forEach(function (item) {
        arr.push(item.title || item.key);
      });
      CSV = CSV + arr.join(',') + '\r\n';
      data.forEach(function (item) {
        arr = [];
        curvalue = '';
        columns.forEach(function (col) {
          colKey = col.key;
          if (
            typeof item[colKey] == 'string' ||
            typeof item[colKey] === 'number'
          ) {
            curvalue =
              typeof col.formatter === 'function'
                ? col.formatter(item, col, colKey)
                : item[colKey];
            curvalue = typeof curvalue === 'function' ? '' : curvalue + '';
            curvalue = curvalue.replace(/\,/gi, '');
          }
          arr.push(curvalue);
        });
        CSV = CSV + arr.join(',') + '\r\n';
      });
      this.SaveAs(fileName, CSV);
    },
    SaveAs(fileName = '', csvData = '') {
      const bw = this.browser();
      if (!bw.edge || !bw.ie) {
        let alink = document.createElement('a');
        alink.id = 'linkDwnldLink';
        alink.href = this.getDownloadUrl(csvData);
        document.body.appendChild(alink);
        let linkDom = document.getElementById('linkDwnldLink');
        linkDom.setAttribute('download', fileName);
        linkDom.click();
        document.body.removeChild(linkDom);
      } else if (bw.ie >= 10 || bw.edge == 'edge') {
        let _utf = '\uFEFF';
        let _csvData = new Blob([_utf + csvData], {
          type: 'text/csv',
        });
        navigator.msSaveBlob(_csvData, fileName);
      } else {
        let oWin = window.top.open('about:blank', '_blank');
        oWin.document.write('sep=,\r\n' + csvData);
        oWin.document.close();
        oWin.document.execCommand('SaveAs', true, fileName);
        oWin.close();
      }
    },
    getDownloadUrl(csvStr = '') {
      let _utf = '\uFEFF'; // 为了使Excel以utf-8的编码模式,同时也是解决中文乱码的问题
      if (window.Blob && window.URL && window.URL.createObjectURL) {
        let csvData = new Blob([_utf + csvStr], {
          type: 'text/csv',
        });
        return URL.createObjectURL(csvData);
      }
    },
    browser() {
      const Sys = {};
      const ua = navigator.userAgent.toLowerCase();
      let s;
      (s =
        ua.indexOf('edge') !== -1
          ? (Sys.edge = 'edge')
          : ua.match(/rv:([\d.]+)\) like gecko/))
        ? (Sys.ie = s[1])
        : (s = ua.match(/msie ([\d.]+)/))
        ? (Sys.ie = s[1])
        : (s = ua.match(/firefox\/([\d.]+)/))
        ? (Sys.firefox = s[1])
        : (s = ua.match(/chrome\/([\d.]+)/))
        ? (Sys.chrome = s[1])
        : (s = ua.match(/opera.([\d.]+)/))
        ? (Sys.opera = s[1])
        : (s = ua.match(/version\/([\d.]+).*safari/))
        ? (Sys.safari = s[1])
        : 0;
      return Sys;
    },
  };
})();