- // ==UserScript==
- // @name B站备注
- // @namespace https://github.com/pxoxq
- // @version 0.4.1
- // @description B站用户备注脚本| Bilibili用户备注
- // @license AGPL-3.0-or-later
- // @author pxoxq
- // @match https://*.bilibili.com/**
- // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
- // @grant GM_addElement
- // @grant GM_addStyle
- // @grant window.onurlchange
- // @require https://code.jquery.com/jquery-3.7.1.min.js
- // ==/UserScript==
-
- // -----------ElementGetter----------------
- var elmGetter = function() {
- const win = window.unsafeWindow || document.defaultView || window;
- const doc = win.document;
- const listeners = new WeakMap();
- let mode = 'css';
- let $;
- const elProto = win.Element.prototype;
- const matches = elProto.matches ||
- elProto.matchesSelector ||
- elProto.webkitMatchesSelector ||
- elProto.mozMatchesSelector ||
- elProto.oMatchesSelector;
- const MutationObs = win.MutationObserver ||
- win.WebkitMutationObserver ||
- win.MozMutationObserver;
- function addObserver(target, callback) {
- const observer = new MutationObs(mutations => {
- for (const mutation of mutations) {
- if (mutation.type === 'attributes') {
- callback(mutation.target);
- if (observer.canceled) return;
- }
- for (const node of mutation.addedNodes) {
- if (node instanceof Element) callback(node);
- if (observer.canceled) return;
- }
- }
- });
- observer.canceled = false;
- observer.observe(target, {childList: true, subtree: true, attributes: true});
- return () => {
- observer.canceled = true;
- observer.disconnect();
- };
- }
- function addFilter(target, filter) {
- let listener = listeners.get(target);
- if (!listener) {
- listener = {
- filters: new Set(),
- remove: addObserver(target, el => listener.filters.forEach(f => f(el)))
- };
- listeners.set(target, listener);
- }
- listener.filters.add(filter);
- }
- function removeFilter(target, filter) {
- const listener = listeners.get(target);
- if (!listener) return;
- listener.filters.delete(filter);
- if (!listener.filters.size) {
- listener.remove();
- listeners.delete(target);
- }
- }
- function query(all, selector, parent, includeParent, curMode) {
- switch (curMode) {
- case 'css':
- const checkParent = includeParent && matches.call(parent, selector);
- if (all) {
- const queryAll = parent.querySelectorAll(selector);
- return checkParent ? [parent, ...queryAll] : [...queryAll];
- }
- return checkParent ? parent : parent.querySelector(selector);
- case 'jquery':
- let jNodes = $(includeParent ? parent : []);
- jNodes = jNodes.add([...parent.querySelectorAll('*')]).filter(selector);
- if (all) return $.map(jNodes, el => $(el));
- return jNodes.length ? $(jNodes.get(0)) : null;
- case 'xpath':
- const ownerDoc = parent.ownerDocument || parent;
- selector += '/self::*';
- if (all) {
- const xPathResult = ownerDoc.evaluate(selector, parent, null, 7, null);
- const result = [];
- for (let i = 0; i < xPathResult.snapshotLength; i++) {
- result.push(xPathResult.snapshotItem(i));
- }
- return result;
- }
- return ownerDoc.evaluate(selector, parent, null, 9, null).singleNodeValue;
- }
- }
- function isJquery(jq) {
- return jq && jq.fn && typeof jq.fn.jquery === 'string';
- }
- function getOne(selector, parent, timeout) {
- const curMode = mode;
- return new Promise(resolve => {
- const node = query(false, selector, parent, false, curMode);
- if (node) return resolve(node);
- let timer;
- const filter = el => {
- const node = query(false, selector, el, true, curMode);
- if (node) {
- removeFilter(parent, filter);
- timer && clearTimeout(timer);
- resolve(node);
- }
- };
- addFilter(parent, filter);
- if (timeout > 0) {
- timer = setTimeout(() => {
- removeFilter(parent, filter);
- resolve(null);
- }, timeout);
- }
- });
- }
- return {
- get currentSelector() {
- return mode;
- },
- get(selector, ...args) {
- let parent = typeof args[0] !== 'number' && args.shift() || doc;
- if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
- const timeout = args[0] || 0;
- if (Array.isArray(selector)) {
- return Promise.all(selector.map(s => getOne(s, parent, timeout)));
- }
- return getOne(selector, parent, timeout);
- },
- each(selector, ...args) {
- let parent = typeof args[0] !== 'function' && args.shift() || doc;
- if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
- const callback = args[0];
- const curMode = mode;
- const refs = new WeakSet();
- for (const node of query(true, selector, parent, false, curMode)) {
- refs.add(curMode === 'jquery' ? node.get(0) : node);
- if (callback(node, false) === false) return;
- }
- const filter = el => {
- for (const node of query(true, selector, el, true, curMode)) {
- const _el = curMode === 'jquery' ? node.get(0) : node;
- if (refs.has(_el)) break;
- refs.add(_el);
- if (callback(node, true) === false) {
- return removeFilter(parent, filter);
- }
- }
- };
- addFilter(parent, filter);
- },
- create(domString, ...args) {
- const returnList = typeof args[0] === 'boolean' && args.shift();
- const parent = args[0];
- const template = doc.createElement('template');
- template.innerHTML = domString;
- const node = template.content.firstElementChild;
- if (!node) return null;
- parent ? parent.appendChild(node) : node.remove();
- if (returnList) {
- const list = {};
- node.querySelectorAll('[id]').forEach(el => list[el.id] = el);
- list[0] = node;
- return list;
- }
- return node;
- },
- selector(desc) {
- switch (true) {
- case isJquery(desc):
- $ = desc;
- return mode = 'jquery';
- case !desc || typeof desc.toLowerCase !== 'function':
- return mode = 'css';
- case desc.toLowerCase() === 'jquery':
- for (const jq of [window.jQuery, window.$, win.jQuery, win.$]) {
- if (isJquery(jq)) {
- $ = jq;
- break;
- };
- }
- return mode = $ ? 'jquery' : 'css';
- case desc.toLowerCase() === 'xpath':
- return mode = 'xpath';
- default:
- return mode = 'css';
- }
- }
- };
- }();
-
-
- // ==========防抖函数=============
- function pxoDebounce(func, delay) {
- let timer = null;
- function _debounce(...arg) {
- timer && clearTimeout(timer);
- timer = setTimeout(() => {
- func.apply(this, arg);
- timer = null;
- }, delay);
- }
- return _debounce;
- }
-
- class DateUtils {
- static getCurrDateTimeStr() {
- let date = new Date();
- let year = date.getFullYear();
- let month = date.getMonth() + 1;
- let day = date.getDate();
- let hour = date.getHours();
- let minutes = date.getMinutes();
- let sec = date.getSeconds();
- return `${year}${month}${day}${hour}${minutes}${sec}`;
- }
- }
-
- /* =======================================
- IndexedDB 开始
- ======================================= */
- class MyIndexedDB {
- request;
- db;
- dbName;
- dbVersion;
- store;
-
- constructor(dbName, dbVersion, store) {
- this.dbName = dbName;
- this.dbVersion = dbVersion;
- this.store = store;
- }
-
- // 直接 await MyIndexedDB.create(xxxx) 获取实例对象
- static async create(dbName, dbVersion, store) {
- const obj = new MyIndexedDB(dbName, dbVersion, store);
- obj.db = await obj.getConnection();
- return obj;
- }
-
- // 通过 new MyIndexedDB(xxx) 获取实例对象后,还需要 await initDB() 一下
- async initDB() {
- return new Promise((resolve, rej) => {
- this.getConnection().then((res) => {
- this.db = res;
- resolve(this);
- });
- });
- }
-
- // 控制台打印错误
- consoleError(msg) {
- console.log(`[myIndexedDB]: ${msg}`);
- }
-
- // 获取连接;直接挂到 this.db 上
- // 需要注意,第一次的话,会初始化好 db、 store。但是之后就不会初始化 store,需要判断获取
- getConnection = async () => {
- return new Promise((resolve, rej) => {
- // console.log("连接到数据库: "+`--${this.dbName}-- --${this.dbVersion}--`)
- // 打开数据库,没有则新建
- this.request = indexedDB.open(this.dbName, this.dbVersion);
- this.request.onerror = (e) => {
- console.error(
- `连接 ${this.dbName} [IndexedDB] 失败. version: [${this.dbVersion}]`,
- e
- );
- };
-
- this.request.onupgradeneeded = async (event) => {
- const db = event.target.result;
- await this.createAndInitStore(
- db,
- this.store.conf.storeName,
- this.store.data,
- this.store.conf.uniqueIndex,
- this.store.conf.normalIndex
- );
- // await this.createAndInitStore(db);
- resolve(db);
- };
-
- this.request.onsuccess = (e) => {
- const db = e.target.result;
- resolve(db);
- };
- });
- };
-
- // 创建存储桶并初始化数据,默认是自增id
- async createAndInitStore(
- db = this.db,
- storeName = "",
- datas = [],
- uniqueIndex = [],
- normalIndex = []
- ) {
- if (!storeName || !datas) return;
- return new Promise((resolve, rej) => {
- // 自增id
- const store = db.createObjectStore(storeName, {
- keyPath: "id",
- autoIncrement: true,
- });
- // 设置两类索引
- uniqueIndex.forEach((item) => {
- store.createIndex(item, item, { unique: true });
- });
- normalIndex.forEach((item) => {
- store.createIndex(item, item, { unique: false });
- });
-
- // 初始填充数据
- store.transaction.oncomplete = (e) => {
- const rwStore = this.getCustomRWstore(storeName, db);
- datas.forEach((item) => {
- rwStore.add(item);
- });
- resolve(0);
- };
- });
- }
-
- // 获取所有数据
- async getAllDatas() {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- const req = rwStore.getAll();
- req.onsuccess = (e) => {
- resolve(req?.result);
- };
- });
- }
-
- // 添加一条数据
- async addOne(item) {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- const req = rwStore.add(item);
- req.onsuccess = () => {
- resolve(true);
- };
- req.onerror = () => {
- rej(false);
- };
- });
- }
-
- // 根据uid获取一条数据
- async getOne(id = 0) {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- const req = rwStore.get(id);
- req.onsuccess = () => {
- resolve(req.result);
- };
- });
- }
-
- // 查询一条数据, 字段column包含value子串
- async queryOneLike(column, value) {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- rwStore.openCursor().onsuccess = (event) => {
- const cursor = event.target.result;
- if (cursor) {
- const item = { ...cursor.value };
- if (item[column] && item[column].indexOf(value) > -1) {
- item.id = cursor.key;
- resolve(item);
- }
- cursor.continue();
- } else {
- resolve(false);
- }
- };
- });
- }
-
- // 查询一条数据, 字段column等于value
- async queryOneEq(column, value) {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- rwStore.openCursor().onsuccess = (event) => {
- const cursor = event.target.result;
- if (cursor) {
- const item = { ...cursor.value };
- if (item[column] == value) {
- item.id = cursor.key;
- resolve(item);
- }
- cursor.continue();
- } else {
- resolve(false);
- }
- };
- });
- }
-
- // 更新一条数据
- async updateOne(item) {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- const req = rwStore.put(item);
- req.onsuccess = () => {
- resolve(true);
- };
- req.onerror = (e) => {
- console.log(req);
- console.log(e);
- rej(false);
- };
- });
- }
-
- // 删除一条数据
- async delOne(id) {
- return new Promise((resolve, rej) => {
- const rwStore = this.getCustomRWstore();
- const req = rwStore.delete(id);
- req.onsuccess = () => {
- resolve(true);
- };
- req.onerror = (e) => {
- rej(false);
- };
- });
- }
-
- // 获取读写权限的存储桶 store。默认是this上挂的storename
- getCustomRWstore(storeName = this.store.conf.storeName, db = this.db) {
- return db.transaction(storeName, "readwrite").objectStore(storeName);
- }
-
- // 状态值为 done 时表示连接上了。db挂到了this上
- requestState() {
- return this.request.readyState;
- }
- isReady() {
- return this.request.readyState == "done";
- }
-
- // 关闭数据库链接
- closeDB() {
- this.db && this.db.close();
- }
-
- static setDBVersion(version) {
- localStorage.setItem("pxoxq-dbv", version);
- }
-
- static getDBVersion() {
- const v = localStorage.getItem("pxoxq-dbv");
- return v;
- }
- }
- /* =======================================
- IndexedDB 结束
- ======================================= */
-
- /* =======================================
- 配置数据库表 结束
- ======================================= */
- class ConfigDB {
- static simplifyIdx = false;
- static autoWideMode = false;
- static playerHeight = 700;
- static memoMode = 0;
- static importMode = 0;
-
- static Keys = {
- simplifyIdx: "simplifyIdx",
- autoWideMode: "autoWideMode",
- playerHeight: "playerHeight",
- memoMode: "memoMode",
- importMode: "importMode",
- };
-
- static dbConfig = {
- DB_NAME: "bilibili_pxo",
- DB_V: MyIndexedDB.getDBVersion() ?? 2,
- store: {
- conf: {
- storeName: "conf",
- },
- },
- };
-
- static async connnectDB(func) {
- const myDb = await MyIndexedDB.create(
- this.dbConfig.DB_NAME,
- this.dbConfig.DB_V,
- this.dbConfig.store
- );
- const result = await func(myDb);
- myDb.closeDB();
- return result;
- }
-
- static async getConf() {
- const res = await this.connnectDB(async (db) => {
- const rrr = db.getOne("bconf");
- return rrr;
- });
- return res;
- }
-
- static async updateConf(conf) {
- const res = await this.connnectDB(async (db) => {
- const rrr = await db.updateOne(conf);
- return rrr;
- });
- return res;
- }
-
- static async updateOne(key, val) {
- const res = await this.connnectDB(async (db) => {
- const config = await this.getConf();
- config[key] = val;
- const rrr = db.updateOne(config);
- return rrr;
- });
- return res;
- }
-
- static async updateSimplifyIdx(val) {
- return await this.updateOne(this.Keys.simplifyIdx, val);
- }
-
- static async updateAutoWideMode(val) {
- return await this.updateOne(this.Keys.autoWideMode, val);
- }
-
- static async updatePlayerHeight(val) {
- return await this.updateOne(this.Keys.playerHeight, val);
- }
-
- static async updateMemoMode(val) {
- return await this.updateOne(this.Keys.memoMode, val);
- }
-
- static async updateImportMode(val) {
- return await this.updateOne(this.Keys.importMode, val);
- }
- }
- /* =======================================
- 配置数据库表 结束
- ======================================= */
-
- /*=========================================
- 哔站昵称功能对IndexedDB 进行的封装 开始
- ==========================================*/
- class BilibiliMemoDB {
- static dbConfig = {
- DB_NAME: "bilibili_pxo",
- DB_V: MyIndexedDB.getDBVersion() ?? 2,
- store: {
- conf: {
- storeName: "my_friends",
- },
- },
- };
-
- static async connectDB(func) {
- const db = await MyIndexedDB.create(
- this.dbConfig.DB_NAME,
- this.dbConfig.DB_V,
- this.dbConfig.store
- );
- const result = await func(db);
- db.closeDB();
- return result;
- }
-
- static async addOne(uid) {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.addOne(uid);
- return rrr;
- });
- return res;
- }
-
- static async getOne(uid) {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.getOne(uid);
- return rrr;
- });
- return res;
- }
-
- static async queryEq(column, value) {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.queryOneEq(column, value);
- return rrr;
- });
- return res;
- }
-
- static async queryLike(column, value) {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.queryOneLike(column, value);
- return rrr;
- });
- return res;
- }
-
- static async getOneByBid(bid) {
- const res = await this.queryEq("bid", bid);
- return res;
- }
-
- static async getAll() {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.getAllDatas();
- return rrr;
- });
- return res;
- }
-
- static async updateByIdAndMemo(id, memo) {
- const item = await this.getOne(id);
- item.nick_name = memo;
- const res = await this.updateOne(item);
- return res;
- }
-
- static async addOrUpdateMany(datas, ignore_mode = true) {
- for (const data of datas) {
- const _item = await this.getOneByBid(data.bid);
- if (_item) {
- if (!ignore_mode) {
- _item.nick_name = data.nick_name;
- _item.bname = data.bname;
- await this.updateOne(_item);
- }
- } else {
- if (!data.bid) continue;
- else {
- const _itm = {
- bid: data.bid,
- bname: data.bname,
- nick_name: data.nick_name,
- };
- await this.addOne(_itm);
- }
- }
- }
- return 1;
- }
-
- static async updateOne(item) {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.updateOne(item);
- return rrr;
- });
- return res;
- }
-
- static async delByBid(bid) {
- const _item = this.getOneByBid(bid);
- if (_item) {
- return await this.delOne(_item.id);
- } else {
- return false;
- }
- }
-
- static async delOne(id) {
- const res = await this.connectDB(async (db) => {
- const rrr = await db.delOne(id);
- return rrr;
- });
- }
- }
- /*=========================================
- 哔站昵称功能对IndexedDB 进行的封装 结束
- ==========================================*/
-
- /* =======================================
- 所有数据库表初始化 开始
- ======================================= */
- class DBInit {
- static dbName = "bilibili_pxo";
- static dbV = "1";
- static storeList = [
- {
- name: "B站备注表",
- conf: {
- uniqueIndex: ["bid"],
- normalIndex: ["nick_name"],
- DB_NAME: "bilibili_pxo",
- storeName: "my_friends",
- },
- data: [
- ],
- },
- {
- name: "配置项表",
- conf: {
- DB_NAME: "bilibili_pxo",
- storeName: "conf",
- },
- data: [
- {
- id: "bconf",
- simplifyIdx: false,
- autoWideMode: false,
- playerHeight: 700,
- memoMode: 0,
- importMode: 0,
- },
- ],
- },
- ];
-
- static async initAllDB() {
- for (let idx = 0; idx < this.storeList.length; idx++) {
- const myDb = await MyIndexedDB.create(
- this.dbName,
- idx * 1 + 1,
- this.storeList[idx]
- );
- MyIndexedDB.setDBVersion(idx * 1 + 1);
- setTimeout(() => {
- myDb.closeDB();
- }, 100);
- }
- }
- }
- /* =======================================
- 所有数据库表初始化 结束
- ======================================= */
-
- /* =======================================
- 菜单UI部分 结束
- ======================================= */
- class BMenu {
- static menuStyle = `
- @media (max-width: 1190px){
- div#pxoxq-b-menu .pxoxq-menu-wrap{
- display: block;
- overflow-y: scroll;
- scrollbar-width: thin;
- height: 340px;
- }
- #pxoxq-b-menu .pxoxq-menu-wrap::-webkit-scrollbar{
- width: 5px;
- }
- #pxoxq-b-menu .pxoxq-menu-wrap::-webkit-scrollbar-thumb{
- background-color: #FC6296;
- border-radius: 6px;
- }
- }
-
- /* 菜单最外层 */
- #pxoxq-b-menu{
- text-align: initial;
- font-size: 15px;
- z-index: 999;
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0px;
- height: 340px;
- padding: 8px 10px;
- background-color: white;
- transition: all .24s linear;
- border-top: 1px solid #c3c3c3;
- }
- #pxoxq-b-menu.pxoxq-hide{
- padding: unset;
- height: 0;
- }
- #pxoxq-b-menu button{
- background-color: #FC6296;
- border: 1px solid white;
- color: white;
- font-size: 13px;
- padding: 1px 6px;
- border-radius: 5px;
- }
- #pxoxq-b-menu button:hover{
- border: 1px solid #c5c5c5;
- }
- #pxoxq-b-menu button:active{
- opacity: .7;
- }
- #pxoxq-b-menu .pxoxq-tag{
- position: absolute;
- width: 24px;
- text-align: center;
- color: white;
- padding: 0px 6px;
- left: 2px;
- top: -21px;
- background-color: #FC6296;
- border-radius: 4px 4px 0 0;
- user-select: none;
- transition: all .3s linear;
- }
-
- #pxoxq-b-menu .pxoxq-tag:hover{
- letter-spacing: 3px;
- }
- #pxoxq-b-menu .pxoxq-tag:active{
- opacity: .5;
- }
- #pxoxq-b-menu .pxoxq-menu-wrap{
- display: flex;
- }
- #pxoxq-b-menu .pxoxq-menu-col {
- height: 340px;
- min-height: 340px;
- overflow-y: scroll;
- scrollbar-width: thin;
- }
- #pxoxq-b-menu .pxoxq-menu-col::-webkit-scrollbar{
- width: 5px;
- }
- #pxoxq-b-menu .pxoxq-menu-col::-webkit-scrollbar-thumb{
- background-color: #FC6296;
- border-radius: 6px;
- }
- #pxoxq-b-menu .pxoxq-menu-wrap .pxoxq-setting-wrap{
- flex-grow: 1;
- }
- #pxoxq-b-menu .setting-row:not(.import-row) {
- padding: 4px 0;
- display: flex;
- gap: 3px;
- }
- #pxoxq-b-menu .setting-row .pxoxq-label{
- font-weight: 600;
- color: rgb(100, 100, 100);
- }
- #pxoxq-b-menu .pxoxq-setting-wrap .setting-box{
- display: flex;
- gap: 22px;
- }
- #pxoxq-b-menu .setting-row .pxoxq-inline-label{
- display: inline-block;
- margin-right: 20px;
- }
- #pxoxq-player-h{
- width: 300px;
- }
- #pxoxq-b-menu .setting-row.memo-mode-row{
- display: flex;
- padding-bottom: 10px;
- }
- #pxoxq-b-menu .setting-item-import{
- display: flex;
- margin-bottom: 10px;
- }
- #pxoxq-b-menu .frd-import-btn{
- margin-left: 40px;
- }
- /* 右边部分 */
- #pxoxq-b-menu .pxoxq-menu-wrap .pxoxq-frd-wrap{
- border-left: 1px solid #d1d1d1;
- padding-left: 10px;
- }
- #pxoxq-b-menu .pxoxq-right-header{
- display: flex;
- padding-bottom: 6px;
- margin-bottom: 5px;
- border-bottom: 1px dotted #b2b2b2;
- }
- #pxoxq-b-menu .pxoxq-right-header .pxoxq-right-title{
- font-size: 18px;
- flex-grow: 1;
- text-align: center;
- font-weight: 600;
- color: #4b4b4b;
- }
- /* 右边表格部分 */
- #pxoxq-b-menu .pxoxq-frd-tab{
- white-space: nowrap;
- height: 340px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-tbody{
- height: 280px;
- overflow-y: scroll;
- scrollbar-width: thin;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-tbody::-webkit-scrollbar{
- width: 4px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-tbody::-webkit-scrollbar-thumb{
- background-color: #FC6296;
- border-radius: 5px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-thead{
- font-weight: 600;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-tr{
- border-bottom: 1px solid #dadada;
- /* text-align: center; */
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-tr .pxoxq-cell{
- display: inline-block;
- text-align: center;
- font-size: 14px;
- padding: 2px 3px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-col-1{
- width: 30px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-col-2{
- width: 120px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-col-3{
- width: 120px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-col-4{
- width: 180px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-col-5{
- width: 100px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-memo-ipt{
- outline: none;
- border: unset;
- text-align: center;
- padding: 2px 3px;
- }
- #pxoxq-b-menu .pxoxq-frd-tab .pxoxq-memo-ipt.active{
- border-bottom: 1px solid #ffb3e3;
- color:#FC6296;
- }
- `;
-
- static wrapId = "pxoxq-b-menu";
-
- static saveDelay = 200;
-
- static importJson = "";
-
- static init() {
- this.injectMemuHtml();
- this.injectStyle();
- }
-
- static injectMemuHtml() {
- // 参数初始化
- const wrap = $("#pxoxq-b-menu");
- ConfigDB.getConf().then(async (_conf) => {
- const friendTab = await this.genFriendTab();
- const leftMenu = `
- <h3>备注模块设置</h3>
- <div class="setting-row memo-mode-row">
- <div class="pxoxq-label pxoxq-inline-label">备注显示模式</div>
- <div class="pxoxq-radio-item">
- <input class="pxoxq-memo-mode" ${
- _conf.memoMode == 0 ? "checked" : ""
- } value="0" type="radio" name="memo-mode" id="nope">
- <label for="nope">关闭备注功能</label>
- </div>
- <div class="pxoxq-radio-item">
- <input class="pxoxq-memo-mode" ${
- _conf.memoMode == 1 ? "checked" : ""
- } value="1" type="radio" name="memo-mode" id="nick-first">
- <label for="nick-first">昵称(备注)</label>
- </div>
- <div class="pxoxq-radio-item">
- <input class="pxoxq-memo-mode" ${
- _conf.memoMode == 2 ? "checked" : ""
- } value="2" type="radio" name="memo-mode" id="memo-first">
- <label for="memo-first">备注(昵称)</label>
- </div>
- <div class="pxoxq-radio-item">
- <input class="pxoxq-memo-mode" ${
- _conf.memoMode == 3 ? "checked" : ""
- } value="3" type="radio" name="memo-mode" id="just-memo">
- <label for="just-memo">备注</label>
- </div>
- </div>
- <div class="setting-row import-row">
- <div class="pxoxq-setting-item setting-item-import">
- <div class="pxoxq-label pxoxq-inline-label">导入数据</div>
- <input class="pxoxq-import-mode" ${
- _conf.importMode == 0 ? "checked" : ""
- } id="ignore-same" value="0" type="radio" checked name="import-mode">
- <label for="ignore-same">跳过重复项</label>
- <input class="pxoxq-import-mode" ${
- _conf.importMode == 1 ? "checked" : ""
- } id="update-same" value="1" type="radio" name="import-mode">
- <label for="update-same">覆盖重复项</label>
- <button class="frd-import-btn" type="button">导入</button>
- </div>
- <div class="pxoxq-setting-item">
- <textarea placeholder="请输入数据..." name="pxoxq-frd-json" id="pxoxq-frd-json" cols="80" rows="10"></textarea>
- </div>
- </div>
- `;
- if (wrap && wrap.length > 0) {
- this.flushConfTab();
- this.flushFrdTab();
- } else {
- const _html = `
- <div id="pxoxq-b-menu" class="pxoxq-hide">
- <div class="pxoxq-tag">:)</div>
- <div class="pxoxq-menu-wrap">
- <div class="pxoxq-menu-col pxoxq-setting-wrap">
- ${leftMenu}
- </div>
-
- <div class="pxoxq-frd-wrap">
- <div class="pxoxq-right-header">
- <div class="pxoxq-right-title">昵称数据</div>
- <button class="pxoxq-export-frd-btn" type="button">导出当前数据</button>
- </div>
- <div class="pxoxq-tab-wrap">
- <div class="pxoxq-frd-tab">
- <div class="pxoxq-tr pxoxq-thead">
- <div class="pxoxq-cell pxoxq-col-1">ID</div>
- <div class="pxoxq-cell pxoxq-col-2">BilibiliID</div>
- <div class="pxoxq-cell pxoxq-col-3">昵称</div>
- <div class="pxoxq-cell pxoxq-col-4">备注</div>
- <div class="pxoxq-cell pxoxq-col-5">操作</div>
- </div>
- <div class="pxoxq-tbody">
- ${friendTab}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- `;
- $("body").append(_html);
- this.addListener();
- }
- });
- }
-
- static async genFriendTab() {
- const friends = await BilibiliMemoDB.getAll();
- let _html = "";
-
- for (const friend of friends) {
- _html += `
- <div class="pxoxq-tr pxoxq-frd-row pxoxq-frd-${friend.id}">
- <div class="pxoxq-cell pxoxq-col-1">${friend.id}</div>
- <div class="pxoxq-cell pxoxq-col-2" title="${friend.bid}">${friend.bid}</div>
- <div class="pxoxq-cell pxoxq-col-3">${friend.bname}</div>
- <div class="pxoxq-cell pxoxq-col-4">
- <input class="pxoxq-memo-ipt pxoxq-memo-ipt-${friend.id}" data-id="${friend.id}" type="text" value="${friend.nick_name}" readonly>
- </div>
- <div class="pxoxq-cell pxoxq-col-5">
- <button class="pxoxq-memo-edit-btn pxoxq-memo-edit-btn-${friend.id}" data-id="${friend.id}" type="button">编辑</button>
- <button class="pxoxq-memo-del-btn" data-id="${friend.id}" type="button">删除</button>
- </div>
- </div>
- `;
- }
- return _html;
- }
-
- static flushFrdTab() {
- this.genFriendTab().then((_tabHtml) => {
- $("#pxoxq-b-menu .pxoxq-frd-tab .pxoxq-tbody").html(_tabHtml);
- });
- }
-
- static flushConfTab() {
- ConfigDB.getConf().then((_conf) => {
- const mmRadios = $(".pxoxq-memo-mode");
- for (const item of mmRadios) {
- if (item.value == _conf.memoMode) {
- item.checked = true;
- } else {
- item.checked = false;
- }
- }
- const modeRadios = $(".pxoxq-import-mode");
- for (const item of modeRadios) {
- if (item.value == _conf.memoMode) {
- item.checked = true;
- } else {
- item.checked = false;
- }
- }
- });
- }
-
- static injectStyle() {
- GM_addStyle(this.menuStyle);
- }
-
- static addListener() {
- const wrapIdSelector = `#${this.wrapId}`;
-
- // 面板展开、折叠
- $("body").on(
- "click",
- wrapIdSelector + " .pxoxq-tag",
- pxoDebounce(this.toggleMenuHandler, this.saveDelay)
- );
-
- // 备注模式选框
- $("body").on(
- "click",
- ".pxoxq-memo-mode",
- pxoDebounce(this.memoModeHandler, this.saveDelay)
- );
-
- // 导入数据模式
- $("body").on(
- "click",
- ".pxoxq-import-mode",
- pxoDebounce(this.importModeHandler, this.saveDelay)
- );
-
- // 导入数据
- $("body").on("click", ".frd-import-btn", this.importFriendHandler);
-
- // 导出数据
- $("body").on(
- "click",
- ".pxoxq-export-frd-btn",
- pxoDebounce(this.exportFrdHandler, this.saveDelay * 2)
- );
-
- // 双击比编辑
- $("body").on("dblclick", "input.pxoxq-memo-ipt", this.editMemoHandler);
-
- // 编辑按钮编辑
- $("body").on(
- "click",
- ".pxoxq-memo-edit-btn",
- pxoDebounce(this.editMemoHandler, this.saveDelay)
- );
-
- // 保存昵称(更新
- $("body").on(
- "click",
- ".pxoxq-memo-save-btn",
- pxoDebounce(this.updateMemoHandler, this.saveDelay)
- );
-
- // 删除备注
- $("body").on(
- "click",
- ".pxoxq-memo-del-btn",
- pxoDebounce(this.delMemoHandler, this.saveDelay)
- );
- }
-
- // 折叠、打开面板
- static toggleMenuHandler() {
- $("#pxoxq-b-menu").toggleClass("pxoxq-hide");
- // 刷新面板数据
- if (
- document
- .getElementById("pxoxq-b-menu")
- .classList.value.indexOf("pxoxq-hide") < 0
- ) {
- BMenu.flushConfTab();
- BMenu.flushFrdTab();
- } else {
- }
- }
-
- static delMemoHandler() {
- const id = parseInt(this.dataset.id);
- const memo = $(".pxoxq-memo-ipt-" + id).val();
- if (confirm(`是否要删除备注【${memo}】?`)) {
- BilibiliMemoDB.delOne(id);
- $(".pxoxq-frd-tab .pxoxq-frd-" + id).remove();
- }
- }
-
- static updateMemoHandler() {
- const id = this.dataset.id;
- let editBtn = $(".pxoxq-memo-edit-btn-" + id);
- const memoInput = $(".pxoxq-memo-ipt-" + id);
- // 都需编辑按钮复原
- $(editBtn).text("编辑");
- $(editBtn).removeClass("pxoxq-memo-save-btn");
- memoInput[0].readOnly = true;
- $(memoInput).removeClass("active");
- const val = memoInput[0].value;
- BilibiliMemoDB.updateByIdAndMemo(parseInt(id), val);
- }
-
- static editMemoHandler() {
- const id = this.dataset.id;
- // pxoxq-memo-ipt-2
- let editBtn = $(".pxoxq-memo-edit-btn-" + id);
- const memoInput = $(".pxoxq-memo-ipt-" + id);
- if (!memoInput[0].readOnly) {
- return;
- }
-
- // 都需要给编辑按钮变个东西
- $(editBtn).text("保存");
- $(editBtn).addClass("pxoxq-memo-save-btn");
-
- memoInput[0].readOnly = false;
- $(memoInput).addClass("active");
- }
-
- // 导出数据
- static exportFrdHandler() {
- BilibiliMemoDB.getAll().then((_datas) => {
- const json_str = JSON.stringify(_datas);
- const dataURI =
- "data:text/plain;charset=utf-8," + encodeURIComponent(json_str);
- const link = document.createElement("a");
- link.href = dataURI;
- link.download = `${DateUtils.getCurrDateTimeStr()}.txt`;
- link.click();
- });
- }
-
- // 导入数据
- static importFriendHandler() {
- const textNode = $("#pxoxq-frd-json");
- const val = $(textNode).val();
- if (!/\S+/.test(val)) return;
- ConfigDB.getConf().then(async (_conf) => {
- try {
- const datas = JSON.parse(val);
- if (Array.isArray(datas)) {
- const ignore_mode = _conf.importMode == 1 ? false : true;
- await BilibiliMemoDB.addOrUpdateMany(datas, ignore_mode);
- BMenu.flushFrdTab();
- alert("导入成功");
- } else {
- throw Error("数据格式错误!");
- }
- } catch (e) {
- alert("导入失败:" + e);
- }
- });
- }
-
- static importModeHandler() {
- ConfigDB.updateImportMode(this.value);
- }
-
- static memoModeHandler() {
- MemoGlobalConf.mode = this.value;
- ConfigDB.updateMemoMode(this.value);
- }
- }
- /* =======================================
- 菜单UI部分 结束
- ======================================= */
-
- /*............................................................................................
- Memo部分 开始
- ............................................................................................*/
- /* =============================================
- 一些配置参数 开始
- =============================================*/
- const memoClassPrefix = "pxo-memo";
- const MemoGlobalConf = {
- mode: 1, // 【模式】 0:昵称替换成备注; 1:昵称(备注); 2:(备注)昵称
- myFriends: [], // 好友信息列表
- memoClassPrefix,
- fansInputBlurDelay: 280, // 输入框防抖延迟
- fansInputBlurTimer: "",
- fansLoopTimer: "",
- memoStyle: `
- .content .be-pager li{
- z-index: 999;
- position: relative;
- }
- .pxo-frd{
- color: #3fb9ffd4;
- font-weight:600;
- letter-spacing: 2px;
- border: 1px solid #ff88a973;
- border-radius: 6px;
- background: #ffa9c1a4;
- margin-top:-2px;
- padding: 2px 5px;}
- .h #h-name {
- background: #ffffffbd;
- padding: 5px 10px;
- border-radius: 6px;
- letter-spacing: 3px;
- line-height: 22px;
- font-size: 20px;
- box-shadow: 1px 1px 2px 2px #ffffff40;
- border: 1px solid #fff;
- color: #e87b99;
- overflow: hidden;
- transition:all .53s linear;
- }
- .h #h-name.hide{
- width:0px;
- padding:0px;
- height:0px;
- border:none;
- }
- .h .homepage-memo-input{
- border: none;
- outline:none;
- overflow:hidden;
- padding: 5px 6px;
- border-bottom:2px solid #ff0808;
- width: 230px;
- font-size: 17px;
- line-height: 22px;
- vertical-align: middle;
- background: #ffffffbd;;
- color: #f74979;
- font-weight:600;
- margin-right: 8px;
- transition:all .53s linear;
- border-radius: 5px 5px 0 0;
- }
- .h .homepage-memo-input.hide{
- width: 0px;
- padding: 0;
- border:none;
-
- }
- .${memoClassPrefix}-setting-box{
- display: inline-block;
- vertical-align:top;
- margin-top:-2px;
- line-height:20px;
- margin-left:18px;
- }
- .${memoClassPrefix}-setting-box div.btn{
- padding:2px 5px;
- user-select:none;
- display:inline-block;
- overflow: hidden;
- letter-spacing:2px;
- background:#e87b99cc;
- border:none;
- border-radius:5px;
- color:white;
- margin:0 3px;
- transition:all .53s linear;
- }
- .${memoClassPrefix}-setting-box div.btn.hide{
- height: 0px;
- width: 0px;
- opacity: 0.2;
- padding:0px;
- }
- .${memoClassPrefix}-setting-box div.btn:hover{
- box-shadow: 1px 1px 2px 1px #80808024;
- outline: .5px solid #e87b99fc;
-
- }
- .${memoClassPrefix}-setting-box input{
- border: none;
- outline:none;
- overflow:hidden;
- padding: 2px 3px;
- border-bottom:1px solid #c0c0c0;
- width: 190px;
- font-size: 16px;
- line-height: 18px;
- color: #ff739a;
- font-weight:600;
- vertical-align:top;
- transition:all .25s linear;
- }
- .${memoClassPrefix}-setting-box input.hide{
- width:0px;
- padding:0px;
- }
- `,
- };
- /* =============================================
- 一些配置参数 结束
- =============================================*/
-
- /* =============================================
- 定制日志输出 开始
- =============================================*/
- class MyLog {
- static prefix = "[BilibiliMemo]";
-
- static genMsg(msg, type = "") {
- return `${this.prefix} ${type}: ${msg}`;
- }
-
- static error(msg) {
- console.error(this.genMsg(msg, "error"));
- }
-
- static warn(msg) {
- console.warn(this.genMsg(msg, "warn"));
- }
-
- static success(msg) {
- console.info(this.genMsg(msg, "success"));
- }
- static log(msg, ...arg) {
- console.log(this.genMsg(msg), ...arg);
- }
- }
- /* =============================================
- 定制日志输出 结束
- =============================================*/
-
- /* =============================================
- html 注入部分 开始
- =============================================*/
- class BilibiliMemoInjectoin {
- // 个人主页 替换 以及初始化
- static async injectUserHome(bid) {
- const user = await this.getUserInfoByBid(bid);
- elmGetter.get('#h-name').then(uname => {
- if(!uname) return
- let nickName = uname.innerHTML;
- if(user){
- $(uname).html(this.getNameStr(nickName, user.nick_name));
- $(uname).attr("data-id", user.id);
- }
- $(uname).attr("data-bid", bid);
- $(uname).attr("data-bname", nickName);
- // 添加备注模块
- const inputNode = `<input data-bname="${nickName}" data-bid="${bid}" class='${MemoGlobalConf.memoClassPrefix}-input hide homepage-memo-input'/>`
- $(uname).after(inputNode)
- })
- }
- // 个人主页 替换 更新
- static injectOneHomePage(user) {
- if (user) {
- const nickName = $(".h #h-name").attr("data-bname");
- $("#h-name").html(this.getNameStr(nickName, user.nick_name));
- $("#h-name").attr("data-id", user.id);
- }
- }
-
- // 个人关注、粉丝页替换 以及初始化
- static injectFanList() {
- elmGetter.each(".relation-list > li > div.content > a", async (user) => {
- try {
- let url = user.href;
- let uid = url.split("/")[3];
- const cPrefix = MemoGlobalConf.memoClassPrefix;
- if (!$(user.children).attr("data-bid")) {
- const userInfo = await this.getUserInfoByBid(uid);
- let nickName = $(user.children).html();
- $(user.children).attr("data-bname", nickName);
- $(user.children).attr("data-bid", uid);
- if (userInfo) {
- $(user.children).html(this.getNameStr(nickName, userInfo.nick_name));
- $(user.children).attr("data-id", userInfo.id);
- $(user).addClass("pxo-frd");
- $(user).addClass("pxo-frd-" + uid);
- }
- // 注入备注模块代码
- const memoBlock = `<div class='${cPrefix}-setting-${uid} ${cPrefix}-setting-box'>
- <input data-bname="${nickName}" data-bid='${uid}' class='${cPrefix}-input-${uid} hide'/>
- <div data-bid='${uid}' class='${cPrefix}-btn-bz btn bz-btn-${uid}'>备注</div>
- <div data-bid='${uid}' class='${cPrefix}-btn-cfm op-btn-${uid} btn cfm-btn-${uid} hide'>确认</div>
- <div data-bid='${uid}' class='${cPrefix}-btn-cancel op-btn-${uid} btn cancel-btn-${uid} hide'>取消</div>
- <div data-bid='${uid}' class='${cPrefix}-btn-del op-btn-${uid} btn del-btn-${uid} hide'>清除备注</div>
- </div>`;
- $(user).after(memoBlock);
- }
- } catch (e) {
- MyLog.error(e);
- }
- });
- }
-
- // 个人关注、粉丝页替换 单个
- static injectOneFans(user, userANode) {
- if (user && userANode) {
- const nickName = $(userANode.children).attr("data-bname");
- $(userANode.children).html(this.getNameStr(nickName, user.nick_name));
- $(userANode.children).attr("data-id", user.id);
- $(userANode).addClass("pxo-frd");
- $(userANode).addClass("pxo-frd-" + user.bid);
- }
- }
-
- static replaceMemo(uri) {
- /*
- uri 一共有几种形式:
- https://space.bilibili.com/28563843/fans/follow
- https://space.bilibili.com/28563843/fans/follow?tagid=-1
- https://space.bilibili.com/28563843/fans/fans
- https://space.bilibili.com/472118057/?spm_id_from=333.999.0.0
-
- 1、换页是页内刷新,需要给页码搞个点击事件
- 2、个人页形式跟其他不太一样
- */
- const uType = this.judgeUri(uri);
- // MyLog.success(`类型是:[${uType}] ${uri}`);
- switch (uType) {
- case "-1":
- MyLog.warn("Uri获取失败");
- break;
- case "+1": //粉丝关注
- BilibiliMemoInjectoin.injectFanList();
- break;
- default: // 个人主页
- BilibiliMemoInjectoin.injectUserHome(uType);
- }
- }
-
- static judgeUri(uri) {
- /*
- -1 uri为空
- +x +1:粉丝、关注 | +* 后续
- xxxx 纯数字,个人主页
- */
- if (!uri) return "-1";
-
- const uri_parts = uri.split("/"); // 0-https 1-'' 2-host 3-bid 4-fans/query 5-fans/follow
- // 这是 space 域下的处理,之后可能扩展到其他更多页面模块
- if (uri_parts[2] && "space.bilibili.com" == uri_parts[2]) {
- // 粉丝、关注列表 【归一类,处理方式一样】
- if (
- uri_parts.length > 4 &&
- uri_parts[4] == "fans" &&
- /(?=fans)|(?=follow)/.test(uri_parts[5])
- ) {
- return "+1";
- }
- // 个人主页
- else {
- return uri_parts[3].split("?")[0];
- }
- }
- return "-1";
- }
-
- // 根据bid获取用户信息 直接从数据库取吧
- static async getUserInfoByBid(bid) {
- const res = await BilibiliMemoDB.getOneByBid(bid);
- return res;
- }
-
- // 根据昵称、备注获取最终显示名
- static getNameStr(nickName, remark) {
- // span 标签用于判断是否已经替换过
- if (nickName.indexOf("<span>") > 0) {
- return nickName;
- }
- let res = "";
- if (MemoGlobalConf.mode == 1) {
- res = remark;
- } else if (MemoGlobalConf.mode == 2) {
- res = nickName + `(${remark})`;
- } else if (MemoGlobalConf.mode == 3) {
- res = remark + `(${nickName})`;
- }
- return res + "<span>";
- }
-
- // 注入css样式到头部
- static injectCSS(css) {
- GM_addStyle(css);
- }
- }
- /* =============================================
- html 注入部分 结束
- =============================================*/
-
- /* =============================================
- 通用函数部分 开始
- =============================================*/
- class BMemoUtils {
- // 关注、粉丝列表页 备注编辑模块 编辑模式 / 正常模式
- static toggleMemoBox(bid, editMode = true) {
- if (editMode) {
- $(`.btn.op-btn-${bid}`).removeClass("hide");
- $(`.${MemoGlobalConf.memoClassPrefix}-input-${bid}`).removeClass("hide");
- $(`.btn.bz-btn-${bid}`).addClass("hide");
- } else {
- $(`.btn.op-btn-${bid}`).addClass("hide");
- $(`.${MemoGlobalConf.memoClassPrefix}-input-${bid}`).addClass("hide");
- $(`.btn.bz-btn-${bid}`).removeClass("hide");
- }
- }
-
- // 个人主页 编辑模式 / 正常模式
- static toggleUserHomeEditMode(editMode = true) {
- if (editMode) {
- $(".h #h-name").addClass("hide");
- $(".homepage-memo-input").removeClass("hide");
- } else {
- $(".h #h-name").removeClass("hide");
- $(".homepage-memo-input").addClass("hide");
- }
- }
-
- // 个人空间主页 编辑模式初始化
- static homePageEditModeHandler(bid) {
- this.toggleUserHomeEditMode();
- const inputNode = $(".homepage-memo-input")[0];
- const bName = $(inputNode).attr("data-bname");
- $(inputNode).focus();
- BilibiliMemoDB.getOneByBid(bid).then((user) => {
- if (user) {
- $(inputNode).val(user.nick_name);
- } else {
- $(inputNode).val(bName);
- }
- });
- }
-
- // 个人空间主页 编辑确认
- static homePageSetMemoHandler(bid) {
- const inputNode = $(".homepage-memo-input")[0];
- const bName = $(inputNode).attr("data-bname");
- const val = $(inputNode).val();
- const val_reg = /\S.*\S/;
- if (val && val_reg.test(val)) {
- const memo = val_reg.exec(val)[0];
- BilibiliMemoDB.getOneByBid(bid).then(async (user) => {
- if (user) {
- if (memo != user.nick_name) {
- user.nick_name = memo;
- user.bname = bName;
- await BilibiliMemoDB.updateOne(user);
- BilibiliMemoInjectoin.injectOneHomePage(user);
- }
- this.toggleUserHomeEditMode(false);
- } else {
- if (memo != bName) {
- user = {
- bid,
- nick_name: memo,
- bname: bName,
- };
- await BilibiliMemoDB.addOne(user);
- user = await BilibiliMemoDB.getOneByBid(bid);
- BilibiliMemoInjectoin.injectOneHomePage(user);
- }
- this.toggleUserHomeEditMode(false);
- }
- });
- }
- }
-
- // 删除备注
- static delMemoHandler(bid) {
- BilibiliMemoDB.getOneByBid(bid).then(async (_item) => {
- if (_item) {
- if (confirm(`是否删除备注【${_item.nick_name}】?`)) {
- await BilibiliMemoDB.delOne(_item.id);
- $("a.pxo-frd-" + bid).removeClass("pxo-frd");
- const nameSpan = $("a.pxo-frd-" + bid + " span.fans-name");
- $(nameSpan).text(nameSpan[0].dataset.bname);
- }
- }
- });
- }
-
- // 粉丝、关注页 编辑模式初始化
- static editModeHandler(bid) {
- const inputNode = $(`.${MemoGlobalConf.memoClassPrefix}-input-${bid}`)[0];
- BilibiliMemoDB.getOneByBid(bid).then((user) => {
- const val = $(inputNode).val();
- if (!/\S+/.test(val)) {
- if (user) {
- $(inputNode).val(user.nick_name);
- } else {
- $(inputNode).val($(inputNode).attr("data-bname"));
- }
- }
- });
- this.toggleMemoBox(bid);
- $(inputNode).focus();
- }
-
- // 粉丝、关注页编辑确认
- static setMemoHandler(bid) {
- const inputNode = $(`.${MemoGlobalConf.memoClassPrefix}-input-${bid}`)[0];
- const val = $(inputNode).val();
- const val_reg = /\S.*\S/;
- const bName = $(inputNode).attr("data-bname");
- if (val_reg.test(val)) {
- const memo = val_reg.exec(val)[0];
- const userANode = $(inputNode).parent().siblings("a")[0];
- BilibiliMemoInjectoin.getUserInfoByBid(bid).then(async (user) => {
- if (user) {
- if (user.nick_name != memo) {
- user.nick_name = memo;
- user.bname = bName;
- await BilibiliMemoDB.updateOne(user);
- BilibiliMemoInjectoin.injectOneFans(user, userANode);
- }
- this.toggleMemoBox(bid, false);
- } else {
- if (memo != bName) {
- user = {
- bid,
- nick_name: memo,
- bname: bName,
- };
- await BilibiliMemoDB.addOne(user);
- user = await BilibiliMemoDB.getOneByBid(bid);
- BilibiliMemoInjectoin.injectOneFans(user, userANode);
- }
- this.toggleMemoBox(bid, false);
- }
- });
- }
- }
- }
- /* =============================================
- 通用函数部分 结束
- =============================================*/
- /*-----------------初始化 开始-----------------*/
- async function BilibiliMemoInit() {
- // 注入样式
- BilibiliMemoInjectoin.injectCSS(MemoGlobalConf.memoStyle);
-
- // 个人主页双击修改事件
- $('body').on(
- 'dblclick',
- `.h #h-name`,
- function(event){
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.homePageEditModeHandler(bid)
- }
- )
-
- // 个人主页搜索框失去焦点事件
- $('body').on(
- 'focusout',
- '.homepage-memo-input',
- function(event){
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.homePageSetMemoHandler(bid)
- }
- )
-
- // 粉丝、关注页 备注按钮点击事件:
- $("body").on(
- "click",
- `.${MemoGlobalConf.memoClassPrefix}-setting-box div.${MemoGlobalConf.memoClassPrefix}-btn-bz`,
- function (event) {
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.editModeHandler(bid)
- }
- );
-
- // 删除备注按钮点击事件
- $("body").on(
- "click",
- `.${MemoGlobalConf.memoClassPrefix}-setting-box div.${MemoGlobalConf.memoClassPrefix}-btn-del`,
- function (event) {
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.delMemoHandler(bid)
- }
- )
-
- // 粉丝、关注页确认按钮点击事件
- $("body").on(
- "click",
- `.${MemoGlobalConf.memoClassPrefix}-setting-box .${MemoGlobalConf.memoClassPrefix}-btn-cfm`,
- function (event) {
- clearTimeout(MemoGlobalConf.fansInputBlurTimer)
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.setMemoHandler(bid)
- }
- );
-
- // 粉丝、关注页取消按钮点击事件
- $("body").on(
- "click",
- `.${MemoGlobalConf.memoClassPrefix}-setting-box .${MemoGlobalConf.memoClassPrefix}-btn-cancel`,
- function (event) {
- clearTimeout(MemoGlobalConf.fansInputBlurTimer)
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.toggleMemoBox(bid, false)
-
- })
-
- // 粉丝、关注页输入框市区焦点事件
- $("body").on(
- "focusout",
- `.${MemoGlobalConf.memoClassPrefix}-setting-box input`,
- function (event) {
- clearTimeout(MemoGlobalConf.fansInputBlurTimer)
- MemoGlobalConf.fansInputBlurTimer = setTimeout(()=>{
- const bid = event.currentTarget.dataset.bid;
- BMemoUtils.toggleMemoBox(bid, false)
- }, MemoGlobalConf.fansInputBlurDelay)
-
- })
- }
- /*-----------------初始化 结束-----------------*/
-
- /*........................................................................................................................................
- Memo部分 结束
- ........................................................................................................................................*/
-
- async function flushConf() {
- const _conf = await ConfigDB.getConf();
- MemoGlobalConf.mode = _conf.memoMode;
- return true;
- }
-
- /*+++++++++++++++++++++++++++++++++++++
- 主程序初始化 开始
- +++++++++++++++++++++++++++++++++++++*/
- async function bilibiliCustomInit() {
- if (!MyIndexedDB.getDBVersion()) {
- await DBInit.initAllDB();
- }
- // 从数据库获取数据,刷新配置参数
- await flushConf();
- BMenu.init();
- if (MemoGlobalConf.mode == 0) return;
- const uri = window.location.href;
- BilibiliMemoInit().then((r) => {
- BilibiliMemoInjectoin.replaceMemo(uri);
- });
- }
- /*+++++++++++++++++++++++++++++++++++++
- 主程序初始化 结束
- +++++++++++++++++++++++++++++++++++++*/
-
-
- function toNewOne(){
- const newScriptUrl = 'https://scriptcat.org/zh-CN/script-show-page/3059'
- let timeDiff = 2 * 24 * 60 * 60 * 1e3
- const never = localStorage.getItem('neverShow')
- let neverEnd = localStorage.getItem('neverEndTime')
- const curr = new Date().getTime()
- neverEnd = Number(neverEnd)
- if(never == 1 && neverEnd && curr - neverEnd < timeDiff){
- return
- }
- const dog = document.createElement('dialog')
- dog.style.cssText = `border:none;border-radius:8px;padding:18px;border: 5px solid #E16689;position:fixed;top: 20vh;margin: 0 auto;`
- document.body.appendChild(dog)
- const h = document.createElement('h2')
- h.style.cssText = `color:#E16689;text-align:center;`
- dog.appendChild(h)
- h.innerText = 'B站备注 -- 全新版本来啦!!!!'
- const content = `<div style="font-size: 18px;line-height: 40px;">新版本已完成适配,支持导入这个版本导出的数据(可以从这个版本导出数据,然后导入到新版本)。<br>
- 迁移完数据后,可以卸载当前版本,只保留新版本。<br>
- 新版本在这里安装:<a target="_blank" style="color:blue;outline:none;" href="${newScriptUrl}">${newScriptUrl}</a>
- </div>`
- dog.insertAdjacentHTML('beforeend', content)
- const btnD = document.createElement('div')
- dog.appendChild(btnD)
- const cfm = document.createElement('button')
- const neverShow = document.createElement('button')
- btnD.appendChild(cfm)
- btnD.appendChild(neverShow)
- btnD.style.cssText = `text-align: right;`
- cfm.innerText = '已知晓'
- neverShow.innerText = '不再展示'
- cfm.style.cssText = `margin-left: 20px;color:white;font-weight:600;font-size: 18px;border:2px solid pink;outline:none;background: #E16689;border-radius:4px;padding: 8px 10px;`
- neverShow.style.cssText= cfm.style.cssText
- dog.showModal()
- cfm.addEventListener('click', function(){
- dog.close()
- })
- neverShow.addEventListener('click', function(){
- dog.close()
- localStorage.setItem('neverShow', 1)
- localStorage.setItem('neverEndTime', new Date().getTime())
- })
- }
-
- (function () {
- toNewOne()
- bilibiliCustomInit().then((res) => {
- console.log("init over");
- });
- })();