豆瓣读书同步到Notion

抓取豆瓣读书信息,基于Notion搭建私人图书管理系统

// ==UserScript==
// @name         豆瓣读书同步到Notion
// @namespace    https://greasyfork.org/zh-CN/scripts/464467-%E8%B1%86%E7%93%A3%E8%AF%BB%E4%B9%A6%E5%90%8C%E6%AD%A5%E5%88%B0notion
// @version      1.5
// @description  抓取豆瓣读书信息,基于Notion搭建私人图书管理系统
// @author       @Yanwudong https://twitter.com/yanwudong
// @match        https://book.douban.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=douban.com
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @license      GNU GPLv3
// ==/UserScript==
(function() {
    'use strict';
        // 添加CSS样式
    GM_addStyle(`
  .toast {
    position: fixed;
    top: 150px;
    right: 50%;
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.2s ease-in-out;
  }
  .toast.show {
    opacity: 1;
  }

  .toast-body {
    background-color: #f2f8f2;
    //border: 1px solid #ccc;
    //border-radius: 3px;
    color:#4f946e;
    padding: 2px;
    padding-left:10px;
    padding-right:10px
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  }
  #nlog {
    top: 130px;
    right: 50%;
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.2s ease-in-out;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
    background: #fff;
    overflow: hiadden;
    padding: 40px 30px 30px;
    position: fixed;
    display : none;
  }
  #nlog.show {
    opacity: 1;
  }
  .form-group{
    margin-bottom: 10px;
  }
  .form-control{
    border: 1px solid #e4e6e5;
    border-radius: 3px;
    box-sizing: border-box;
    font-size: 13px;
    padding: 10px;
    width: 280px;
  }
  #exampleModalLabel{
    cursor: pointer;
    font-size: 18px;
    line-height: 2;
    text-align: center;
    border-bottom: 2px solid #494949;
    color: #333;
    font-weight: 600;
    margin-bottom:20px;
  }
  #twitter{
    margin-left:30px;
  }
  #saveBtn{
    background-color: #41ac52;
    border: 1px solid #b9dcd0;
    color: #fff;
    cursor: default;
    font-size: 15px;
    font-weight: 600;
    padding: 0;
    width:79%;
  }
  .modal-footer button{
    height:36px;
    width:19%;
    margin-bottom:20px;

  }
  #syncbt {
    display: inline-block;
    margin-left: 10px;
    font-size: 13px;
    color:#4f946e;
    background-color: #f2f8f2;
    padding: 2px;
    padding-left: 8px;
    padding-right: 8px;
    border: 1px solid #b9dcd0;
    border-radius:3px
  }
  #syncbt:hover {
    cursor: pointer;
  }
  #setbt {
    display: inline-block;
    margin-left: 5px;
    font-size: 13px;
    color:#4f946e;
    background-color: #f2f8f2;
    padding: 2px;
    border: 1px solid #b9dcd0;
    border-radius:3px
  }

     `);

// 创建一个 Modal
const modalHtml = `
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Notion Api 设置</h5>
      </div>
      <div class="modal-body">
        <form>
          <div class="form-group">
            <input type="text" class="form-control" id="apiInput" placeholder="token">
          </div>
          <div class="form-group">
            <input type="text" class="form-control" id="pageIdInput" placeholder="页面 ID">
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary" id="saveBtn">保存设置</button>
        <button type="button" class="btn btn-secondary" id="closeBtn" data-dismiss="modal">关闭</button>
      </div>
      <div class="modal-bottom">
        <a href="https://seemly-pear-9fc.notion.site/Notion-e0ae1a1d391143abb9ff383730649149" id="refBtn" target="_blank";>操作说明</a>
        <a href="https://twitter.com/yanwudong" id="twitter" target="_blank";>推特</a>
      </div>
    </div>
  </div>
</div>
`;
//创建一个提示信息
    const info = `
     <div class="toast-body">
       🎉 同步成功!
     </div>
    `;

    //GM_setValue('databaseId','');

    //初始化用户信息

    var nToken = GM_getValue('nToken');
    var pageId = GM_getValue('pageId');
    var databaseId = GM_getValue('databaseId');
    const notionVersion = '2022-06-28';


    // 创建一个轻提示框元素
    const toast = document.createElement('div');
    toast.id = 'toast';
    toast.className = 'toast';
    toast.innerHTML =`
     <div class="toast-body">
       🎉 同步成功!
     </div>
    `;
    // 将轻提示框添加到页面中
    document.body.appendChild(toast);

    //创建一个授权框,让用户输入ApiKey和PageId
    const nlog = document.createElement('div');
    nlog.id = 'nlog';
    nlog.innerHTML = modalHtml;
    document.body.appendChild(nlog);
    nlog.classList.add('show');
    // 等待页面加载完成后执行
    $(document).ready(function() {
        // 给按钮添加点击事件
        $('#saveBtn').click(function() {
            nToken = $('#apiInput').val();
            pageId = $('#pageIdInput').val();
            GM_setValue('nToken', nToken);
            GM_setValue('pageId', pageId);
            //用户第一次使用先创建数据库
            createDatabase();
            nlog.style.display = 'none';
        });
        $('#closeBtn').click(function(){
            nlog.style.display = 'none';
        });
    });

    //检查用户是否登录
    const checkUserInfo = () =>{
        // 如果用户没有输入过信息,弹出输入框让其输入
        if (!nToken || !pageId || !databaseId ) {
            nlog.style.display = 'block';
        }else{
            syncToNotion();
        }
    }
    //设置用户信息,暂时没用这个
    const setUserInfo = () =>{
        //如果有用户信息,则在输入框展示,之前有设置的时候用,现在用不到了
        if (nToken || pageId || databaseId ) {
            $('#apiInput').val(nToken);
            $('#pageIdInput').val(pageId);
        }
        nlog.style.display = 'block';
    }

    //输入用户信息并保存,暂时没用这个
    const addUserInfo = () =>{
        nToken = prompt('请输入您的ApiKey:');
        pageId = prompt('请输入您的页面Id:');
        // 存储用户信息
        GM_setValue('nToken', nToken);
        GM_setValue('pageId', pageId);
        //用户第一次使用先创建数据库
        createDatabase();
    }

    // 添加同步按钮到页面
    const addButton = () => {
        const button = document.createElement('button');
        button.innerText = '同步到Notion';
        button.onclick = checkUserInfo;
        //const set = document.createElement('button');
        //set.innerText ='⚙️';
        //set.onclick = setUserInfo;
        //button.insertAdjacentElement('afterend',set);
        //set.id = 'setbt';
        const actions = document.querySelector('#wrapper > h1 > span');//在书籍名称后面加按钮
        actions.insertAdjacentElement('afterend',button);
        button.id = 'syncbt';
    };

    // 获取书籍信息,需要研究下元素获取
    const getBookInfo = () => {
        const infos = document.querySelectorAll('#info .pl');
        let book = {};
        book['书名'] = document.querySelector('#wrapper > h1 > span').innerText;
        book['封面'] = document.querySelector('#mainpic > a > img').src;
        book['评分'] = document.querySelector('.rating_num').innerText;
        book['豆瓣链接']=window.location.href;
        //循环遍历infos,并把每个元素赋值给info
        for(const info of infos){
            if(info.innerText === '作者'){
                book['作者'] = info.nextSibling.nextSibling.innerText
            }else if(info.innerText === '出版社:'){
                book['出版社'] = info.nextSibling.nextSibling.innerText
            }else if(info.innerText === '丛书:'){
                book['丛书'] = info.nextSibling.nextSibling.innerText
            }else{
               let prop = info.innerText.substr(0,info.innerText.length-1)
                book[prop] = info.nextSibling.data
            }
        }
        return book
    };

    // 同步书籍信息到Notion
    const syncToNotion = () => {
        //先判断有没有设置用户api
        //checkUserInfo();
        const book = getBookInfo();
        //怎么创建数据库,并把数据传进去,是最大的问题?开始查资料,问chatGPT,2023年4月17日11:40:34创建数据库;2023年4月17日14:52,创建数据库成功了,卧槽感人
        //createDatabase()
        //同步书到notion数据库
        createItem(book);
    };
    //创建页面子数据库database
    function createDatabase() {
        const body = {
            'parent': { 'type': 'page_id', 'page_id': pageId },
            'title': [
                {
                    'type': 'text',
                    'text': {
                        'content': 'BookList',
                        'link': null
                    }
                }
            ],
            'icon':{
                'type':'emoji',
                'emoji':'📚'
            },
            'properties': {
                '书名': {
                    'title': {}
                },
                '标签':{
                    'multi_select':{
                        'options':[
                            {
                                'name':'运营',
                                'color':'purple'
                            },
                            {
                                'name':'文学',
                                'color':'orange'
                            },
                            {
                                'name':'流行',
                                'color':'green'
                            },
                            {
                                'name':'生活',
                                'color':'default'
                            },
                            {
                                'name':'经管',
                                'color':'yellow'
                            },
                            {
                                'name':'科技',
                                'color':'blue'
                            },
                            {
                                'name':'文化',
                                'color':'red'
                            }
                        ]
                    }
                },
                '状态':{
                    'select':{
                        'options':[
                            {
                                'name':'🌑想读',
                                'color':'purple'
                            },
                            {
                                'name':'🌒在读',
                                'color':'orange'
                            },
                            {
                                'name':'🌕读过',
                                'color':'green'
                            }
                        ]
                    }
                },
                '打分':{
                    'select':{
                        'options':[
                            {
                                'name':'⭐️⭐️⭐️⭐️⭐️'
                            },
                            {
                                'name':'⭐️⭐️⭐️⭐️'
                            },
                            {
                                'name':'⭐️⭐️⭐️'
                            },
                            {
                                'name':'⭐️⭐️'
                            },
                            {
                                'name':'⭐️'
                            }
                        ]
                    }
                },
                '作者': {
                    'rich_text': {}
                },
                '出版社': {
                    'rich_text': {}
                },
                '出版年月': {
                    'rich_text': {}
                },
                '页数': {
                    'number': {
                        'format': 'number'
                    }
                },
                '评分': {
                    'number': {
                        'format': 'number'
                    }
                },
                'ISBN': {
                    'rich_text': {}
                },
                '封面': {
                    'files': {}
                },
                '豆瓣链接': {
                    'url': {}
                },
                '简介': {
                    'rich_text': {}
                },
            }
        };
        const options = {
            headers: {
                Authorization: 'Bearer ' + nToken,
                'Notion-Version': notionVersion,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(body)
        }
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://api.notion.com/v1/databases',
            headers: options.headers,
            data: options.body,
            onload: function(response) {
                const res = JSON.parse(response.responseText);
                if (res.object === 'error') {
                    alert(res.message);
                } else {
                    GM_setValue('databaseId',res.id);
                    databaseId = res.id;
                    toast.innerHTML =`
                       <div class="toast-body">
                          🎉 配置成功!
                       </div>
                        `;
                    // 显示轻提示框
                    toast.classList.add('show');

                    // 3秒后隐藏轻提示框
                    setTimeout(() => {
                        toast.classList.remove('show');
                    }, 3000);
                }
            }
        });

    }
    //同步书
    function createItem(book) {
        const body = {
            'parent': { 'type': 'database_id', 'database_id': databaseId },
            'icon':{
                'type':'emoji',
                'emoji':'📔'
            },
            'properties': {
                '书名': {
                    'type': 'title',
                    'title': [{ 'type': 'text', 'text': { 'content': book['书名'] } }]
                },
                'ISBN': {
                    'type': 'rich_text',
                    'rich_text': [{ 'type': 'text', 'text': { 'content': book['ISBN'] ? book['ISBN']:'' } }]
                },
                '页数': {
                    'number': parseInt(book['页数'] ? book['页数']:'' )
                },
                '状态':{
                    select:{
                        'name':'🌑想读'
                    }
                },
                '出版社': {
                    'type': 'rich_text',
                    'rich_text': [{ 'type': 'text', 'text': { 'content': book['出版社'] ? book['出版社']:'' } }]
                },
                '出版年月': {
                    'type': 'rich_text',
                    'rich_text': [{ 'type': 'text', 'text': { 'content': book['出版年'] ? book['出版年']:'' } }]
                },
                '评分': {
                    'number': parseFloat(book['评分'])
                },
                '作者': {
                    'type': 'rich_text',
                    'rich_text': [{ 'type': 'text', 'text': { 'content': book['作者']} }]
                },
                '封面': {
                    'files': [
                        {
                            'type': 'external',
                            'name': 'cover',
                            'external': { 'url': book['封面']}
                        },
                    ]
                },
                '豆瓣链接':{
                    'type':'url',
                    'url':book['豆瓣链接']
                }
            },
        };
        //传参数
        const options = {
            headers: {
                Authorization: 'Bearer ' + nToken,
                'Notion-Version': notionVersion,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(body)
        }
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://api.notion.com/v1/pages',
            headers: options.headers,
            data: options.body,
            onload: function(response) {
                const res = JSON.parse(response.responseText);
                if (res.object === 'error') {
                    alert(res.message);
                } else {
                    toast.innerHTML =`
                       <div class="toast-body">
                          🎉 同步成功!
                       </div>
                        `;
                    // 显示轻提示框
                    toast.classList.add('show');

                    // 3秒后隐藏轻提示框
                    setTimeout(() => {
                        toast.classList.remove('show');
                    }, 3000);

                }
            }
        });
    }

    addButton()
})();