Greasy Fork

Greasy Fork is available in English.

bilibili history manage B站历史记录持久化并扩展搜索功能

B站历史记录持久化存储到浏览器的indexDB数据库中,提供更丰富的搜索功能

当前为 2022-07-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         bilibili history manage B站历史记录持久化并扩展搜索功能
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  B站历史记录持久化存储到浏览器的indexDB数据库中,提供更丰富的搜索功能
// @author       You
// @match        https://www.bilibili.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @require      http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js
// @license      MIT
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @run-at       document-start

// ==/UserScript==
/*导入zl-indexdb开源库,并遵守MIT开源协议进行少量修改*/
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global['zl-indexdb'] = factory());
}(this, (function () { 'use strict';

  class IndexDBOperation {

      /**
       * @method getDB
       * @for IndexDBOperation
       * @description 得到IndexDBOperation实例,创建/打开/升级数据库
       * @param  {String} dbName 要创建或打开或升级的数据库名
       * @param  {Number} dbVersion 要创建或打开或升级的数据库版本
       * @param  {Array} objectStores 此数据库的对象仓库(表)信息集合
       * @param  {String} objectStores.objectStoreName  表名
       * @param  {Number} objectStores.type  表示创建数据库或者升级数据库时,这个数据仓库(表)的处理类型( 0:表示把已存在的删除,然后重新创建。 1:表示如果不存在才重新创建,2:表示只删除,不再进行重新创建)
       * @param  {Object}  objectStores.keyMode  这个表的key的模式,具有两个可选属性:keyPath,autoIncrement,如果都不写那么添加数据时就需要自动手动的指定key 
       * @param  {String} objectStores.keyMode.keyPath   表示在添加数据时,从对象类型的值中取一个字段,作为这条记录的键值,如果作为值的对象中不含有此字段,那么就会报错。
       * @param  {Boolean} objectStores.keyMode.autoIncrement   表示自动生成的递增数字作为记录的键值,一般从1开始。
       * @param  {Array} objectStores.indexs  索引数组,每个对象元素都代表了一条设置的索引,{ indexName: "keyIndex", fieldName: "key", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
       * @return  IDBRequest对象
       * @author zl-fire 2021/08/17
       * @example
       * // 初始化时,开始创建或者打开indexDB数据库,objectStores 会在升级函数里面执行
       * let objectStores = [
       * {
       *     objectStoreName: "notes",//表名
       *     type: 1,//0:表示把已存在的删除,然后重新创建。 1:表示如果不存在才重新创建,2:表示只删除,不再进行重新创建
       *     keyMode:  { autoIncrement: true, keyPath: 'recordID' },//key的模式: 【keyPath: 'recordID' 表示指定recordID为主键】, 【autoIncrement: true 表示设置主键key自动递增(没有指定具体主键的情况下,主键字段名默认为key)】,【两者可以同存,也可以只存在一个,甚至都不存在】
       *         { indexName: "keyIndex", fieldName: "key", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
       *         { indexName: "emailIndex", fieldName: "email", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
       *         { indexName: "doc_typeIndex", fieldName: "doc_type", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
       *     ]
       * }, 
       * {
       *     objectStoreName: "users",//表名
       *     type: 1,//0:表示把已存在的删除,然后重新创建。 1:表示如果不存在才重新创建,2:表示只删除,不再进行重新创建
       *     keyMode: { keyPath: 'keyword' },//key的模式,使用时直接设置就可
       *     indexs: [
       *         { indexName: "keywordIndex", fieldName: "keyword", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
       *     ]
       * }
       * ]
       * window.DB = new IndexDBOperation("taskNotes", 1, objectStores); //如果版本升级,请刷新下页面在执行升级操作
       */
      constructor(dbName, dbVersion, objectStores) {
          //先检查数据是否传入
          if (!(dbName && dbVersion && objectStores)) {
              console.error("dbName, dbVersion, objectStores参数都为必填项!"); return;
          }
          this.dbName = dbName;
          this.dbVersion = dbVersion;
          this.objectStores = objectStores;
          this.db = null;//数据库默认为null
          this.getDB();//得到数据库对象:this.db = 数据库对象;,这里可以不用await进行等待,因为后续会使用示例对象进行操作,并且还会判断db是否存在
      }

      /**
      * @method createObjectStores
      * @for IndexDBOperation
      * @description 创建对象仓库的函数
      * @param  {Object} db  indexDB数据库的对象
      * @param  {Object}  obj 此数据库的 对象仓库(表)相关信息,包含表名,keyMode,索引集合
      * @return {void}  无返回值
      * @author zl-fire 2021/08/17
      * @example
      * db: DB,
      * obj: {
      *     objectStoreName: "notes",//表名
      *     type: 1,//0:表示把已存在的删除,然后重新创建。 1:表示如果不存在才重新创建,2:表示只删除,不再进行重新创建
      *     keyMode:  { autoIncrement: true, keyPath: 'recordID' },//key的模式,使用时直接设置就可
      *     indexs: [
      *         { indexName: "keyIndex", fieldName: "key", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
      *         { indexName: "emailIndex", fieldName: "email", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
      *         { indexName: "doc_typeIndex", fieldName: "doc_type", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
      *     ]
      * }
       */
      async createObjectStores(db, obj) {
          let store = db.createObjectStore(obj.objectStoreName, obj.keyMode);
          if (obj.indexs && obj.indexs instanceof Array) {
              obj.indexs.forEach(ele => {
                  store.createIndex(ele.indexName, ele.fieldName, ele.only); //索引名,字段名,索引属性值是否唯一
              });
          }
      }

      /**
       * 作用:创建/打开/升级数据库,
       * @method getDB
       * @for IndexDBOperation
       * @description 此方法自动将thid.db赋值为数据库对象
       * @return void
       */
      async getDB() {
          let { dbName, dbVersion, objectStores } = this;
          let _this = this;
          return new Promise(function (resolve, reject) {
              let request = window.indexedDB.open(dbName, dbVersion);
              request.onerror = function (event) {
                  console.log("数据库", dbName, "创建/打开失败!");
                  reject({
                      state: false,//失败标识
                      mes: "数据库" + dbName + "创建/打开失败!"
                  });
              };
              request.onsuccess = function (event) {
                  console.log("数据库", dbName, "创建/打开成功,拿到数据库对象了!");
                  _this.db = request.result;
                  resolve({
                      state: true,//成功标识
                  });
              };
              request.onupgradeneeded = function (event) {
                  console.log("数据库", dbName, "版本变化了!");
                  let db = event.target.result; //event中包含了数据库对象
                  //创建所有指定的表
                  objectStores.forEach(obj => {
                      switch (obj.type) {
                          case 0: //表示把已存在的删除,然后重新创建
                              if (db.objectStoreNames.contains(obj.objectStoreName)) {
                                  db.deleteObjectStore(obj.objectStoreName);
                              }
                              _this.createObjectStores(db, obj); //开始创建数据
                              break;
                          case 1: //表示如果不存在才重新创建
                              if (!db.objectStoreNames.contains(obj.objectStoreName)) {
                                  _this.createObjectStores(db, obj); //开始创建数据
                              }
                              break;
                          case 2: //表示只删除,不再进行重新创建
                              if (db.objectStoreNames.contains(obj.objectStoreName)) {
                                  db.deleteObjectStore(obj.objectStoreName);
                              }
                              break;
                      }
                  });

              };
          })
      }

      /**
       * 作用:给对象仓库(表)添加数据,可添加一条,也可批量添加多条,
       * @method addData
       * @for IndexDBOperation
       * @param  {Array} stores  数据要插入到那个对象仓库中去,如["hello"],类型为数组是为了后面好扩展新功能
       * @param  {Object|Array} data  要添加的数据,对象或对象数组
       * @return {boolean} true:添加成功,false:添加失败
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //==============创建数据仓库时指定了keypath的模式==============
       * 
       * //添加一条数据(添加的数据中包含了key,或者key会自动生成并自动递增)
       * let res = await DB.addData(["hello"], { name: "zs", age: 18 });
       * 
       * //添加多条数据(添加的数据中包含了key,或者key会自动生成并自动递增)
       * let res2 = await DB.addData(["hello"], [
       * { name: "zss1", age: 18 }, 
       * { name: "zsd2", age: 18 }, 
       * { name: "zs3", age: 18 }, 
       * { name: "zsf4", age: 20 }
       * ]);
       * 
       * //==============创建数据仓库时没有指定keypath的模式==============
       * 
       * //添加一条数据(需要手动传入指定的key)
       * let res = await DB.addData(["hello"], { name: "zs", age: 18, thekeyName:"id" });
       * 
       * //添加多条数据添加多条数据(需要手动传入指定的key:thekeyName)
       * let res2 = await DB.addData(["hello"], [
       * { name: "zss1", age: 18, thekeyName:"id1"  }, 
       * { name: "zsd2", age: 18, thekeyName:"id2" }, 
       * { name: "zs3", age: 18 , thekeyName:"id3" }, 
       * { name: "zsf4", age: 20 , thekeyName:"id4" }
       * ]);
       **/
      async addData(stores, data) {
          let _this = this;
          return new Promise(async function (resolve) {
              try {
                  //先检查数据是否传入
                  if (!(stores && data)) {
                      console.error("stores, data,参数都为必填项!"); return;
                  }
                  //  * 逻辑:先判断数据库是否已经被创建
                  if (_this.db === null) {
                      await _this.getDB();
                  }
                  //创建事物
                  let transaction = _this.db.transaction(stores, "readwrite"); //以可读写方式打开事物,该事务跨越stores中的表(object store)
                  let store = transaction.objectStore(stores[0]); //获取stores数组的第一个元素对象
                  //然后在判断要插入的数据类型:如果是数组,则循环添加插入
                  if (data instanceof Array) {
                      let resArr = [];
                      data.forEach(obj => {
                          let res;
                          if (obj.thekeyName) {
                              let key=obj.thekeyName;
                              delete obj.thekeyName;
                              res = store.add(obj, key);
                          }
                          else {
                              res = store.add(obj);
                          }
                          res.onsuccess = function (event) {
                              resArr.push('数据添加成功');
                              if (resArr.length == data.length) {
                                  resolve(true);
                              }
                          };
                          res.onerror = function (event) {
                              console.error(event);
                              resolve(false);
                          };
                      });

                  }
                  else {
                      let res;
                      if (data.thekeyName) {
                          // 表示用户在创建数据仓库时,没有设置keypath,所以添加数据时需要手动的指定
                          let key=data.thekeyName;
                          delete data.thekeyName;
                          res = store.add(data, key);
                      }
                      else {
                          res = store.add(data);
                      }
                      res.onsuccess = function (event) {
                          resolve(true);
                      };
                      res.onerror = function (event) {
                          resolve(false);
                          console.error(event);
                      };
                  }
              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })
      }

      /**
       * @method queryBykeypath
       * @for IndexDBOperation
       * @description 通过keypath向对象仓库(表)查询数据,无参数则查询所有,
       * @param {Array}  stores  对象仓库数组,但是只取第一个对象仓库名
       * @param {String}  keypath  查询条件,keypath
       * @return {Object} 查询结果
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //从hello表中查询主键(keypath)为5的单条数据
       * let res9 = await DB.queryBykeypath(['hello'],5)
       * 
       * //从hello表中查询所有数据
       * let res10 = await DB.queryBykeypath(['hello'])
       **/
      async queryBykeypath(stores, keypath) {
          let _this = this;
          return new Promise(async function (resolve) {
              try {
                  //先检查数据是否传入
                  if (!stores) {
                      console.error("stores参数为必填项!"); return;
                  }
                  //  * 逻辑:先判断数据库是否已经被创建
                  if (_this.db === null) {
                      await _this.getDB();
                  }
                  var transaction = _this.db.transaction(stores, 'readwrite');
                  var store = transaction.objectStore(stores[0]);
                  if (keypath) {//查询单条数据
                      //从stores[0]表中通过keypath查询数据 
                      var request = store.get(keypath);
                      request.onsuccess = function (e) {
                          var value = e.target.result;
                          resolve(value);
                      };
                  }
                  else {  //查询所有数据
                      let arr = [];
                      store.openCursor().onsuccess = function (event) {
                          var cursor = event.target.result; //游标:指向表第一条数据的指针
                          if (cursor) {
                              arr.push(cursor.value);
                              cursor.continue();
                          } else {
                              // console.log('没有更多数据了!');
                              resolve(arr);
                          }
                      };
                  }

              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })
      }

      /**
       * 作用:通过索引index向对象仓库(表)查询数据,不传查询参数就查询所有数据
       * @method queryByIndex
       * @for IndexDBOperation
       * @param {Array} stores 对象仓库数组,但是只取第一个对象仓库名
       * @param {Object} indexObj 查询条件,索引对象,例:{name:"",value:"",uni:true},uni表示索引是否唯一,true唯一,false不唯一
       * @param {Object[]} where 在基于索引查出的数据中进一步筛选。对象数组 [{key,value,opt:"=="},{key,value,opt}] opt为操作符
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //从hello表中查询nameIndex为zs3的单条数据
       * let res11 = await DB.queryByIndex(['hello'],{name:"nameIndex",value:"zs3",uni:true})
       * 
       * //从hello表中查询ageIndex为18的多条数据
       * let res12 = await DB.queryByIndex(['hello'],{name:"ageIndex",value:"18"})
       * 
       * //从hello表中查询ageIndex为18的多条数据,然后传入where进一步查询
       * let res12 = await DB.queryByIndex(['hello'],{name:"ageIndex",value:"18"},[{key:"sex",value:'男',opt:"=="}])
       **/
      async queryByIndex(stores, indexObj, where ,ps=1000) {
          let _this = this;
          return new Promise(async function (resolve) {
              try {
                  //先检查数据是否传入
                  if (!stores) {
                      console.error("stores参数为必填项!"); return;
                  }
                  //  * 逻辑:先判断数据库是否已经被创建
                  if (_this.db === null) {
                      await _this.getDB();
                  }
                  var transaction = _this.db.transaction(stores, 'readwrite');
                  var store = transaction.objectStore(stores[0]);
                  if (indexObj) {//按索引查询数据
                      // 如果索引时唯一的
                      if (indexObj.uni) {
                          var index = store.index(indexObj.name);
                          index.get(indexObj.value).onsuccess = function (e) {
                              var v = e.target.result;
                              resolve(v);
                          };
                      }
                      // 如果索引是不唯一的
                      else {
                          let arr = [];
                          var index = store.index(indexObj.name);
                          var request = index.openCursor(IDBKeyRange.upperBound(indexObj.value),'prev');/* 这里迎合需求改掉了*/
                          var count = 0
                          request.onsuccess = function (e) {
                              var cursor = e.target.result;
                              if (cursor&&count<ps) {
                                  var v = cursor.value;
                                  arr.push(v);
                                  cursor.continue();
                                  count++
                              }
                              else {
                                  // 如果还需要进一步筛选(全部为且操作)
                                  if (where) {
                                      let arr1 = arr.filter(ele => {
                                          let flag = true;
                                          // 只要有一个条件不符合,此条数据就过滤掉
                                          for (let i = 0; i < where.length; i++) {
                                              let filterObj = where[i];
                                              let val = ele[filterObj.key];
                                              if (!val) val = "";
                                              if (typeof val == 'string') val = val.trim();
                                              switch (filterObj.opt) {
                                                  case "==":
                                                      flag = (val == filterObj.value);
                                                      break;
                                                  case "===":
                                                      flag = (val === filterObj.value);
                                                      break;
                                                  case "<":
                                                      flag = (val < filterObj.value);
                                                      break;
                                                  case "<=":
                                                      flag = (val <= filterObj.value);
                                                      break;
                                                  case ">":
                                                      flag = (val > filterObj.value);
                                                      break;
                                                  case ">=":
                                                      flag = (val >= filterObj.value);
                                                      break;
                                                  case "!=":
                                                      flag = (val != filterObj.value);
                                                      break;
                                                  case "!==":
                                                      flag = (val !== filterObj.value);
                                                      break;
                                                  case "include": //包含操作
                                                      flag = (val.includes(filterObj.value));
                                                      break;
                                                  case "beIncluded": //包含操作
                                                      flag = (filterObj.value.includes(val));
                                                      break;
                                                  default: break;
                                              }
                                              if (!flag) {
                                                  break;
                                              }
                                          }
                                          return flag;
                                      });
                                      resolve(arr1);
                                  }
                                  else resolve(arr);
                              }
                          };
                      }
                  }
                  else {  //查询所有数据
                      let arr = [];
                      store.openCursor().onsuccess = function (event) {
                          var cursor = event.target.result; //游标:指向表第一条数据的指针
                          if (cursor) {
                              arr.push(cursor.value);
                              cursor.continue();
                          } else {
                              // console.log('没有更多数据了!');
                              resolve(arr);
                          }
                      };
                  }

              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })
      }

      /**
       * 作用:修改对象仓库数据,不存在就创建
       * @method updateData
       * @for IndexDBOperation
       * @param {Array} stores 要修改的对象仓库
       * @param {any} data 要修改的数据
       * @return {boolean}  true:修改成功,false:修改失败
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //======================主键本身就是数据元素中的一个字段:recordID===========
       * //修改单条数据
       * let res3 = await DB.updateData(["hello"], { name: "111", age: 111, recordID: 1 });
       * 
       * //批量修改数据
       * let res4 = await DB.updateData(["hello"], [
       * { name: "zss111111", age: 180, recordID: 21 }, 
       * { name: "zss1222222", age: 180, recordID: 22 }, 
       * { name: "zss1333333", age: 180, recordID: 23 }
       * ]);
       * 
       * //======================主键为手动指定的字段thekeyName,不存在于数据仓库结构中===========
       * 
       * //修改单条数据
       *  let res3 = await DB.updateData(["hello"], { name: "111", age: 111, recordID: 1 , thekeyName:1 } );
       * 
       * //批量修改数据
       * let res4 = await DB.updateData(["hello"], [
       * { name: "zss111111", age: 180, recordID: 21 , thekeyName:2 }, 
       * { name: "zss1222222", age: 180, recordID: 22 , thekeyName:3 }, 
       * { name: "zss1333333", age: 180, recordID: 23 , thekeyName:4 }
       * ]);
       * 
       * 
       **/
      async updateData(stores, data ) {
          let _this = this;
          return new Promise(async function (resolve, reject) {
              try {
                  //先检查数据是否传入
                  if (!(stores && data)) {
                      console.error("stores, data,参数都为必填项!"); return;
                  }
                  //  * 逻辑:先判断数据库是否已经被创建
                  if (_this.db === null) {
                      await _this.getDB();
                  }
                  //创建事物
                  let transaction = _this.db.transaction(stores, "readwrite"); //以可读写方式打开事物,该事务跨越stores中的表(object store)
                  let store = transaction.objectStore(stores[0]); //获取stores数组的第一个元素对象
                  //然后在判断要插入的数据类型:如果是数组,则循环添加插入
                  if (data instanceof Array) {
                      let resArr = [];
                      data.forEach(obj => {
                          let res;
                          // 如果是手动指定thekeyName字段的值为主键
                          if (obj.thekeyName) {
                              let key = obj.thekeyName;
                              delete obj.thekeyName;
                              res = store.put(obj, key);
                          }
                          else {
                              res = store.put(obj);
                          }
                          res.onsuccess = function (event) {
                              resArr.push('数据更新成功');
                              if (resArr.length == data.length) {
                                  resolve(true);
                              }
                          };
                          res.onerror = function (event) {
                              resolve(false);
                              console.error(event);

                          };
                      });

                  }
                  else {
                      let res;
                      let obj = data;
                      // 如果是手动指定thekeyName字段的值为主键
                      if (obj.thekeyName) {
                          let key = obj.thekeyName;
                          delete obj.thekeyName;
                          res = store.put(obj, key);
                      }
                      else {
                          res = store.put(obj);
                      }
                      res.onsuccess = function (event) {
                          resolve(true);
                      };
                      res.onerror = function (event) {
                          resolve(false);
                          console.error(event);
                      };
                  }
              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })

      }

      /**
       * 作用:删除对象仓库数据,
       * @method deleteData
       * @for IndexDBOperation
       * @param {Array} stores 要删除的对象仓库
       * @param {number|string|Array} data 要删除的数据的key,或者批量key的集合
       * @return {boolean}  true:删除成功,false:删除失败
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //删除主键为23的数据
       * let res5 = await DB.deleteData(["hello"], [23]);
       * 
       * //删除表的所有数据
       * let res6 = await DB.deleteData(["hello"]);
       **/
      async deleteData(stores, data) {
          let _this = this;
          return new Promise(async function (resolve, reject) {
              try {
                  //先检查数据是否传入
                  if (!stores) {
                      console.error("stores参数为必填项!"); return;
                  }
                  //  * 逻辑:先判断数据库是否已经被创建
                  if (_this.db === null) {
                      await _this.getDB();
                  }
                  //创建事物
                  let transaction = _this.db.transaction(stores, "readwrite"); //以可读写方式打开事物,该事务跨越stores中的表(object store)
                  let store = transaction.objectStore(stores[0]); //获取stores数组的第一个元素对象
                  //如果未传入data参数,则删除此对象仓库所有的数据
                  if (!data) {
                      store.clear();
                  }
                  //如果是数组,则循环删除
                  else if (data instanceof Array) {
                      let resArr = [];
                      data.forEach(obj => {
                          let res = store.delete(obj);
                          res.onsuccess = function (event) {
                              resArr.push('数据删除成功');
                              if (resArr.length == data.length) {
                                  resolve(true);
                              }
                          };
                          res.onerror = function (event) {
                              resolve(false);
                              console.error(event);

                          };
                      });

                  }
                  //如果是单个值,直接删除
                  else {
                      let res = store.delete(data);
                      res.onsuccess = function (event) {
                          resolve(true);
                      };
                      res.onerror = function (event) {
                          resolve(false);
                          console.error(event);
                      };
                  }
              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })

      }

      /**
       * 作用:关闭数据链接,
       * @method close
       * @for IndexDBOperation
       * @return {boolean}  true:成功,false:失败
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //关闭数据库链接(当数据库的链接关闭后,对他的操作就不再有效)
       * let res7 = await DB.close();
       **/
      async close() {
          let _this = this;
          return new Promise(async function (resolve, reject) {
              try {
                  //  * 逻辑:先判断数据库是否已经被创建
                  if (_this.db === null) {
                      await _this.getDB();
                  }
                  _this.db.close();
              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })
      }

      /**
       * 作用:删除数据库,如果没传参就删除本身数据库,否则删除指定数据库
       * @method deleteDataBase
       * @for IndexDBOperation
       * @author zl-fire 2021/08/17
       * @example
       * //如果版本升级,请下刷新下页面在执行升级操作
       * let DB = new indexDBOperation("testDB4", 1, objectStores); 
       * 
       * //删除数据库,如果没传参就删除本身数据库,否则删除指定数据库
       * let res8 = await DB.deleteDataBase();//删除后,可能要刷新下才能看到application中indexdb数据库的变化     
       **/
      async deleteDataBase(name) {
          name = name ? name : this.dbName;
          return new Promise(async function (resolve, reject) {
              try {

                  window.indexedDB.deleteDatabase(name);
                  resolve(true);
              }
              catch (err) {
                  resolve(false);
                  console.error(err);
              }
          })

      }
  }

  return IndexDBOperation;

})));






(function() {
    
    var possibleNextData 
    var pageIndex = -1
    var argumentsInStorage = sessionStorage.getItem('arguments')
    /*
    劫持XHR为本地数据库数据
    */
    const xhrOpen = XMLHttpRequest.prototype.open;
if(document.location.href == "https://www.bilibili.com/account/history"){
    XMLHttpRequest.prototype.open = function() {
      if (arguments[1].includes("https://api.bilibili.com/x/web-interface/history")) {
        const xhr = this;
        const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText').get;
        Object.defineProperty(xhr, 'responseText', {
          get: () => {
            let result = getter.call(xhr);
            result_json = JSON.parse(result)
            //result_json.data.list[0].title = '劫持后的数据'
            result_json = possibleNextData||result_json 
            result = JSON.stringify(result_json)
            if(pageIndex==-1){
                result = sessionStorage.getItem('frontTwenty')
                return result;
            }           
            if(argumentsInStorage != ''){
                result = JSON.stringify(possibleNextData)
                return result
            }
   
            
          }
        });
      }
      return xhrOpen.apply(this, arguments);
    };
}
    // Your code here...
    setInterval(()=>{
        newIndex = getPageIndex()-1
        if(newIndex != pageIndex ){
            pageIndex = newIndex 
            updatePossibleNextData()
        }
    },1000)
    setTimeout(()=>{updatePossibleNextData()},3000)

    window.onload = async function(){
      await initMyDB()
      if(document.location.href != "https://www.bilibili.com/account/history"){
      list = await getNewHistory()
      await updateMyDB(list)}
          /*来点UI*/
        GM_addStyle('.btnControl{background-color:#1E90FF;position:relative}')
        GM_addStyle('.controlArea{position:fixed;left:40%;top:40%;width:400px;height:200px;z-index:999;}')
        GM_addStyle(`
        .controlArea input{
            max-width:100px;
           
        }
        .bigBtn{
            
        }
        .controlArea{
            display:none;
            background-color:#6495ED;
        }
        `)
        let showControl = '<button class="btnControl">扩展功能</button>'
        let controlArea = '<div class="controlArea"></div>'
        let timeInput = '<div><input></input>日<input></input>时</div>前的视频'
        let keyWordInput = '<div><input></input>标题关键词搜索</div>'
        let watchFrom = `<div>
        <input type="checkbox" name="from" value="phone" checked>手机端</input>
        <input type="checkbox" name="from" value="web" checked>网页端</input>
        <input type="checkbox" name="from" value="other" checked>其他</input>
        </div>`
        let business = `<div>
        <input type="checkbox" name="business" value="archive" checked>稿件</input>
        <input type="checkbox" name="business" value="pgc" checked>番剧</input>
        <input type="checkbox" name="business" value="live" checked>直播</input>
        <input type="checkbox" name="business" value="article" checked>文章</input>
        </div>`
        let submitBtn = '<div class="bigBtn"><button>提交</button></div>'
        jQuery('.b-head-search').before(showControl,controlArea)
        jQuery('.controlArea').append(timeInput,keyWordInput,watchFrom,business,submitBtn)
        jQuery('.btnControl').click(function(){
            jQuery('.controlArea').toggle()
        })
        jQuery('.bigBtn').click(function(){
            let day = jQuery('.controlArea div:nth-child(1) input:nth-child(1)').val()
            let hour = jQuery('.controlArea div:nth-child(1) input:nth-child(2)').val()
            let keyWord = jQuery('.controlArea div:nth-child(2) input:nth-child(1)').val()
            let fromList = []
            let businessList = []
            jQuery("input[name=from]:checked").each(function(){
                fromList.push(this.value);
            });
            jQuery("input[name=business]:checked").each(function(){
                businessList.push(this.value);
            }); 
            searchDBWithForm(day,hour,keyWord,fromList,businessList)    
        })

    }
    async function searchDBWithForm(day,hour,keyWord,fromList,businessList){
        let [query1,query2] = queryCreate(arguments)
        let res = await searchDB(query1,query2,ps=10000)
        frontTwenty =  packingHistory(res,0)
        sessionStorage.setItem('frontTwenty',JSON.stringify(frontTwenty))
        sessionStorage.setItem('arguments',JSON.stringify(arguments))
        window.location.reload()    
    }
    async function updatePossibleNextData(){
        console.log('i want to update possibleNextData')
       let arg = JSON.parse(argumentsInStorage)
       let [query1,query2] = queryCreate([arg[0],arg[1],arg[2],arg[3],arg[4]])
      let search = await searchDB(query1,query2,ps=10000)
      console.log(search)
      possibleNextData = packingHistory(search,20*((pageIndex?pageIndex:0)+1))
      console.log(possibleNextData)
    }




})();


function queryCreate(args){
    let [day,hour,keyWord,fromList,businessList] = args
    let seconds = 0
    if(day != '') {seconds += 60*60*24*parseInt(day)}
    if(hour != ''){seconds += 60*60*parseInt(hour)}
    console.log(day,hour,keyWord,fromList,businessList)
    let query1 = {name:'viewAtIndex',value:parseInt(+new Date()/1000)-seconds}


    let query2 = []
    if(keyWord != ''){
        query2.push({key:'title',value:keyWord,opt:'include'})
    }
    if(fromList.length>0){
        let arr = []
        if(fromList.includes('phone')){
            arr = arr.concat([1,3,5,7])
        }
        if(fromList.includes('web')){
            arr = arr.concat([2])
        }
        if(fromList.includes('other')){
            arr = arr.concat([4,6,8,9,33,0])
        }
        query2.push({key:'dt',value:arr,opt:'beIncluded'})
    }
    if(businessList.length>0){
        query2.push({key:'business',value:businessList,opt:'beIncluded'})
    }
    console.log('queryCreate Function',args,query1,query2)
    return [query1,query2]
}

/* 
B站的历史记录api来获取数据
*/
async function getNewHistory(){
  var wholeList = []
  var page = await getHistoryApi(0,0,'')
  console.log(page)
  wholeList = wholeList.concat(page.data.list)
  stopFlag = await ifExistInDB(page.data.list)
  for(let i=0;i<20&&!stopFlag;i++){
    cursor = page.data.cursor
    page = await getHistoryApi(cursor.max,cursor.view_at,cursor.business)
    wholeList = wholeList.concat(page.data.list)
    stopFlag = await ifExistInDB(page.data.list)
    if(stopFlag){
      break
    }
  }
  return wholeList
}
function getHistoryApi(max,view_at,business){
let myPromise = new Promise(function(resolve,reject){
  jQuery.support.cors = true;
  jQuery.ajax({
            url: `https://api.bilibili.com/x/web-interface/history/cursor?max=${max}&view_at=${view_at}&business=${business}`,
            type: "get",
            dataType: "json", 
            xhrFields: { withCredentials: true },
            success: function(res) {
                console.log(res)
                resolve(res)
              }
            })
 })
 return myPromise
}
async function ifExistInDB(list){
    let data = {}
    for(let i=0;i<list.length;i++){
      data = await getMyDBById(list[i].history.oid)
      if(data&&data.view_at==list[i].view_at){
        return true
      }
    }
    return false
}
function packingHistory(list,i){
    let listCut = list.slice(i,i+20)
    if(listCut.length==0){
        return []
    }
    let res = {code:0,ttl:1,message:'0',data:{tab:[{type: "archive", name: "视频"}, {type: "live", name: "直播"}, {type: "article", name: "专栏"}]}}
    res.data.list = listCut
    last = listCut[listCut.length-1]
    res.data.cursor = {
      business:last.history.business,
      max:last.history.oid,
      ps:20,
      view_at:last.view_at
    }
    return res
}
/* 
处理一些页面的基本信息
*/
function getPageIndex(){
    return Math.ceil(jQuery('.history-list').children().length/20)
}
/*
通过开源库操作indexDB
*/
async function initMyDB(){
  let IndexDBOpt=window["zl-indexdb"];
  let dbName='bilibiliHistoryDB';
  let dbVersion=1;
  let objectStores = [ 
    {
        objectStoreName: "history",//表名
        type: 1, //0:表示把已存在的删除,然后重新创建。 1:表示如果不存在才重新创建,2:表示只删除,不再进行重新创建
        keyMode:  { keyPath: 'oid' },
        indexs: [ // 创建索引信息
            { indexName: "oidIndex", fieldName: "oid", only: { unique: false } },//索引名,字段名,索引属性值是否唯一
            { indexName: "viewAtIndex", fieldName: "view_at", only: { unique: false } },
            { indexName: "titleIndex", fieldName: "title", only: { unique: false } },
            { indexName: "businessIndex", fieldName: "business", only: { unique: false } },
        ]
    }
  ]
  window.DB = new IndexDBOpt(dbName, dbVersion, objectStores);
}
async function updateMyDB(list){
  for(let i=0;i<list.length;i++){
    list[i] = {...list[i],...list[i].history}
  }
  await DB.updateData(['history'],list)
}
async function getMyDBById(oid){
    let res = await DB.queryBykeypath(['history'],oid)
    return res
}
async function searchDB(query,queryList,ps){
  let res = await DB.queryByIndex(['history'],query,queryList,ps)
  return res
}