Greasy Fork

来自缓存

Greasy Fork is available in English.

番组计划月度统计

个人主页时光机下面增加一个月度统计的小功能

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         番组计划月度统计
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  个人主页时光机下面增加一个月度统计的小功能
// @author       You
// @match        https://bangumi.tv/user/*
// @match        https://bgm.tv/user/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bangumi.tv
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";
  const COLLECTION_TYPE = {
    1: "想看",
    2: "看过",
    3: "在看",
    4: "搁置",
    5: "抛弃",
  };
  const SUBJECT_TYPE = {
      1: 'N',
      2: 'A',
      3: 'M',
      4: 'G',
      6: '三'
  }

  const reg = /\/user\/[^/]*$/;
  if (!reg.test(location.pathname)) return;

  const sourceElement = document.querySelector("#columnA");
  if (!sourceElement) {
    return;
  }
  const tempDiv = document.createElement("div");
  tempDiv.setAttribute('class', 'my-month-data');
  sourceElement.appendChild(tempDiv);

  const buttonDiv = document.createElement("div");
  sourceElement.appendChild(buttonDiv);
  buttonDiv.innerHTML = `<div class="request-more-data"><div class='need-scroll-to-top'></div><div class='request-more-data-button' id='request-more-data-button'>加载中...</div><div id='need-scroll-to-top'>🔝</div></div>`;
  const requestButton = document.querySelector("#request-more-data-button");
  const toTopButton = document.querySelector("#need-scroll-to-top");

  toTopButton.addEventListener('click', () => {
      window.scrollTo({
          left: 0,
          top: 0,
          behavior: 'smooth'
      })
  })

  let totalDataArray = [];
  const limit = 100;
  let offset = 0;
  const hasAllMap = {};
  let hasFinish = false;
  let hasClick = false;

  const d = location.pathname.split("/");
  const requestUrl = `https://api.bgm.tv/v0/users/${
    d[d.length - 1]
  }/collections`;

  requestButton.addEventListener("click", () => {
    if (hasClick || hasFinish) {
      return;
    }
    requestData();
  });
  tempDiv.addEventListener('click', (e) => {
    const subjectId = e.target.dataset.subjectId
    if (subjectId) {
      window.location.href = `${location.origin}/subject/${subjectId}`
    }
  })

  requestData();

  function requestData() {
    requestButton.innerHTML = "加载中...";
    hasClick = true;
    const oldhasAllMap = JSON.stringify(hasAllMap);
    fetch(`${requestUrl}?limit=${limit}&offset=${offset}`, {
      mode: "cors",
    }).then((res) => {
      res.json().then((jes) => {
        totalDataArray.push(...jes.data);
        hasFinish = totalDataArray.length === jes.total;
        init(totalDataArray);
        if (hasFinish) {
            requestButton.innerHTML = "无更多数据";
        } else {
            requestButton.innerHTML = "加载更多";
        }
        offset = totalDataArray.length;
        hasClick = false;
        if ((oldhasAllMap === JSON.stringify(hasAllMap) || Object.values(hasAllMap).every(k => JSON.stringify(k) === '{}')) && !hasFinish) {
          requestData();
        }
      });
    });
  }

  function getMonthItemRender(monthDataMap, year) {
    let strListRenderStr = "";
    for (let monthKey in monthDataMap) {
      const tempObj = {};
      monthDataMap[monthKey].forEach((dateData) => {
        if (!tempObj[dateData.type]) {
          tempObj[dateData.type] = [];
        }
        tempObj[dateData.type].push(dateData);
      });
      let tempRenderStr = "";
      const customKeySort = [2,5,1,3,4].filter(ct => tempObj.hasOwnProperty(ct))
      for (let tempObjKey of customKeySort) {
        tempRenderStr += `
            <div class='my-total-month-connect my-total-timeline'>${(() => {
              let subjectRenderStr = `<div class='my-total-month-type my-total-month-type${tempObjKey}'>${COLLECTION_TYPE[tempObjKey]} <span class='my-total-month-type-number'>${tempObj[tempObjKey].length}</span></div>`;
              tempObj[tempObjKey].forEach((collection) => {
                collection.subject.name_cn = collection?.subject.name_cn.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
                collection.subject.name = collection?.subject.name.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
                collection.comment = collection.comment?.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
                subjectRenderStr += `
                    <span class='my-total-month-list-item-span'>
                    <img class='my-total-month-list-item-img' data-subject-id='${collection.subject.id}' title="《${collection.subject.name_cn || collection.subject.name}》${collection.comment ? `\n&quot;${collection.comment}&quot;` : ''}" src=${
                      collection.subject.images.small || '/img/no_img.gif'
                    }></img>
                    <span class='my-total-month-list-item-date'>${`${
                      new Date(collection.updated_at).getMonth() + 1
                    }.${new Date(collection.updated_at).getDate()}`}</span>
                    <span class='my-total-month-list-item-subject-type'>${SUBJECT_TYPE[collection.subject.type]}</span>
                    </span>
                    `;
              });
              return subjectRenderStr;
            })()}</div>
            `;
      }

      const monthRenderStr = `
            <div class='my-total-month'>
              <div class='my-total-month-title my-total-timeline'>${monthKey}月</div>
              ${tempRenderStr}
            </div>`;
      if (hasAllMap[year][monthKey]) {
        strListRenderStr = monthRenderStr + strListRenderStr;
      }
    }
    return strListRenderStr;
  }

  function init(data) {
    const collectionsData = {};
    let lastCollectionsData = {};
    data.forEach((item, index) => {
      const keyYear = new Date(item.updated_at).getFullYear();
      const keyMonth = new Date(item.updated_at).getMonth() + 1;
      if (!collectionsData[keyYear]) {
        collectionsData[keyYear] = {};
        hasAllMap[keyYear] = {};
      }
      if (!collectionsData[keyYear][keyMonth]) {
        collectionsData[keyYear][keyMonth] = [];
      }
      collectionsData[keyYear][keyMonth].push(item);

      if (
        index > 0 &&
        new Date(item.updated_at).getMonth() !== new Date(lastCollectionsData.data.updated_at).getMonth()
      ) {
        hasAllMap[lastCollectionsData.keyYear][lastCollectionsData.keyMonth] = true;
      }
      if (index > 0 && new Date(item.updated_at).getFullYear() !== new Date(lastCollectionsData.data.updated_at).getFullYear()) {
          hasAllMap[lastCollectionsData.keyYear].all = true;
      }
      if (hasFinish) {
        hasAllMap[keyYear][keyMonth] = true;
        hasAllMap[keyYear].all = true;
      }
      lastCollectionsData.data = item;
      lastCollectionsData.keyYear = keyYear;
      lastCollectionsData.keyMonth = keyMonth;
    });

    let connectStr = "";

    for (let yearKey in collectionsData) {
        const tmpObjYear = {
            1: 0,
            2: 0,
            3: 0,
            4: 0,
            5: 0
        }
        for (let monthKey in collectionsData[yearKey]) {
            collectionsData[yearKey][monthKey].forEach(k => {
            tmpObjYear[k.type] = tmpObjYear[k.type] + 1
            })

        }
        const customKeySort = [2,5,1,3,4].filter(ct => tmpObjYear.hasOwnProperty(ct))

      let itemStr = `
            <div class='my-total-item'>
              <div class='my-total-item-year-title my-total-timeline'>
              ${yearKey}
              ${hasAllMap[yearKey].all ? `<div class='my-total-item-year-count'>${customKeySort.map(type => {
              return `<div class='my-total-month-type-yaer-item my-total-month-type${type}'>${COLLECTION_TYPE[type]} <span class='my-total-month-type-number'>${tmpObjYear[type]}</span></div>`
              }).join('')}</div>` : ''}
              </div>
              <div class='my-total-item-year-connect-list'>${getMonthItemRender(
                collectionsData[yearKey],
                yearKey
              )}</div>
              <div></div>
            </div>
            `;
      connectStr = itemStr + connectStr;
    }
    const htmlStr = `
        <div class='my-month-data-title'>月度统计</div>
        ${connectStr}
        `;
    tempDiv.innerHTML = htmlStr;
  }

  const style = `
    .my-month-data {
    margin-top: 40px;
    }
    .my-month-data-title {
    padding-left: 5px;
    margin-bottom: 10px;
    font-size: 14px;
    border-bottom: 1px solid #CCC;
    color: #09C;
    }

    .my-total-item-year-title {
    font-weight: 600;
    position: relative;
    font-size: 24px;
    padding-bottom: 15px;
    display: flex;
    align-items: stretch;
    }
    .my-total-item-year-title::before {
    content: '';
    display: inline-block;
    position: absolute;
    left: -3.4px;
    top: calc(50% - 7.5px);
    transform: translateY(-50%) rotate(45deg);
    width: 6px;
    height: 6px;
    background-color: #000;
    }


    .my-total-month-title {
    font-weight: 500;
    position: relative;
    font-size: 18px;
    padding-bottom: 10px;
    }
    .my-total-month-title::before {
    content: '';
    display: inline-block;
    position: absolute;
    left: -6.5px;
    top: calc(50% - 5px);
    transform: translateY(-50%);
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1px solid #ccc;
    background-color: #fff;
    }

    .my-total-month-type {
    position: relative;
    font-size: 14px;
    padding-bottom: 5px;
    color: #888;
    }
    .my-total-month-type::before {
    content: '';
    display: inline-block;
    position: absolute;
    left: -24.5px;
    top: calc(50% - 2.5px);
    transform: translateY(-50%);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    border: 1px solid #F09199;
    background-color: #F09199;
    }
    .my-total-month-type2::before {
    border: 1px solid #91B876;
    background-color: #91B876;
    }
    .my-total-month-type3::before {
    border: 1px solid #6BAAE8;
    background-color: #6BAAE8;
    }
    .my-total-month-type4::before {
    border: 1px solid #E68E46;
    background-color: #E68E46;
    }
    .my-total-month-type5::before {
    border: 1px solid #9065ED;
    background-color: #9065ED;
    }

    .my-total-month-type-number {
    color: #369CF8;
    }


    .my-total-month-list-item-span {
    overflow: hidden;
    display: inline-block;
    margin-right: 4px;
    border-radius: 4px;
    width: 60px;
    height: 85px;
    position: relative;
    transition: all 0.2s ease-out;
    }
    .my-total-month-list-item-span:hover {
    box-shadow:0px 0px 10px #666;
    transform: scale(1.2);
    z-index: 2;
    }

    .my-total-month-list-item-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    cursor: pointer;
    }
    .my-total-month-list-item-date {
    position: absolute;
    right: 0;
    bottom: 0;
    font-size: 10px;
    color: #fff;
    line-height: 10px;
    background-color: #F09199;
    border-radius: 4px;
    padding: 2px;
    }
    .my-total-month-list-item-subject-type {
    position: absolute;
    right: 0;
    top: 0;
    font-size: 10px;
    color: #F09199;
    line-height: 10px;
    background-color: rgb(255,255,255,0.85);
    border-radius: 4px;
    padding: 2px;
    }

    .my-total-month-connect {
    padding-bottom: 25px;
    }

    .request-more-data {
    display: flex;
    align-item: center;
    justify-content: center;
    }
    .request-more-data-button {
    cursor: pointer;
    }

    .my-total-timeline {
    border-left: 1px solid #ccc;
    padding-left: 20px;
    }


    .my-total-item-year-count {
    margin-left: 20px;
    display: flex;
    font-size: 14px;
    align-items: flex-end;
    line-height: 14px;
    font-weight: 400;
    color: #888;
    opacity: 0;
    transition: all 0.2s ease-out;
    cursor: default;
    }
    .my-total-item-year-count:hover {
    opacity: 1;
    }
    .my-total-month-type-yaer-item {
    margin-right: 15px;
    }
    .need-scroll-to-top {
    width: 50px;
    margin: 0 10px;
    opacity: 0;
    }
    #need-scroll-to-top {
    cursor: pointer;
    width: 50px;
    margin: 0 10px;
    opacity: 0;
    }
    #need-scroll-to-top:hover {
    opacity: 1;
    }

    `;

  GM_addStyle(style);
})();