个人主页时光机下面增加一个月度统计的小功能
// ==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, '"').replace(/'/g, '''); collection.subject.name = collection?.subject.name.replace(/"/g, '"').replace(/'/g, '''); collection.comment = collection.comment?.replace(/"/g, '"').replace(/'/g, '''); 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"${collection.comment}"` : ''}" 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); })();