Greasy Fork

来自缓存

在搜索引擎中搜索思源笔记(SiYuan)

在搜索引擎侧栏,必应(Bing)和谷歌(Google),展示相同的关键词在思源笔记中的结果。

// ==UserScript==
// @name         在搜索引擎中搜索思源笔记(SiYuan)
// @namespace    https://fradeet.top/
// @supportURL   https://github.com/Fradeet/SearchEngine-with-SiYuan-serach
// @version      2025-02-16
// @description  在搜索引擎侧栏,必应(Bing)和谷歌(Google),展示相同的关键词在思源笔记中的结果。
// @description:en Display results for the same keywords in the SiYuan Note in the search engine sidebar.
// @author       Fradeet
// @license      MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFJSURBVDhPxZJPTsJAFMbntbXRA+iKhUESPQGmJaQB45pdwxE4gXHLMWTnlkQCBxDSQAobL6DEpNFNw86NyYh9vvnT2gqJxg2/zXvv63zfTKdlO8fUNeOxWi3x45Pzh5foWUuSu1rtMi6XeRRFb1qSGLpKhBlNcxKDNXDrFy0tsyfXbR0lOFgn1sTzvJKWJaBrZiahcmXt8xUAQwT/9vOdKvZjWnq9d2AzZEvLWDeCIHgVPnmCvFnMCrABsC/MNNhKI4BV8ieRAWgY3aI5BYTx25xCIR+J2RWtCgDYuMxfAfUBCpf4H7YG0OugbvNs0zYDEsTeCqFNLVeKhB8ybNOF9vScUQgQ5tPFojObjYeIzKdNRQhPAPyz+XwYTsednyFZQGpOjx9O70fiP5DmMBzJRfRMhjB2o2fF0nGaJJL3z4BTbzR1v1MY+wIHyIJUp2H/PgAAAABJRU5ErkJggg==
// @match        https://*.bing.com/search*
// @include      https://www.google.*/search?*
// @connect      127.0.0.1
// @connect      *
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    // console.log("GM_info", GM_info);
    // console.log("Window", window.location.href);
    // GM_getTab((tab) => console.log("Get Tab", tab));

    const currentURL = window.location.href;
    const currentDomain = currentURL.split('?')[0];

    // 获取本地存储配置
    // console.log("GM_getValue", GM_getValue("config"));
    let config = JSON.parse(GM_getValue("config", `{
        "Location": "local",
        "SiYuan": {
            "Endpoint": "http://127.0.0.1:6806/",
            "Token": ""
        }
    }`));

    // 插入样式
    const style = document.createElement('style');
    style.innerHTML = `
        #siyuan-search {
            margin-top: 1em;
        }
        #siyuan-search-list {
            list-style: none;
            padding: 0;
            margin: 0;
        }
        #siyuan-search-list li {
            margin-bottom: 0.5em;
        }
        
        #siyuan-search li:hover {
            background-color: #f5f5f5;
        }
            
        #siyuan-search li a {
            text-decoration: none;
            color: #0078d7;
        }
            
        #siyuan-search li a:hover {
            text-decoration: underline;
        }
         
        #siyuan-search ul {
            list-style-type: none;
        }

        #donate-button {
            margin-left: 1em;
        }
            
        #setting-button {
            margin-left: 1em;
        }

        .siyuan-search-info {
            display: flex;
            color: #71777d
        }

        .siyuan-updated {
            margin-left: auto;
        }

        .siyuan-search-item {
            background-color: white;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            margin-bottom: 10px;
            padding: 10px;
            position: relative;
        }

        .siyuan-search-title {
            display: -webkit-box;
            -webkit-box-orient: vertical; 
            -webkit-line-clamp: 2; /* 设置你想要的行数 */
            overflow: hidden;
            margin-bottom: 0.5em;
        }
            
        .siyuan-hpath {
            background-color: rgba(55, 53, 47, 0.06);
            border-radius: 1em;
            max-width: 70%;
            white-space: nowrap; /* 防止文本换行 */
            overflow: hidden; /* 隐藏溢出的文本 */
            text-overflow: ellipsis; /* 使用省略号表示被隐藏的文本 */
            direction: rtl; /* 文本从右到左排列 */
            text-align: left; /* 将文本对齐方式设置为左对齐 */
            font-size: 0.8em;
        }

        #siyuan-search-header img {
            width: 2em;
            height: 2em;
            align-items: center; /* 垂直居中 */

        }         
        
        #siyuan-search-header div {
            align-items: center; /* 垂直居中 */
        }

        #siyuan-related-list li {
            margin-bottom: 0.5em;
        }

        .siyuan-search-sidepanel {
            grid-column: span 7/-2;
        }
        `;
    document.head.appendChild(style);

    const bodyHTML = `
            <div id="siyuan-search-header" style="display: flex;">
                <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFJSURBVDhPxZJPTsJAFMbntbXRA+iKhUESPQGmJaQB45pdwxE4gXHLMWTnlkQCBxDSQAobL6DEpNFNw86NyYh9vvnT2gqJxg2/zXvv63zfTKdlO8fUNeOxWi3x45Pzh5foWUuSu1rtMi6XeRRFb1qSGLpKhBlNcxKDNXDrFy0tsyfXbR0lOFgn1sTzvJKWJaBrZiahcmXt8xUAQwT/9vOdKvZjWnq9d2AzZEvLWDeCIHgVPnmCvFnMCrABsC/MNNhKI4BV8ieRAWgY3aI5BYTx25xCIR+J2RWtCgDYuMxfAfUBCpf4H7YG0OugbvNs0zYDEsTeCqFNLVeKhB8ybNOF9vScUQgQ5tPFojObjYeIzKdNRQhPAPyz+XwYTsednyFZQGpOjx9O70fiP5DmMBzJRfRMhjB2o2fF0nGaJJL3z4BTbzR1v1MY+wIHyIJUp2H/PgAAAABJRU5ErkJggg==" alt="SiYuan"></img>
                <div style="display: flex;">
                    <div>SiYuan 搜索</div>
                </div>
                <div style="margin-left: auto; display: flex;">
                    <div id="donate-button">
                        <a href="https://afdian.com/a/fradeet" target="_blank">
                            ❤️
                        </a>
                    </div>
                    <button id="setting-button">
                        设置
                    </button>
                </div>
            </div>
            <div style="display: none;" id="siyuan-setting">
                <label>思源笔记位置:</label>
                <select id="location" name="location">
                    <option value="local">本地</option>
                    <option value="remote">远程</option>
                </select>
                <br>
                <label for="url">地址:</label>
                <input type="text" id="url" name="url" required>
                <br>
                <label for="token">Token:</label>
                <input type="password" id="token" name="token" required>
                <br>
                <div id="setting-log" style="margin-top: 1em;"></div>
                <button id="save-button">保存</button>
            </div>
            <div id="siyuan-search">
                <ul id="siyuan-search-list">
                </ul>
                <div id="middle-line">
                    <hr />
                    <div style="margin-bottom: 1em;">可能相关的笔记:</div>
                </div>
                <ul id="siyuan-related-list">
                </ul>
            </div>
            <div id="bottom-panel">
            </div>
            `;

    // 从 URL 获取搜索词
    const searchInput = document.querySelector('[name="q"]').value;

    // 判断是 bing 还是 google
    if (currentDomain.includes('bing')) {
        // 创建一个 li
        let body = document.createElement('li');
        body.style.cssText = 'margin-bottom: 2em;';

        body.innerHTML = bodyHTML;
        
        const sidePanel = document.getElementById("b_context");
        sidePanel.insertBefore(body, sidePanel.firstChild);

    } else if (currentDomain.includes('google')) {
        
        // 检测是否创建了侧栏
        if (document.getElementById("rhs") === null) {
            const sidePanel = document.createElement("div");
            sidePanel.id = "rhs";

            sidePanel.classList.add("siyuan-search-sidepanel");

            document.getElementById("center_col").parentElement.appendChild(sidePanel);
        }
        let body = document.createElement('div');
        body.style.cssText = 'margin-bottom: 2em;';

        body.innerHTML = bodyHTML;
        
        const sidePanel = document.getElementById("rhs");
        sidePanel.insertBefore(body, sidePanel.firstChild);
    }

    

    const title_ul = document.querySelector("#siyuan-search-list")
    const related_ul = document.querySelector("#siyuan-related-list")

    let setting_button = document.getElementById("setting-button");
    let setting = document.querySelector("#siyuan-setting");
    setting_button.onclick = () => {
        setting.style.display = 'inline';
    }

    // 将现有配置填入
    if (config.SiYuan !== undefined) {
        if (config.Location === "local") {
            document.getElementById("location").value = "local";
            document.getElementById("url").disabled = true;
        } else {
            document.getElementById("location").value = "remote";
        }
        document.getElementById("url").value = config.SiYuan.Endpoint;
        document.getElementById("token").value = config.SiYuan.Token;
    }

    // TODO 选择远程的时候弹出提示,提示可能需要 Tampermonkey 允许跨域


    // 监听设置,如果是本地则服务器输入框禁用
    document.getElementById("location").onchange = () => {
        if (document.getElementById("location").value === "local") {
            document.getElementById("url").disabled = true;
        } else {
            document.getElementById("url").disabled = false;
        }
    }

    document.getElementById("save-button").onclick = () => {
        let location = document.getElementById("location").value;
        if (location === "local") {
            config.Location = "local";
            config.SiYuan.Endpoint = "http://127.0.0.1:6806";
        } else {
            config.Location = "remote";
            config.SiYuan.Endpoint = document.getElementById("url").value;
        }
        config.SiYuan.Token = document.getElementById("token").value;
        GM_setValue("config", JSON.stringify(config));
        document.getElementById("setting-log").innerHTML = `
            <div style="color: green;">保存成功,刷新生效。</div>
        `;
        setTimeout(() => {
            document.getElementById("setting-log").innerHTML = '';
        }, 2000);
    }

    const ErrorPage = (code) => {
        document.getElementById("middle-line").style.display = 'none';
        title_ul.appendChild(document.createTextNode(`连接 SiYuan 失败。${code ? `错误码: ${code}` : ''}`));
    }

    if (config.SiYuan.Token !== "") {
        console.log("[SiYuan Search] CheckConnect");
        const checkUrl = new URL('/api/system/version', config.SiYuan.Endpoint);
        GM_xmlhttpRequest({
            method: 'POST',
            url: checkUrl.href,
            responseType: 'json',
            onload: function (res) {
                if (res.status == 200 && res.response.code === 0) {
                    console.log("[SiYuan Search] Connect success, SiYuan version: ", res.response.data);

                    const sqlUrl = new URL('/api/query/sql', config.SiYuan.Endpoint);
                    // SQL 搜索
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: sqlUrl.href,
                        data: JSON.stringify({
                            "stmt": `SELECT id, content, hpath, updated, root_id
                                FROM blocks 
                                WHERE content
                                LIKE '%${searchInput}%' 
                                ORDER BY 
                                sort ASC, 
                                updated DESC;`
                        }),
                        Headers: {
                            "Authorization": "Token " + config.SiYuan.Token
                        },
                        responseType: 'json',
                        onload: function (res) {
                            if (res.status === 200 && res.response.code === 0) {
                                console.log(`[SiYuan Search] Search '${searchInput}' success, result: `, res.response.data);
                                // 以标题成功匹配的列表
                                let note_list = [];
                                // 以块成功匹配的对象,用于存放查询到的笔记标题
                                let block_list = [];
                                // 渲染页面
                                res.response.data.forEach((e) => {
                                    let li = document.createElement('li');
                                    // 判断是否文档块
                                    if (e.root_id === e.id) {
                                        li.classList.add('siyuan-search-item');
                                        note_list.push(e.root_id);
                                        if (config.Location === "local") {
                                            li.innerHTML = `<a href="siyuan://blocks/${e.root_id}" target="_blank">
                                            <div class="siyuan-search-title">${e.content}</div>
                                            </a>
                                            <div class="siyuan-search-info">
                                                <div class="siyuan-updated">时间:${e.updated.substring(0, 8)}</div>
                                            </div>`;
                                        } else if (config.Location === "remote") {
                                            li.innerHTML = `<a href="${config.SiYuan.Endpoint}?id=${e.root_id}&focus=true" target="_blank">
                                            <div class="siyuan-search-title">${e.content}</div>
                                            </a>
                                            <div class="siyuan-search-info">
                                                <div class="siyuan-updated">时间:${e.updated.substring(0, 8)}</div>
                                            </div>`;
                                        }
                                        title_ul.appendChild(li);
                                    } else {
                                        if (note_list.includes(e.root_id) === false && block_list.includes(e.root_id) === false) {
                                            const attrUrl = new URL('/api/attr/getBlockAttrs', config.SiYuan.Endpoint);

                                            // 获取块的笔记名字
                                            GM_xmlhttpRequest({
                                                method: 'POST',
                                                url: attrUrl.href,
                                                data: JSON.stringify({
                                                    "id": e.root_id,
                                                }),
                                                Headers: {
                                                    "Authorization": "Token " + config.SiYuan.Token
                                                },
                                                responseType: 'json',
                                                onload: function (res) {
                                                    if (res.status === 200 && res.response.code === 0) {
                                                        // 渲染到页面
                                                        li.innerHTML = `<a href="${config.SiYuan.Endpoint}?id=${e.root_id}&focus=true" target="_blank">
                                                        <div>${res.response.data.title}</div>
                                                        </a>`;
                                                        related_ul.appendChild(li);

                                                    }
                                                },
                                            });
                                            // 标记这个笔记已经显示了,放里面会有异步
                                            block_list.push(e.root_id);
                                        }
                                    }
                                });
                            } else {
                                console.error("[SiYuan Search] Response issue: ", res);
                                ErrorPage(res.response.code);
                            }
                        },
                        onerror: function () {
                            console.error("[SiYuan Search] Search failed.");
                        }
                    });
                } else {
                    console.error("[SiYuan Search] Response issue: ", res);
                    ErrorPage(res.response.code);
                }
            },
            onerror: function () {
                console.error("[SiYuan Search] Connect failed.");
                ErrorPage();
            }
        });
    } else {
        console.log("[SiYuan Search] Please configure the connection information.");
        // 展开设置页面
        setting.style.display = 'inline';
        document.getElementById("middle-line").style.display = 'none';
        title_ul.appendChild(document.createTextNode(`请先登录。`));
    }
})();