Greasy Fork

Greasy Fork is available in English.

知乎收藏夹批量导出为MarkDown文档

用于批量将知乎收藏夹全部页面导出为独立的MarkDown文档,支持段落、图片、视频、外链、引用和加粗文本转换。

当前为 2024-12-25 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         知乎收藏夹批量导出为MarkDown文档
// @namespace    https://github.com/zfeny
// @version      0.9
// @description  用于批量将知乎收藏夹全部页面导出为独立的MarkDown文档,支持段落、图片、视频、外链、引用和加粗文本转换。
// @author       ZFeny
// @license      MIT
// @match        https://www.zhihu.com/collection/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_download
// ==/UserScript==

(function() {
    'use strict';

    const myCollectionExport = {
        init: async function() {
            const exportButton = document.createElement('button');
            exportButton.textContent = '导出为Markdown';
            exportButton.style.position = 'fixed';
            exportButton.style.top = '10px';
            exportButton.style.right = '10px';
            exportButton.style.zIndex = '1000';
            exportButton.style.padding = '10px';
            exportButton.style.backgroundColor = '#2cbe60';
            exportButton.style.color = 'white';
            exportButton.style.borderRadius = '5px';
            document.body.appendChild(exportButton);

            exportButton.onclick = async () => {
                const pathname = location.pathname;
                const matched = pathname.match(/(?<=\/collection\/)\d+/);
                const collectionId = matched ? matched[0] : "";

                if (!collectionId) return;

                // 获取收藏夹名称
                const collectionTitleElement = document.querySelector('.CollectionDetailPageHeader-title');
                let collectionTitle = collectionTitleElement ? collectionTitleElement.innerText.trim() : '知乎收藏夹';
                collectionTitle = collectionTitle.replace(/_生成PDF__仅对当前页内容进行导出$/, '');

                // 获取收藏夹总页数
                const totalPagesElement = document.querySelectorAll('.Pagination button:not(.PaginationButton--ellipsis)');
                console.log(`Total pages elements:`, totalPagesElement);
                const totalPages = totalPagesElement.length > 0 ? Number(totalPagesElement[totalPagesElement.length - 2].innerText) : 1;
                console.log(`Total pages: ${totalPages}`);

                let collectionsMarkdown = [];

                for (let page = 1; page <= totalPages; page++) {
                    console.log(`Fetching page ${page}`);
                    const offset = 20 * (page - 1);
                    const fetchHeaders = { /* 可根据需要自定义请求头 */ };
                    const response = await fetch(`/api/v4/collections/${collectionId}/items?offset=${offset}&limit=20`, {
                        method: "GET",
                        headers: new Headers(fetchHeaders)
                    });
                    const res = await response.json();
                    console.log(`Page ${page} response:`, res);

                    const pageMarkdown = (res.data || []).map(item => {
                        const { type, url, question, content, title } = item.content;
                        let markdownContent = "";

                        switch (type) {
                            case "zvideo":
                                markdownContent = `### 视频:${title}\n[视频链接](${url})\n`;
                                break;
                            case "answer":
                            case "article":
                            default:
                                markdownContent = `### ${title || (question ? question.title : '')}\n[内容链接](${url})\n\n${this.convertHtmlToMarkdown(content)}\n`;
                                break;
                        }
                        return markdownContent;
                    });

                    collectionsMarkdown = collectionsMarkdown.concat(pageMarkdown);
                }

                // 生成Markdown内容
                const markdownContent = `# ${collectionTitle}\n\n` + collectionsMarkdown.join("\n");
                console.log(`Generated Markdown content:`, markdownContent);

                // 创建下载链接
                const blob = new Blob([markdownContent], { type: 'text/markdown' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `${collectionTitle}.md`;
                a.click(); // 触发下载
                URL.revokeObjectURL(url); // 释放Blob URL
            };
        },
        /**
         * 将HTML内容转换为Markdown内容
         * @param {string} html HTML字符串
         * @returns {string} 转换后的Markdown字符串
         */
        convertHtmlToMarkdown: function(html) {
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = html;

            function parseNode(node) {
                const textContent = node.textContent.trim();
                if (node.nodeType === Node.TEXT_NODE) {
                    return textContent;
                } else if (node.nodeType === Node.ELEMENT_NODE) {
                    const tag = node.tagName.toLowerCase();
                    if (tag === 'p') {
                        return Array.from(node.childNodes).map(parseNode).join('');
                    } else if (tag === 'img') {
                        const src = node.getAttribute('data-original') || node.src;
                        return `![图片描述](${src})`;
                    } else if (tag === 'b' || tag === 'strong') {
                        return `**${textContent}**`;
                    } else if (tag === 'blockquote') {
                        return `> ${Array.from(node.childNodes).map(parseNode).join('').replace(/\n/g, '\n> ')}`;
                    } else if (tag === 'a') {
                        return `[${textContent}](${node.href})`;
                    } else if (tag === 'ul' || tag === 'ol') {
                        const items = Array.from(node.children).map(child => `- ${parseNode(child)}`);
                        return items.join('\n');
                    } else if (tag === 'li') {
                        return textContent;
                    } else if (tag.startsWith('h') && tag.length === 2 && !isNaN(tag[1])) {
                        const level = tag[1];
                        return `${'#'.repeat(level)} ${textContent}`;
                    } else if (tag === 'figure') {
                        const imgNode = node.querySelector('img');
                        if (imgNode) {
                            const src = imgNode.getAttribute('data-original') || imgNode.src;
                            return `![图片描述](${src})`;
                        }
                    } else if (tag === 'br') {
                        return '\n';
                    }
                }
                return '';
            }

            const elements = Array.from(tempDiv.childNodes).map(parseNode);

            return elements.filter(e => e).join('\n\n');
        }
    };

    // 初始化导出功能
    myCollectionExport.init();
})();