您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
B站历史记录持久化存储到浏览器的indexDB数据库中,提供更丰富的搜索功能
// ==UserScript== // @name bilibili history manage B站历史记录持久化并扩展搜索功能 // @namespace http://tampermonkey.net/ // @version 0.5 // @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 // @grant GM_addStyle // @grant GM_getResourceText // @license MIT // @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; case "function": //给定函数操作 flag = filterObj.value(ele) 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.pathname=='/account/history'){ console.log('ok') 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... var whole_data = undefined setInterval(()=>{ newIndex = getPageIndex()-1 if(newIndex != pageIndex ){ pageIndex = newIndex updatePossibleNextData() } },500) setTimeout(async()=>{ await updatePossibleWholeData() await updatePossibleNextData() },3000) setTimeout(()=>{jQuery('#loading').hide()},4000) window.onload = async function(){ await initMyDB() await navigator.storage.persist() if(document.location.pathname != "/account/history"){ list = await getNewHistory() await updateMyDB(list) setInterval(()=>{collect_info()},2000) await updateInfoToDB() } /*来点UI*/ GM_addStyle('.btnControl{background-color:#1E90FF;position:relative}') GM_addStyle('.controlArea{position:fixed;left:50%;top:50%;transform: translate(-50%, -50%);width:450px;height:300px;z-index:999;padding:0 10px;}') GM_addStyle(` #loading{ position:fixed;left:50%;top:20%; color:#7FFFD4;font-size:15px;font-weight:500; } .controlArea input{ max-width:40px; } .controlArea fieldset:nth-child(2) input{ max-width:100px; } fieldset{ padding:5px; margin:10px; } .bigBtn{ height:45px; position:relative; } .bigBtn button{ position: absolute; top: 50%; left: 50%; background: #b4a078; color: white; padding: .3em 1em .5em; border-radius: 3px; box-shadow: 0 0 .5em #b4a078; transform: translate(-50%, -50%); } .controlArea{ display:none; background-color:#6495EDDD; } `) let showControl = '<button class="btnControl">扩展功能</button><a id="loading" >查找数据中</a>' let controlArea = '<div class="controlArea"></div>' let timeInput = `<fieldset> <legend>观看情况筛选</legend> <div>在<input></input>日<input></input>小时前观看的视频</div> <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> </fieldset>` let keyWordInput = ` <fieldset> <legend>视频关键词搜索</legend> <input></input>标题关键词搜索(支持多个关键词,如"空洞|速通")</div><br> <input type="checkbox" name="plus" value="plus" false>关键词搜索范围扩展到标签、up主名称、简介(仅适用于web端观看)</input><br> <input type="checkbox" name="plus" value="commentPlus" false>扩展到热评(仅web端)</input> </fieldset>` let watchFrom = ` <fieldset><legend>视频信息筛选</legend> <div>视频长度大于<input></input>分钟且小于<input></input>分钟/<div> <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> </fieldset>` let submitBtn = '<div class="bigBtn"><button>提交</button></div>' jQuery('.b-head-search').before(showControl,controlArea) jQuery('.controlArea').append(timeInput,keyWordInput,watchFrom,submitBtn) jQuery('.btnControl').click(function(){ jQuery('.controlArea').toggle() }) jQuery('.bigBtn').click(function(){ let day = jQuery('.controlArea fieldset:nth-child(1) div input:nth-child(1)').val() let hour = jQuery('.controlArea fieldset:nth-child(1) div input:nth-child(2)').val() let keyWord = jQuery('.controlArea fieldset:nth-child(2) input:nth-child(2)').val() let durationStart = jQuery('.controlArea fieldset:nth-child(3) div input:nth-child(1)').val() let durationEnd = jQuery('.controlArea fieldset:nth-child(3) div input:nth-child(2)').val() let keyWordPlus = false let commentPlus = false 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); }); jQuery("input[name=plus]:checked").each(function(){ if(this.value == 'plus'){ keyWordPlus = true } if(this.value == 'commentPlus'){ commentPlus = true } }); searchDBWithForm(day,hour,keyWord,fromList,businessList,keyWordPlus,commentPlus,durationStart,durationEnd) }) } /* 提供前20条数据用于初次渲染*/ async function searchDBWithForm(day,hour,keyWord,fromList,businessList,keyWordPlus,commentPlus,durationStart,durationEnd){ let [query1,query2] = queryCreate(arguments) let res = await searchDB(query1,query2,ps=50000) frontTwenty = packingHistory(res,0) sessionStorage.setItem('frontTwenty',JSON.stringify(frontTwenty)) sessionStorage.setItem('arguments',JSON.stringify(arguments)) window.location.reload() } /* 加载接下来的20条数据 ,*/ async function updatePossibleNextData(){ console.log('I want to update possibleNextData with loading,with the whole_data is' + typeof(whole_data)) if(!whole_data){ let arg = JSON.parse(argumentsInStorage) let [query1,query2] = queryCreate([arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[8]]) let search = await searchDB(query1,query2,ps=50000) possibleNextData = packingHistory(search,20*((pageIndex?pageIndex:0)+1)) } else{ possibleNextData = packingHistory(whole_data,20*((pageIndex?pageIndex:0)+1)) } console.log('Next 20 may be',possibleNextData) } /*加载得到数据库中所有符合条件的数据 */ async function updatePossibleWholeData(){ let arg = JSON.parse(argumentsInStorage) let [query1,query2] = queryCreate([arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[8]]) whole_data= await searchDB(query1,query2,ps=500000) console.log('whole_data now loaded successfully',whole_data) } })(); /*生成数据库查询的参数 */ function queryCreate(args){ let [day,hour,keyWord,fromList,businessList,keyWordPlus,commentPlus,durationStart,durationEnd] = 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,keyWordPlus) let query1 = {name:'viewAtIndex',value:parseInt(+new Date()/1000)-seconds} let query2 = [] 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'}) } function plusFunction(ele){ let resFlag = false let checkString = ele.title if(keyWordPlus){checkString = checkString + ele.author_name + ele.tags + ele.des } if(commentPlus){checkString = checkString + ele.comments } if(!keyWord.includes('|')){ if(checkString.includes(keyWord)){ resFlag = true } }else{ let keyArr = keyWord.split('|') for(let i=0;i<keyArr.length;i++){ resFlag = checkString.includes(keyArr[i]) if(!resFlag) {return false} } } return resFlag } if(keyWord != ''){ query2.push({key:'business',value:plusFunction,opt:'function'}) } if(durationStart!=''){ query2.push({key:'duration',value:durationStart*60,opt:'>'}) } if(durationEnd!=''){ query2.push({key:'duration',value:durationEnd*60,opt:'<'}) } 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 } /* B站api调用 */ 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 } /* 防止过多调用api */ 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 } /* 将数据库数据包装成xhr结果的格式 */ 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 } }, ] }, { objectStoreName: "storage",//表名 type: 1, //0:表示把已存在的删除,然后重新创建。 1:表示如果不存在才重新创建,2:表示只删除,不再进行重新创建 keyMode: { keyPath: 'bvid' }, indexs: [ // 创建索引信息 ] } ] 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} let res = await getMyDBById(list[i].oid) if(res){res.view_at = list[i].view_at; list[i]=res} } 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 } /* 在观看视频时,提供页面的信息 */ async function collect_info(){ var bvid = document.location.pathname.match(/BV\w{1,15}/g)[0] var tags = jQuery("meta[name='keywords']").attr("content"); //描述 var des = jQuery('#v_desc > div.desc-info.desc-v2 > span').text() //简介 var comments = jQuery(' div.content-warp > div.root-reply > span').text().slice(1,300) //评论区 await DB.updateData(['storage'],{bvid,tags,des,comments}) } async function updateInfoToDB(){ let res = await DB.queryByIndex(['storage']) for(let i=0;i<res.length;i++){ let flag = updateOneVideo(res[i]) if(flag) await DB.deleteData(["storage"], [res[i].bvid]); } } async function updateOneVideo(e){ let {bvid,tags,des,comments} = e let res = await searchDB({name:'viewAtIndex',value:parseInt(+new Date()/1000)},[{key:'bvid',value:bvid,opt:'=='}],10000) console.log(res) if(res.length){ console.log(res) res = res[0] res.tags = tags res.des = des res.comments = comments await DB.updateData(['history'],res) return true } return false }