豆瓣自动推荐书籍标签

提取网页内的推荐标签,放置到书籍详情和标签区域中,支持点击填充到标签输入框

// ==UserScript==
// @name 豆瓣自动推荐书籍标签
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 提取网页内的推荐标签,放置到书籍详情和标签区域中,支持点击填充到标签输入框
// @author blue-bird
// @match https://book.douban.com/subject/*/
// @icon https://www.google.com/s2/favicons?sz=64&domain=douban.com
// @grant none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 等待 DOM 完全加载
    window.addEventListener ('load', function () {
        // 从 DoubanAdRequest 对象提取标签字符串
        const criteria = (window.DoubanAdRequest && window.DoubanAdRequest.crtr) || '';
        if (!criteria) {
            console.warn (' 未在 DoubanAdRequest 中找到标签信息!');
            return;
        }

        // 将标签字符串拆分为单独的条目
        const entries = criteria.split ('|');

        // 提取关键词(它们在冒号:后面)
        const keywords = entries.filter (entry => entry.startsWith ('7:')).map (entry => {
            const parts = entry.split (':');
            return parts.length > 1 ? parts [1] : null;
        }).filter (Boolean); // 移除 null/undefined 条目

        if (keywords.length === 0) {
            console.warn (' 未提取到任何关键词标签!');
            return;
        }

        // 创建切换按钮
        const toggleButton = document.createElement ('button');
        toggleButton.textContent = ' 显示 / 隐藏 标签 ';
        toggleButton.style.marginBottom = '10px';
        toggleButton.style.padding = '5px 10px';
        toggleButton.style.cursor = 'pointer';

        // 创建关键词容器
        const keywordContainer = document.createElement ('div');
        keywordContainer.style.display = 'none'; // 初始隐藏

        // 为关键词创建链接
        keywords.forEach ((keyword) => {
            const link = document.createElement ('a');
            link.href = `https://book.douban.com/tag/${encodeURIComponent(keyword)}`;
            link.textContent = keyword;
            link.target = '_blank';
            link.style.marginRight = '10px';
            link.style.textDecoration = 'none';
            link.style.color = '#37a';

            // 添加点击事件,填充到输入框
            link.addEventListener('click', function(e) {
                addTagToInput(keyword);
            });

            keywordContainer.appendChild(link);
        });

        // 添加切换功能
        toggleButton.addEventListener ('click', () => {
            if (keywordContainer.style.display === 'none') {
                keywordContainer.style.display = 'block';
                toggleButton.textContent = ' 隐藏 关键词 ';
            } else {
                keywordContainer.style.display = 'none';
                toggleButton.textContent = ' 显示 关键词 ';
            }
        });

        // 找到 id 为 "info" 的元素并添加内容
        const infoElement = document.getElementById ('info');
        if (infoElement) {
            infoElement.appendChild (toggleButton);
            infoElement.appendChild (keywordContainer);
        } else {
            console.warn (' 未找到 id 为 "info" 的元素!');
        }

        // 添加一个标志防止重复执行
        let tagsAdded = false;

        // 新功能:在 populartags 元素中添加标签
        function addTagsToPopularTags () {
            // 如果已经添加过标签,直接返回
            if (tagsAdded) return;

            const popularTags = document.getElementById ('populartags');
            if (popularTags) {
                // 创建自定义标签的 dl 结构
                const dl = document.createElement ('dl');

                // 创建 dt 元素
                const dt = document.createElement ('dt');
                dt.textContent = ' 流行标签:';
                dl.appendChild (dt);

                // 创建 dd 元素
                const dd = document.createElement ('dd');

                // 为每个关键词创建 span 元素
                keywords.forEach ((keyword, index) => {
                    const span = document.createElement ('span');
                    // 交替使用不同的类,使标签样式有所变化
                    span.className = `tagbtn gract`;
                    span.textContent = keyword;
                    // 添加点击事件,点击时将标签添加到输入框
                    span.addEventListener('click', function() {
                        addTagToInput(keyword);
                    });
                    // 添加悬停效果,提示可点击
                    span.style.cursor = 'pointer';

                    // 添加到 dd 元素
                    dd.appendChild (span);

                    // 添加间隔(最后一个标签不需要)
                    if (index < keywords.length - 1) {
                        // 使用 CSS margin 代替文本空格,避免显示问题
                        span.style.marginRight = '8px';
                    }
                });

                dl.appendChild(dd);

                // 将创建的 dl 添加到 populartags 元素
                popularTags.appendChild (dl);

                // 标记为已添加
                tagsAdded = true;
            } else {
                // 如果未找到 populartags 元素,尝试使用 MutationObserver 监听
                const observer = new MutationObserver ((mutations) => {
                    mutations.forEach ((mutation) => {
                        if (document.getElementById ('populartags')) {
                            addTagsToPopularTags ();
                            observer.disconnect (); // 完成后断开观察
                        }
                    });
                });

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

        // 新增功能:将标签添加到输入框
        function addTagToInput(tag) {
            // 查找标签输入框
            const tagInput = document.querySelector('input[name="tags"]');
            if (!tagInput) {
                console.warn('未找到标签输入框');
                return;
            }

            // 获取当前输入框的值
            let currentValue = tagInput.value.trim();

            // 检查标签是否已存在,避免重复添加
            const existingTags = currentValue.split(/\s+/);
            if (existingTags.includes(tag)) {
                return; // 如果标签已存在,则不添加
            }

            // 构建新的标签值,使用空格分隔
            let newValue = currentValue
            ? `${currentValue} ${tag}`  // 如果已有内容,添加空格和新标签
                : tag;                     // 如果为空,直接设置为标签

            // 更新输入框的值
            tagInput.value = newValue;

            // 触发输入事件,确保页面其他逻辑能感知到变化
            const event = new Event('input', { bubbles: true });
            tagInput.dispatchEvent(event);
        }

        // 执行新功能
        addTagsToPopularTags ();
    });
})();