番组计划月度统计

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

目前為 2024-01-04 提交的版本,檢視 最新版本

// ==UserScript==
// @name         番组计划月度统计
// @namespace    http://tampermonkey.net/
// @version      2024-01-03
// @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 sourceElement = document.querySelector('#columnA')
    const tempDiv = document.createElement('div')
    sourceElement.appendChild(tempDiv)

    const buttonDiv = document.createElement('div')
    sourceElement.appendChild(buttonDiv)
    buttonDiv.innerHTML = `<div class="request-more-data"><div class='request-more-data-button' id='request-more-data-button'>加载更多</div></div>`
    const requestButton = document.querySelector('#request-more-data-button')

    if (!sourceElement) {
    return;
    }
    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', () => {
        requestButton.innerHTML = '加载中...'
        if (hasClick) {return}
        if (hasFinish) {
            requestButton.innerHTML = '无更多数据'
            return
        }
        hasClick = true
        requestData()

    })


    requestData()





    function requestData (){
        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)
                offset = totalDataArray.length
                hasClick = false
                requestButton.innerHTML = '加载更多'
                if (oldhasAllMap === JSON.stringify(hasAllMap)) {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 = ''
            for(let tempObjKey in tempObj) {
                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 => {
                    subjectRenderStr += `
                    <span class='my-total-month-list-item-span'>
                    <img class='my-total-month-list-item-img' src=${collection.subject.images.small}></img>
                    <span class='my-total-month-list-item-date'>${`${new Date(collection.updated_at).getMonth() + 1}.${new Date(collection.updated_at).getDate()}`}</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 (hasFinish){hasAllMap[keyYear][keyMonth] = true}
            lastCollectionsData.data = item
            lastCollectionsData.keyYear = keyYear
            lastCollectionsData.keyMonth = keyMonth

        })

        let connectStr = ''

        for(let yearKey in collectionsData) {
            let itemStr = `
            <div class='my-total-item'>
              <div class='my-total-item-year-title my-total-timeline'>${yearKey}</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">
        ${connectStr}
        </div>
        `
        tempDiv.innerHTML = htmlStr
    }

    const style = `
    .my-month-data {
    margin-top: 40px;
    }

    .my-total-item-year-title {
    font-weight: 600;
    position: relative;
    font-size: 22px;
    padding-bottom: 15px;
    }
    .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;
    }

    .my-total-month-list-item-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    }
    .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-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;
    }
    `

    GM_addStyle(style)


})();