// ==UserScript== // @name Bangumi 社区助手 preview // @version 0.0.3 // @namespace b38.dev // @description 社区助手预览版 // @author 神戸小鳥 @vickscarlet // @license MIT // @include /^https?://(bgm\.tv|chii\.in|bangumi\.tv)\/* // @run-at document-start // @downloadURL none // ==/UserScript== (async () => { /** * 返回一个函数,该函数在调用时会等待上一个调用完成后再执行 * @param {Function} fn */ function callWhenDone(fn) { let done = true; return async () => { if (!done) return; done = false; await fn(); done = true; } } /** * 立刻调用一次函数并返回函数本体 * @param {Function} fn */ function callNow(fn) { fn(); return fn; } // DOM API HELPERS START /** * 设置属性 * @typedef {Record} Props * @param {Element} element 元素 * @param {Props} props 属性 */ function setProps(element, props) { if (!props || typeof props !== 'object') return element; for (const [key, value] of Object.entries(props)) { if (typeof value === 'boolean') { element[key] = value; continue; } if (key === 'class') addClass(element, value); else if (key === 'style' && typeof value === 'object') setStyle(element, value); else element.setAttribute(key, value); } return element; } /** * 添加类名 * @param {Element} element 元素 * @param {string} value 类名 */ function addClass(element, value) { element.classList.add(...[value].flat()); return element; } /** * 设置样式 * @typedef {Record} Styles * @param {Element} element 元素 * @param {Styles} styles */ function setStyle(element, styles) { for (let [k, v] of Object.entries(styles)) { if (v && typeof v === 'number' && !['zIndex', 'fontWeight'].includes(k)) v += 'px'; element.style[k] = v; } return element; } /** * @typedef {[string, Props | AppendParams, ...AppendParams[]]} CreateParams * @typedef {CreateParams | string | Element} AppendParams */ /** * @param {string} name HTML标签 * @param {Props | AppendParams} props 属性 * @param {...AppendParams} childrens 子元素 */ function create(name, props, ...childrens) { if (name === 'svg') return createSVG(name, props, ...childrens); const element = document.createElement(name); if (props === undefined) return element; if (Array.isArray(props) || props instanceof Node || typeof props !== 'object') return append(element, props, ...childrens); return append(setProps(element, props), ...childrens) } /** * @param {Element} element 元素 * @param {...AppendParams} childrens 子元素 */ function append(element, ...childrens) { if (element.name === 'svg') return appendSVG(element, ...childrens); for (const child of childrens) { if (Array.isArray(child)) element.append(create(...child)); else if (child instanceof Node) element.appendChild(child); else element.append(document.createTextNode(child)); } return element; } /** * @param {string} name HTML标签 * @param {Props | AppendParams} props 属性 * @param {...AppendParams} childrens 子元素 */ function createSVG(name, props, ...childrens) { const element = document.createElementNS('http://www.w3.org/2000/svg', name); if (name === 'svg') element.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); if (props === undefined) return element; if (Array.isArray(props) || props instanceof Node || typeof props !== 'object') return append(element, props, ...childrens); return appendSVG(setProps(element, props), ...childrens) } /** * @param {Element} element 元素 * @param {...AppendParams} childrens 子元素 */ function appendSVG(element, ...childrens) { for (const child of childrens) { if (Array.isArray(child)) element.append(createSVG(...child)); else if (child instanceof Node) element.appendChild(child); else element.append(document.createTextNode(child)); } return element; } /** * @template T * @template R * @param {Iterable} list * @param {(item: T, index: number, list: Iterable) => R} fn * @param {R[]} ret * @return {R[]} */ function map(list, fn, ret = []) { let i = 0; for (const item of list) { const result = fn(item, i, list); ret.push(result); i++; } return ret } /** * @param {Element} element 元素 */ function removeAllChildren(element) { while (element.firstChild) element.removeChild(element.firstChild); return element; } // DOM API HELPERS END // indexedDB cache /** * @typedef {{ * name: string * keyPath: string | string[] * unique?: boolean * }} Index * @typedef {{ * collection: string * options?: IDBObjectStoreParameters * indexes?: Index[] * }} CollectionOptions * @typedef {{ * dbName: string * version: number * collections: CollectionOptions[] * }} Options */ class Collection { /** * @param {Database} master * @param {CollectionOptions} param1 */ constructor(master, { collection, options, indexes }) { this.#master = master; this.#collection = collection; this.#options = options this.#indexes = indexes } /** @type {Database} */ #master; #collection; #options #indexes get collection() { return this.#collection } get options() { return this.#options } get indexes() { return this.#indexes } /** * @param {(store:IDBObjectStore)=>Promise>} handler * @param {Parameters[2]} [mode=null] * @returns {ReturnType} */ async transaction(handler, mode) { return this.#master.transaction( this.#collection, async store => { const request = await handler(store) return new Promise((resolve, reject) => { request.addEventListener('error', e => reject(e)) request.addEventListener('success', () => resolve(request.result) ) }) }, mode ) } /** * @template T * @param {IDBValidKey | IDBKeyRange} key * @param {string} [index=''] * @returns {Promise} */ async get(key, index = '') { return this.transaction(store => (index ? store.index(index) : store).get(key)); } /** * @returns {Promise} */ async put(data) { return this.transaction(store => store.put(data), 'readwrite').then(_ => true); } /** * @returns {Promise} */ async clear() { return this.transaction(store => store.clear(), 'readwrite').then(_ => true); } } class Database { /** * @param {Options} param0 */ constructor({ dbName, version, collections }) { this.#dbName = dbName; this.#version = version; for (const options of collections) { this.#collections.set(options.collection, new Collection(this, options)); } } #dbName; #version; /** @type {Map} */ #collections = new Map(); /** @type {IDBDatabase} */ #db; async init() { this.#db = await new Promise((resolve, reject) => { const request = window.indexedDB.open(this.#dbName, this.#version); request.addEventListener('error', event => reject(event.target.error)); request.addEventListener('success', event => resolve(event.target.result)); request.addEventListener('upgradeneeded', event => { for (const c of this.#collections.values()) { const { collection, options, indexes } = c let store if (!request.result.objectStoreNames.contains(collection)) store = request.result.createObjectStore(collection, options) else store = request.transaction.objectStore(collection) if (!indexes) continue for (const { name, keyPath, unique } of indexes) { if (store.indexNames.contains(name)) continue store.createIndex(name, keyPath, { unique }) } } }); }); return this; } /** * @param {string} collection * @param {(store:IDBObjectStore)=>Promise} handler * @param {'readonly'|'readwrite'} mode * @returns {ReturnType} */ async transaction(collection, handler, mode = 'readonly') { return new Promise(async (resolve, reject) => { const transaction = this.#db.transaction(collection, mode); const store = transaction.objectStore(collection); const result = await handler(store); transaction.addEventListener('error', e => reject(e)); transaction.addEventListener('complete', () => resolve(result)); }); } /** * @template T * @param {string} collection * @param {Parameters[0]} key * @param {Parameters[1]} index * @returns {ReturnType>} */ async get(collection, key, index) { return this.#collections.get(collection).get(key, index); } /** * @param {string} collection * @returns {ReturnType} */ async put(collection, data) { return this.#collections.get(collection).put(data); } /** * @param {string} collection * @returns {ReturnType} */ async clear(collection) { return this.#collections.get(collection).clear(); } /** * @returns {Promise} */ async clearAll() { for (const c of this.#collections.values()) await c.clear(); return true; } } const db = new Database({ dbName: 'VCommunity', version: 3, collections: [ { collection: 'values', options: { keyPath: 'id' }, indexes: [{ name: 'id', keyPath: 'id', unique: true }] }, { collection: 'users', options: { keyPath: 'id' }, indexes: [{ name: 'id', keyPath: 'id', unique: true }] }, { collection: 'images', options: { keyPath: 'uri' }, indexes: [{ name: 'uri', keyPath: 'uri', unique: true }] } ] }); /** * @typedef {{ * id: string, * block?: boolean, * names: Set, * }} User */ /** @type {User} */ let user; // Events const listeners = new Map(); function on(event, listener) { if (!listeners.has(event)) listeners.set(event, new Set()); listeners.get(event).add(listener); } function emit(event, ...args) { if (!listeners.has(event)) return; for (const listener of listeners.get(event).values()) listener(...args); } function off(event, listener) { if (!listeners.has(event)) return; listeners.get(event).delete(listener); } const menu = new class { constructor() { const blockBtn = create('li', ['a', { href: 'javascript:void(0)' }, svg('block'), '屏蔽发言']) const editBtn = create('li', ['a', { href: 'javascript:void(0)' }, svg('edit'), '编辑']) const usednameBtn = create('li', ['a', { href: 'javascript:void(0)' }, svg('clock'), '曾用名']) this.#element = create('ul', blockBtn, usednameBtn, editBtn); blockBtn.addEventListener('click', () => this.#block()); editBtn.addEventListener('click', () => this.#edit()); usednameBtn.addEventListener('click', () => this.#usedname()); } #element; #id; id(id) { this.#id = id; return this.#element; } async #block() { const id = this.#id; if (!confirm('确定要屏蔽吗?')) return; const data = await db.get('users', id) || { id }; data.block = true; await db.put('users', data); } async #edit() { console.debug('edit', this.#id) } async #usedname() { const id = this.#id const names = await this.#getUsedNames(id); const data = await db.get('users', id) || { id }; data.names = data.names.union(new Set(names)); await db.put('users', data); } async #getUsedNames(id, ret = [], page = 1) { const res = await fetch(`/user/${id}/timeline?type=say&ajax=1&page=${page}`); const html = await res.text(); const names = Array.from(html.matchAll(/从 \(?.*?)\<\/strong\> 改名为/g), m => m.groups.from); ret.push(...names); if (!html.includes('>下一页 ››')) return ret; return this.#getUsedNames(id, ret, page + 1); } } function hoverUserListener() { const helper = create('div', { id: 'community-helper', class: 'borderNeue' }); const title = create('div', { class: 'title' }, 'Bangumi 社区助手'); const container = create('div', { class: 'user-info' }) append(helper, title, container); let last; const showUser = async (id, currentName) => { if (last === id) return; last = id; /** @type {User} */ const data = await db.get('users', id); if (!data || last !== id) return; removeAllChildren(container); append(container, ['fieldset', ['legend', '用户名'], ['ul', ['li', currentName]]]); data.names.delete(currentName); if (data.names.size) { const used = ['ul', ...map(data.names, name => ['li', name])] append(container, ['fieldset', ['legend', '曾用名'], used]); } } let timeout; on('hover', async ({ id, currentName }) => { clearTimeout(timeout); timeout = setTimeout(() => showUser(id, currentName), 50); }); on('leave', () => { clearTimeout(timeout) }); window.addEventListener('resize', callNow(() => { const r = document.querySelector('#robot_balloon > .inner') const c = document.querySelector('.columns > .column:not(#columnSubjectHomeB,#columnHomeB):last-child') let inner; if (window.innerWidth < 640) inner = r; else inner = c || r; inner.append(helper); })) } function dockInject() { const dock = document.querySelector('#dock'); if (!dock) return; let n, o; o = dock.querySelector('#showrobot'); o.style.display = 'none'; n = create('a', { class: ['showrobot', 'svg-icon'], href: 'javascript:void(0)' }, svg('robot'), ['span', '春菜']); n.addEventListener('click', () => chiiLib.ukagaka.toggleDisplay()); o.parentElement.append(n); o = dock.querySelector('#toggleTheme'); o.style.display = 'none'; n = create('a', { class: ['toggleTheme', 'svg-icon'], href: 'javascript:void(0)' }, svg('light'), ['span', '开关灯']); n.addEventListener('click', () => chiiLib.ukagaka.toggleTheme()); o.parentElement.append(n); o.parentElement.classList.remove('last'); o = null; dock.querySelectorAll('li').forEach(e => { if (!o || o.children.length < e.children.length) o = e; }); o.querySelectorAll('a').forEach(a => { let icon; switch (a.innerText) { case '提醒': icon = 'notify'; break; case '短信': icon = 'message'; break; case '设置': icon = 'setting'; break; case '登出': icon = 'logout'; break; } if (icon) { const title = a.innerText removeAllChildren(a); a.classList.add('svg-icon'); append(a, svg(icon), ['span', title]); } o.parentElement.insertBefore(create('li', a), o); }) o.remove(); } function userNid() { try { return CHOBITS_UID } catch (e) { console.error('获取 CHOITS_UID 失败', e); return null; } } function userSeek() { const dockA = document.querySelector('#dock li.first a'); if (dockA) { const nid = userNid(); const name = dockA.innerText; const id = dockA.href.split('/').pop(); user = { nid, id, name }; return true; } return false; } function parseHasCommentList() { const commentList = document.querySelector('#comment_list') if (!commentList) return; const e = commentList.parentElement; if (!e) return; e.classList.add('topic-box'); const first = e.querySelector(':scope>.clearit') const replyWrapper = e.querySelector('#reply_wrapper'); if (replyWrapper) { e.querySelector('#sliderContainer')?.style.setProperty('display', 'none', 'important'); const getSwitch = () => { const raw = localStorage.getItem('sickyReplySwitch') if (!raw) return 1; return Number(raw) || 0; } const swBtn = create('div', { class: 'switch', switch: Number(localStorage.getItem('sickyReplySwitch')) || 1 }); swBtn.addEventListener('click', callNow(sw => { const s = sw ? sw() : getSwitch(); swBtn.setAttribute('switch', s); const sicky = (() => { const q = e.querySelector('.sicky-reply') if (q) return q; const c = create('div', { class: 'sicky-reply' }); e.insertBefore(c, first || commentList); return c; })(); if (s) { sicky.style.visibility = 'visible'; sicky.append(replyWrapper); } else { sicky.style.visibility = 'hidden'; e.append(replyWrapper); } }).bind(this, () => { const s = (getSwitch() + 1) % 2; localStorage.setItem('sickyReplySwitch', s) return s; })); append(replyWrapper, swBtn); } const handlerClearit = async clearit => { const id = clearit.getAttribute('data-item-user') if (!id) return; const data = await db.get('users', id) || { id, names: new Set() }; const inner = clearit.querySelector('.inner'); const icon = create('a', { class: ['icon', 'svg-icon'], href: 'javascript:void(0)' }, svg('mark')); const action = create('div', { class: ['action', 'dropdown', 'vcomm'] }, icon); icon.addEventListener('mouseenter', () => append(action, menu.id(id))); const actionBox = clearit.querySelector('.post_actions'); actionBox.insertBefore(action, actionBox.lastElementChild); if (!data.names) data.names = new Set(); const currentName = inner.querySelector('strong > a').innerText; if (!data.names.has(currentName)) { data.names.add(currentName); await db.put('users', data); } clearit.addEventListener('mouseenter', e => { emit('hover', { id, currentName }) e.stopPropagation(); }); clearit.addEventListener('mouseleave', () => emit('leave', { id, currentName })); if (data.block) { const btn = create('div', { class: 'svg-box' }, svg('expand')) const tip = create('span', { class: 'svg-box' }, svg('collapse'), '已折叠') const tips = create('div', { class: ['inner', 'tips'] }, tip, btn); btn.addEventListener('click', () => tips.replaceWith(inner)); inner.replaceWith(tips); } } if (first) handlerClearit(first); const owner = e.querySelector('.postTopic')?.getAttribute('data-item-user'); for (const comment of Array.from(commentList.children)) { const floor = comment.getAttribute('data-item-user') if (floor === owner) comment.classList.add('owner'); handlerClearit(comment) comment.querySelectorAll('.clearit').forEach(clearit => { const user = clearit.getAttribute('data-item-user'); if (user === owner) clearit.classList.add('owner'); else if (user === floor) clearit.classList.add('floor'); handlerClearit(clearit); }); } } function svg(type, size = 14) { const fill = 'currentColor'; const stroke = fill; const viewBox = '0 0 16 16' const baseParams = { width: size, height: size } switch (type) { case 'collapse': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M10.896 2H8.75V.75a.75.75 0 0 0-1.5 0V2H5.104a.25.25 0 0 0-.177.427l2.896 2.896a.25.25 0 0 0 .354 0l2.896-2.896A.25.25 0 0 0 10.896 2ZM8.75 15.25a.75.75 0 0 1-1.5 0V14H5.104a.25.25 0 0 1-.177-.427l2.896-2.896a.25.25 0 0 1 .354 0l2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25Zm-6.5-6.5a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z' }] ]; case 'expand': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'm8.177.677 2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25a.75.75 0 0 1-1.5 0V4H5.104a.25.25 0 0 1-.177-.427L7.823.677a.25.25 0 0 1 .354 0ZM7.25 10.75a.75.75 0 0 1 1.5 0V12h2.146a.25.25 0 0 1 .177.427l-2.896 2.896a.25.25 0 0 1-.354 0l-2.896-2.896A.25.25 0 0 1 5.104 12H7.25v-1.25Zm-5-2a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z' }] ]; case 'logout': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M2 2.75C2 1.784 2.784 1 3.75 1h2.5a.75.75 0 0 1 0 1.5h-2.5a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h2.5a.75.75 0 0 1 0 1.5h-2.5A1.75 1.75 0 0 1 2 13.25Zm10.44 4.5-1.97-1.97a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l1.97-1.97H6.75a.75.75 0 0 1 0-1.5Z' }] ]; case 'setting': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z' }] ]; case 'message': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z' }] ]; case 'light': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z' }] ]; case 'notify': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M8 16a2 2 0 0 0 1.985-1.75c.017-.137-.097-.25-.235-.25h-3.5c-.138 0-.252.113-.235.25A2 2 0 0 0 8 16ZM3 5a5 5 0 0 1 10 0v2.947c0 .05.015.098.042.139l1.703 2.555A1.519 1.519 0 0 1 13.482 13H2.518a1.516 1.516 0 0 1-1.263-2.36l1.703-2.554A.255.255 0 0 0 3 7.947Zm5-3.5A3.5 3.5 0 0 0 4.5 5v2.947c0 .346-.102.683-.294.97l-1.703 2.556a.017.017 0 0 0-.003.01l.001.006c0 .002.002.004.004.006l.006.004.007.001h10.964l.007-.001.006-.004.004-.006.001-.007a.017.017 0 0 0-.003-.01l-1.703-2.554a1.745 1.745 0 0 1-.294-.97V5A3.5 3.5 0 0 0 8 1.5Z' }] ]; case 'info': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z' }] ]; case 'unnotify': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'm4.182 4.31.016.011 10.104 7.316.013.01 1.375.996a.75.75 0 1 1-.88 1.214L13.626 13H2.518a1.516 1.516 0 0 1-1.263-2.36l1.703-2.554A.255.255 0 0 0 3 7.947V5.305L.31 3.357a.75.75 0 1 1 .88-1.214Zm7.373 7.19L4.5 6.391v1.556c0 .346-.102.683-.294.97l-1.703 2.556a.017.017 0 0 0-.003.01c0 .005.002.009.005.012l.006.004.007.001ZM8 1.5c-.997 0-1.895.416-2.534 1.086A.75.75 0 1 1 4.38 1.55 5 5 0 0 1 13 5v2.373a.75.75 0 0 1-1.5 0V5A3.5 3.5 0 0 0 8 1.5ZM8 16a2 2 0 0 1-1.985-1.75c-.017-.137.097-.25.235-.25h3.5c.138 0 .252.113.235.25A2 2 0 0 1 8 16Z' }] ]; case 'robot': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M5.75 7.5a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5a.75.75 0 0 1 .75-.75Zm5.25.75a.75.75 0 0 0-1.5 0v1.5a.75.75 0 0 0 1.5 0v-1.5Z' }], ['path', { d: 'M6.25 0h2A.75.75 0 0 1 9 .75V3.5h3.25a2.25 2.25 0 0 1 2.25 2.25V8h.75a.75.75 0 0 1 0 1.5h-.75v2.75a2.25 2.25 0 0 1-2.25 2.25h-8.5a2.25 2.25 0 0 1-2.25-2.25V9.5H.75a.75.75 0 0 1 0-1.5h.75V5.75A2.25 2.25 0 0 1 3.75 3.5H7.5v-2H6.25a.75.75 0 0 1 0-1.5ZM3 5.75v6.5c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75v-6.5a.75.75 0 0 0-.75-.75h-8.5a.75.75 0 0 0-.75.75Z' }] ]; case 'mark': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M1 7.775V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.752 1.752 0 0 1 1 7.775Zm1.5 0c0 .066.026.13.073.177l6.25 6.25a.25.25 0 0 0 .354 0l5.025-5.025a.25.25 0 0 0 0-.354l-6.25-6.25a.25.25 0 0 0-.177-.073H2.75a.25.25 0 0 0-.25.25ZM6 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z' }] ]; case 'edit': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z' }] ]; case 'link': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'm7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z' }] ]; case 'star': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z' }] ]; case 'clock': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z' }] ]; case 'block': return ['svg', { viewBox: '1 1 22 22', fill: 'none', stroke, ...baseParams }, ['path', { 'stroke-width': 2, 'stroke-linecap': "round", 'stroke-linejoin': "round", d: 'M5.63605 5.63603L18.364 18.364M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z' }] ]; case 'edit': return ['svg', { viewBox, fill, ...baseParams }, ['path', { d: 'M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z' }] ]; default: return null; } }; (async () => { append(document.head, ['style', [ `html { --color-bangumi: #fd8a96; --color-bangumi-a20: #fd8a9620; --color-bangumi-a40: #fd8a9640; --color-bangumi-a80: #fd8a9680; --color-white: #ffffff; --color-white-ad0: #ffffffd0; --color-black: #000000; --color-black-a0f: #0000000f; --color-black-a20: #00000020; --color-black-a60: #00000060; --color-black-ad0: #000000d0; --color-yellow: #f9c74c; --color-yellow-a20: #f9c74c20; --color-yellow-a40: #f9c74c40; --color-yellow-a80: #f9c74c80; --color-purple: #a54cf9; --color-purple-a20: #a54cf920; --color-purple-a40: #a54cf940; --color-purple-a80: #a54cf980; --color-blue: #02a3fb; --color-blue-a20: #02a3fb20; --color-blue-a40: #02a3fb40; --color-blue-a80: #02a3fb80; --color-green: #95eb89; --color-green-a20: #95eb8920; --color-green-a40: #95eb8940; --color-green-a80: #95eb8980; }`, `html { --color-base: #ffffff; --color-base-a0f: #ffffff0f; --color-base-a20: #ffffff20; --color-base-a40: #ffffff40; --color-base-a80: #ffffff80; --color-base-ad0: #ffffffd0; --color-gray-1: #e8e8e8; --color-gray-2: #cccccc; --color-gray-3: #aaaaaa; --color-gray-4: #969696; --color-gray-11: #cccccc; --color-bangumi-2: #AB515D; --color-bangumi-2-a20: #AB515D20; --color-bangumi-2-a40: #AB515D40; --color-bangumi-2-a80: #AB515D80; }`, `html[data-theme='dark'] { --color-base: #000000; --color-base-a0f: #0000000f; --color-base-a20: #00000020; --color-base-a40: #00000040; --color-base-a80: #00000080; --color-base-ad0: #000000d0; --color-gray-1: #444444; --color-gray-2: #555555; --color-gray-3: #6a6a6a; --color-gray-4: #888888; --color-gray-11: #cccccc; --color-bangumi-2: #ffb6bd; --color-bangumi-2-a20: #ffb6bd20; --color-bangumi-2-a40: #ffb6bd40; --color-bangumi-2-a80: #ffb6bd80; }`, `html { --color-dock-sp: var(--color-gray-2); --color-switch-border: var(--color-gray-2); --color-switch-on: var(--color-green); --color-switch-off: var(--color-gray-4); --color-switch-bar-border: var(--color-white); --color-switch-bar-inner: var(--color-gray-11); --color-hover: var(--color-blue); --color-icon-btn-bg: var(--color-bangumi-a40); --color-icon-btn-color: var(--color-white); --color-reply-sp: var(--color-gray-1); --color-reply-tips: var(--color-gray-3); --color-reply-normal-top: var(--color-bangumi); --color-reply-normal-bg: var(--color-bangumi-a20); --color-reply-normal-shadow: var(--color-bangumi-a80); --color-reply-owner-top: var(--color-yellow); --color-reply-owner-bg: var(--color-yellow-a20); --color-reply-owner-shadow: var(--color-yellow-a80); --color-reply-floor-top: var(--color-purple); --color-reply-floor-bg: var(--color-purple-a20); --color-reply-floor-shadow: var(--color-purple-a80); --color-sicky-bg: var(--color-base-a20); --color-sicky-border: var(--color-bangumi-a40); --color-sicky-shadow: var(--color-base-a0f); --color-sicky-textarea: var(--color-base-ad0); --color-sicky-hover-bg: var(--color-bangumi-a20); --color-sicky-hover-border: var(--color-bangumi); --color-sicky-hover-shadow: var(--color-bangumi); }`, `html { .columns { > .column:not(#columnSubjectHomeB,#columnHomeB):last-child { > * { margin: 0; } display: flex; gap: 10px; flex-direction: column; position: sticky; top: 0; align-self: flex-start; max-height: 100vh; overflow-y: auto; } } .avatar:not(.tinyCover) { img, .avatarNeue { border-radius: 50% !important; } } .postTopic { border-bottom: none; .inner.tips { display: flex; height: 40px; align-items: center; gap: 8px; color: var(--color-reply-tips); } } #comment_list { box-sizing: border-box; .row:nth-child(odd), .row:nth-child(even) { background: transparent; } > .clearit:first-child { border-top: 1px solid transparent; } > .clearit, .topic_sub_reply > .clearit { box-sizing: border-box; border-bottom: none !important; border-top: 1px dashed var(--color-reply-sp); .inner.tips { display: flex; height: 40px; align-items: center; gap: 8px; color: var(--color-reply-tips); } .sub_reply_collapse .inner.tips { height: auto; } } > .clearit:not(:has(.topic_sub_reply > .clearit:hover)):hover, .topic_sub_reply > .clearit:hover { position: relative; z-index: 1; backdrop-filter: blur(5px); } > .clearit:not(:has(.topic_sub_reply > .clearit:hover)):hover, .topic_sub_reply > .clearit:hover { border-top: 1px solid var(--color-reply-normal-top) !important; background: linear-gradient(var(--color-reply-normal-bg) 1px, #00000000 60px) !important; box-shadow: 0 0 4px var(--color-reply-normal-shadow); } .clearit.owner { border-top: 1px solid var(--color-reply-owner-top) !important; background: linear-gradient(var(--color-reply-owner-bg) 1px, #00000000 60px) !important; } .clearit.owner:not(:has(.clearit:hover)):hover { border-top: 1px solid var(--color-reply-owner-top) !important; background: linear-gradient(var(--color-reply-owner-bg) 1px, #00000000 60px) !important; box-shadow: 0 0 4px var(--color-reply-owner-shadow); } .clearit.floor { border-top: 1px solid var(--color-reply-floor-top) !important; background: linear-gradient(var(--color-reply-floor-bg) 1px, #00000000 60px) !important; } .clearit.floor:not(:has(.clearit:hover)):hover { border-top: 1px solid var(--color-reply-floor-top) !important; background: linear-gradient(var(--color-reply-floor-bg) 1px, #00000000 60px) !important; box-shadow: 0 0 4px var(--color-reply-floor-shadow); } div.reply_collapse { padding: 5px 10px; } } @media (max-width: 640px) { .columns { > .column:last-child { align-self: auto !important; } } } }`, `html, html[data-theme='dark'] { #dock { li { position: relative; height: 18px; display: flex; align-items: center; justify-content: center; } li:not(:last-child) { border-right: 1px solid var(--color-dock-sp); } } .svg-icon { display: flex !important; align-items: center !important; justify-content: center !important; span { visibility: hidden; position: absolute; top: 0; left: 50%; transform: translate(-50%, calc(-100% - 10px)); padding: 2px 5px; border-radius: 5px; background: rgba(0, 0, 0, 0.6); white-space: nowrap; color: #fff; } span::after { content: ''; position: absolute !important; bottom: 0; left: 50%; border-top: 5px solid rgba(0, 0, 0, 0.6); border-right: 5px solid transparent; border-left: 5px solid transparent; backdrop-filter: blur(5px); transform: translate(-50%, 100%); } } .svg-icon:hover { span { visibility: visible; } } .switch { display: inline-block; position: relative; cursor: pointer; border-radius: 50px; height: 12px; width: 40px; border: 1px solid var(--color-switch-border); } .switch::before { content: ''; display: block; position: absolute; pointer-events: none; height: 12px; width: 40px; top: 0px; border-radius: 24px; background-color: var(--color-switch-off); } .switch::after { content: ''; display: block; position: absolute; pointer-events: none; top: 0; left: 0; height: 12px; width: 24px; border-radius: 24px; box-sizing: border-box; background-color: var(--color-switch-bar-inner); border: 5px solid var(--color-switch-bar-border); } .switch[switch="1"]::before { background-color: var(--color-switch-on); } .switch[switch="1"]::after { left: 16px; } .clearit { transition: all 0.3s ease; } .topic-box { #comment_list { .icon { color: var(--color-gray-11); } } .block { display: none; } .sicky-reply { background-color: var(--color-sicky-bg); border: 1px solid var(--color-sicky-border); box-shadow: 0px 0px 0px 2px var(--color-sicky-shadow); textarea { background-color: var(--color-sicky-textarea); } opacity: 0.6; } .sicky-reply:has(:focus), .sicky-reply:hover { opacity: 1; grid-template-rows: 1fr; background-color: var(--color-sicky-hover-bg); border: 1px solid var(--color-sicky-hover-border); box-shadow: 0 0 4px var(--color-sicky-hover-shadow); } #reply_wrapper { position: relative; padding: 5px; min-height: 50px; margin: 0; textarea.reply { width: 100% !important; } .switch { position: absolute; right: 10px; top: 10px; } .tip.rr + .switch { top: 35px; } } .sicky-reply { position: sticky; top: 0; z-index: 2; display: grid; height: auto; grid-template-rows: 0fr; border-radius: 4px; backdrop-filter: blur(5px); transition: all 0.3s ease; width: calc(100% - 1px); overflow: hidden; #slider { position: absolute; right: 5px; top: 13px; max-width: 100%; } } .svg-box { display: flex; justify-content: center; align-items: center; } } .vcomm { ul { white-space: nowrap; justify-content: center; align-items: center; } a { display: flex; align-items: center; gap: 0.5em; } } #community-helper { border-radius: 5px; display: flex; flex-direction: column; > .title { background: var(--color-bangumi); padding: 8px; color: var(--color-base-ad0); border-radius: 4px 4px 0 0; } > .user-info { padding: 10px; color: var(--color-bangumi-2); fieldset { padding-left: 10px; legend { font-weight: bold; margin-left: -10px; } legend::after { content: ':'; } } ul { display: flex; flex-wrap: wrap; gap: 4px; li { padding: 0 5px; border-radius: 50px; background: var(--color-bangumi-a40); border: 1px solid var(--color-bangumi); box-sizing: border-box; } } } } #community-helper:has(.user-info:empty) { visibility: hidden; } #robot_balloon { padding: 10px; .speech { ul { display: flex; flex-wrap: wrap; } } > .inner { padding: 0; max-height: 318px; background: none; overflow-y: scroll; scrollbar-width: none; ::-webkit-scrollbar { display: none; } } #community-helper { padding: 0; box-shadow: none; > .title { display: none; } > .user-info { padding: 0; color: unset; } } } #robot_balloon::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 10px; background: url(/img/ukagaka/balloon_pink.png) no-repeat top left; background-size: 100% auto; z-index: -1; } .ukagaka_balloon_pink_bottom { position: absolute; height: 10px; left: 0; right: 0; bottom: 0; width: 100% !important; background-size: 100% auto; z-index: -1; } @media (max-width: 640px) { #robot_balloon > .inner { max-height: 125px; } } }` ].join('\n')]) document.addEventListener('readystatechange', callNow(async () => { if (document.readyState !== 'complete') return; await db.init(); userSeek(); dockInject(); hoverUserListener(); parseHasCommentList(); })) })() })();