Lofter 网页版查看合集

在 Lofter 网页版查看作者的合集内容

// ==UserScript==
// @name         Lofter 网页版查看合集
// @license      GPLv3
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  在 Lofter 网页版查看作者的合集内容
// @author       SrakhiuMeow
// @match        *://*.lofter.com/
// @exclude      *://www.lofter.com/
// @grant        GM.xmlHttpRequest
// @connect      api.lofter.com
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';
    
    const authkey = getCookie("LOFTER-PHONE-LOGIN-AUTH");
    const blogdomain = window.location.hostname;
    const publisher = blogdomain.split('.')[0];
    var doc = document;

    function subscribe(authkey, collectionId, mode = true) {
        // 订阅合集
        // mode = true 订阅, mode = false 取消订阅
        const url = new URL("https://api.lofter.com/v2.0/subscribeCollection.api");
        const params = {
            'method': mode ? 'subscribe' : 'unSubscribe',
            // 'offset': offset,
            // 'limit': limit,
            // 'order': 1,
            // 'collectionid': collectionId,
            'collectionId': collectionId,
            'product': 'lofter-android-7.6.12'
        };

        Object.keys(params).forEach(key =>
            url.searchParams.append(key, params[key])
        );

        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: "GET",
                url: url.toString(),
                headers: {
                    'Accept-Encoding': "br,gzip",
                    'content-type': "application/x-www-form-urlencoded; charset=utf-8",
                    'lofter-phone-login-auth': authkey,
                },
                onload: function (response) {
                    try {
                        // console.log(response);
                        const data = JSON.parse(response.responseText);
                        resolve(data.response);
                    } catch (e) {
                        reject(e);
                    }
                },
                onerror: function (error) {
                    reject(error);
                }
            });
        });
    }

    function getCollection(authkey, blogid, blogdomain, offset = 0, limit = 20) {
        // 获取用户合集列表

        const url = new URL("https://api.lofter.com/v1.1/postCollection.api");
        const params = {
            'method': 'getCollectionList',
            'needViewCount': 1,
            // 'blogid': blogid,
            'blogdomain': blogdomain,
            'product': 'lofter-android-7.6.12'
        };

        Object.keys(params).forEach(key =>
            url.searchParams.append(key, params[key])
        );

        // console.log(authkey);
        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: "GET",
                url: url.toString(),
                headers: {
                    'Accept-Encoding': "br,gzip",
                    'content-type': "application/x-www-form-urlencoded; charset=utf-8",
                    'lofter-phone-login-auth': authkey,
                },
                onload: function (response) {
                    try {
                        // console.log(response);
                        const data = JSON.parse(response.responseText);
                        resolve(data.response);
                    } catch (e) {
                        reject(e);
                    }
                },
                onerror: function (error) {
                    reject(error);
                }
            });
        });
    }

    function getCollectionDetail(authkey, collectionId, offset, limit = 15, order = 1) {
        // 获取某一合集详情

        const url = new URL("https://api.lofter.com/v1.1/postCollection.api");
        const params = {
            'method': 'getCollectionDetail',
            'product': 'lofter-android-7.6.12',
            'offset': offset,
            'limit': limit,
            'collectionid': collectionId,
            'order': order,
        };

        Object.keys(params).forEach(key =>
            url.searchParams.append(key, params[key])
        );

        // console.log(authkey);
        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: "GET",
                url: url.toString(),
                headers: {
                    'Accept-Encoding': "br,gzip",
                    'content-type': "application/x-www-form-urlencoded; charset=utf-8",
                    'lofter-phone-login-auth': authkey,
                },
                onload: function (response) {
                    try {
                        // console.log(response);
                        const data = JSON.parse(response.responseText);
                        resolve(data.response);
                    } catch (e) {
                        reject(e);
                    }
                },
                onerror: function (error) {
                    reject(error);
                }
            });
        });
    }


    function getCookie(name) {
        const cookies = document.cookie.split('; ');
        for (const cookie of cookies) {
            const [key, value] = cookie.split('=');
            if (key === name) {
                return decodeURIComponent(value); // 解码 Cookie 值
            }
        }
        return null; // 如果未找到 Cookie,返回 null
    }

    function formatTimestamp(timestamp) {
        const date = new Date(timestamp);

        const pad = num => num.toString().padStart(2, '0');

        // return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ` +
        //        `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
        return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
    }

    

    // const controlFrame = document.getElementById('control_frame');

    // subscribe(authkey, '')
    //     .then(response => {
    //         console.log("subscribe response:", response);
    //     })
    //     .catch(error => {
    //         console.error("Error subscribing:", error);
    //     });

    function displayCollection(collections) {
        // 显示合集列表


        const postwrapper = doc.querySelector('div.postwrapper');
        // const page = doc.querySelector('div.page');

        collections.forEach(collection => {
            const block = doc.createElement('div');
            block.className = 'block article';

            const side = doc.createElement('div');
            side.className = 'side';
            const main = doc.createElement('div');
            main.className = 'main';
            block.appendChild(side);
            block.appendChild(main);

            const content = doc.createElement('div');
            content.className = 'content';
            const tag = doc.createElement('div');
            tag.className = 'tag';
            const link = doc.createElement('div');
            link.className = 'link';
            main.appendChild(content);
            main.appendChild(tag);
            main.appendChild(link);

            const tags = collection.tags.split(',');
            tags.forEach(tagg => {
                const tagElement = doc.createElement('a');
                tagElement.href = "https://www.lofter.com/tag/" + tagg;
                tagElement.innerHTML = `● ${tagg}`;
                tagElement.target = '_blank';
                tag.appendChild(tagElement);
            });


            const text = doc.createElement('div');
            text.className = 'text';
            const img = doc.createElement('div');
            img.className = 'img';
            content.appendChild(text);
            side.appendChild(img);

            const collectionDetail = `https://www.lofter.com/collection/${publisher}/?op=collectionDetail&collectionId=${collection.id}&sort=0`;
            const collectionUrl = `https://www.lofter.com/front/blog/collection/share?collectionId=${collection.id}`;

            img.innerHTML = `<img src="${collection.coverUrl}?imageView&thumbnail=70x70&quality=90&type=jpg">`;

            text.innerHTML = `
                <h2><a href="${collectionUrl}">${collection.name}</a></h2>
                <p>${collection.description}</p>
            `;


            link.innerHTML = `
                <a>复制ID</a>
                <a>${collection.postCount}篇</a>
                <a>${collection.viewCount}浏览</a>
                <a>${collection.postCollectionHot}热度</a>
                <a>${formatTimestamp(collection.updateTime)}更新</a>
                <a id="subscribe" style="display: none;">订阅合集</a>
                <a>查看详情</a>
            `;

            
            const copyButton = link.querySelector('a:first-child');
            copyButton.addEventListener('click', () => {
                navigator.clipboard.writeText(collection.id).then(() => {
                    // console.log('已将合集ID复制到剪切板:', collection.id);
                    alert('已将合集ID复制到剪切板:' + collection.id);
                }).catch(err => {
                    console.error('复制失败:', err);
                });
            });
            copyButton.style.cursor = 'pointer';


            const subscribeButton = link.querySelector('#subscribe');
            subscribeButton.style.cursor = 'pointer';
            subscribeButton.addEventListener('click', () => {
                if (subscribeButton.innerHTML === '订阅合集') {
                    subscribe(authkey, collection.id, true)
                        .then(response => {
                            // console.log("subscribe response:", response);
                            subscribeButton.innerHTML = '取消订阅';
                        })
                        .catch(error => {
                            console.error("Error subscribing:", error);
                        });
                } else {
                    subscribe(authkey, collection.id, true)
                        .then(response => {
                            // console.log("unsubscribe response:", response);
                            subscribeButton.innerHTML = '订阅合集';
                        })
                        .catch(error => {
                            console.error("Error unsubscribing:", error);
                        });
                }
            });



            const detailButton = link.querySelector('a:last-child');

            const list = doc.createElement('div');
            main.appendChild(list);
            if (collection.postCount === 0) {
                detailButton.innerHTML = '没有内容了';
                detailButton.style.cursor = 'not-allowed';
                detailButton.removeEventListener('click', this);
                detailButton.remove();
                return;
            }
            const br = doc.createElement('br');
            list.appendChild(br);

            detailOffsets[collection.id] = 0;
            detailButton.addEventListener('click', () => {
                getCollectionDetail(authkey, collection.id, detailOffsets[collection.id])
                    .then(response => {
                        // console.log("collection detail response:", response);

                        subscribeButton.style.display = 'block';
                        if (response.subscribed) {
                            subscribeButton.innerHTML = '取消订阅';
                        } else {
                            subscribeButton.innerHTML = '订阅合集';
                        }

                        // 处理合集详情
                        displayCollectionDetail(response.items, list);

                        if (detailOffsets[collection.id] >= collection.postCount) {
                            detailButton.innerHTML = '没有更多内容了';
                            detailButton.style.cursor = 'not-allowed';
                            const clone = detailButton.cloneNode(true);
                            detailButton.parentNode.replaceChild(clone, detailButton);
                            return;
                        } else {
                            detailButton.innerHTML = '加载更多';
                            detailButton.style.cursor = 'pointer';
                        }

                    })
                    .catch(error => console.error(error));
                detailOffsets[collection.id] += 15;

            });
            detailButton.style.cursor = 'pointer';

            // postwrapper.insertBefore(block, page);
            postwrapper.appendChild(block);
            
        });
    }

    const detailOffsets = {};

    function displayCollectionDetail(items, list) {
        // 显示合集详情


        items.forEach(item => {
            const link = doc.createElement('a');
            link.href = item.post.blogPageUrl;
            link.target = '_blank';
            link.innerHTML = item.post.title == '' ? item.post.noticeLinkTitle : item.post.title;

            list.appendChild(link);
            list.innerHTML += '<br>';

        });

    }


    function change2collection() {
        // 将页面修改为显示合集列表模式

        this.innerHTML = '<a>返回</a>';
        this.addEventListener('click', () => {
            window.location.reload();
        });


        // 清除原有内容
        const postwrapper = doc.querySelector('div.postwrapper');
        const postElements = postwrapper.querySelectorAll('div.block');
        postElements.forEach(element => element.remove());
        const page = doc.querySelector('div.page');
        postwrapper.removeChild(page);

        // 获取合集列表
        getCollection(authkey, '', blogdomain)
            .then(response => displayCollection(response.collections))
            .catch(error => console.error(error));
    }

    function change2collection4theme() {
        // 将页面修改为显示合集列表模式
        newFrame();

        // this.innerHTML = '<a>返回</a>';
        // this.addEventListener('click', () => {
        //     window.location.reload();
        // });


        getCollection(authkey, '', blogdomain)
            .then(response => displayCollection(response.collections))
            .catch(error => console.error(error));


        // 清除原有内容
        // const postwrapper = doc.querySelector('div.postwrapper');
        // const postElements = postwrapper.querySelectorAll('div.block');
        // postElements.forEach(element => element.remove());
        // const page = doc.querySelector('div.page');
        // postwrapper.removeChild(page);

        
    }


    function initialize() {
        // 初始化

        // 添加合集按钮
        const sidelist = document.querySelector('ul.sidelist');
        const collectionButton = document.createElement('li');
        collectionButton.innerHTML = '<a>合集</a>';
        collectionButton.addEventListener('click', change2collection);
        collectionButton.style.cursor = 'pointer';
        sidelist.appendChild(collectionButton);


    }

    function intialize2() {
        // 非默认主题下的初始化
    
        if (document.querySelector('ul.m-nav')) {
            const sidelist = document.querySelector('ul.m-nav');
            const collectionButton = document.createElement('li');
            collectionButton.innerHTML = '<a>合集</a>';
            collectionButton.addEventListener('click', change2collection4theme);
            collectionButton.style.cursor = 'pointer';
            sidelist.appendChild(collectionButton);
        } else if (document.querySelector('div.m-nav')){
            const sidelist = document.querySelector('div.m-nav');
            const collectionButton = document.createElement('li');
            collectionButton.innerHTML = '<a>合集</a>';
            collectionButton.addEventListener('click', change2collection4theme);
            collectionButton.style.cursor = 'pointer';
            sidelist.children[0].appendChild(collectionButton);
        }
        
    }

    function newFrame() {
        
        const overlay = document.createElement('div');
        overlay.style.position = 'fixed';
        overlay.style.top = '0';
        overlay.style.left = '0';
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = '#dfdfe1';
        overlay.style.backgroundAttachment = 'fixed';
        overlay.style.backgroundImage = 'url(https://imglf3.lf127.net/img/1553236065974180.png)';
        overlay.style.wordWrap = 'break-word';
        overlay.style.zIndex = '9999';
        overlay.style.overflowY = 'auto'; // 允许覆盖层自身滚动

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '关闭';
        closeBtn.style.position = 'absolute';
        closeBtn.style.top = '10px';
        closeBtn.style.right = '10px';
        closeBtn.onclick = () => document.body.removeChild(overlay);

        overlay.appendChild(closeBtn);

        const style = document.createElement('style');
        style.textContent = `
            .block {
                margin: 0 0 35px;
            }
            .side {
                width: 84px;
                float: left;
            }

            .img {
                margin: 0 0 15px;
            }

            .main {
                margin-left: 110px;
                padding-bottom: 48px;
            }

            .tag {
                margin: 30px 0 0;
                clear: both;
            }

            .link {
                margin: 20px 0 0;
                clear: both;
                overflow: hidden;
                zoom: 1;
            }
        `;
        document.head.appendChild(style);

        const postwrapper = document.createElement('div');
        postwrapper.className = 'postwrapper box wid700'
        Object.assign(postwrapper.style, {
            margin: '0 auto 40px',
            padding: '25px 30px',
            background: '#fff',
            overflow: 'hidden',
            webkitBoxShadow: '0 0 7px 0 rgba(0, 0, 0, 0.2)',
            width: '640px'
        });
        overlay.appendChild(postwrapper);

        document.body.appendChild(overlay);
        


    }


    // 监听 DOM 变化,等待 ul.sidelist 加载完成
    function waitForElement(selector, callback) {
        const observer = new MutationObserver((mutations, obs) => {
            const element = document.querySelector(selector);
            if (element) {
                clearTimeout(timeoutId); // 清除超时定时器
                obs.disconnect(); // 停止观察
                callback(element);
            }
        });

        const timeoutId = setTimeout(() => {
            observer.disconnect(); // 停止观察
            intialize2();
        }, 1000); // 1秒超时

        // 开始观察整个文档的变化
        observer.observe(document, {
            childList: true,
            subtree: true
        });
    }

    // 避免脚本过早执行
    waitForElement('ul.sidelist', (element) => {
        initialize();
    });

})();