mirror of
https://github.com/naiyQAQ/fanqie-assistant.git
synced 2026-10-21 10:24:47 +00:00
feat & refactor: 支持app端签名的请求,支持右键操作书架书籍,逻辑大幅重构,更改默认的设备逻辑为自动注册,支持载入某些书籍内置的CSS、支持跳转正文注释等等
This commit is contained in:
1 parent
2f2ee606c0
commit
3586b01dbb
47 files changed
+3594
-243
No files matched your search
@@ -3,6 +3,7 @@ import type { HookConfig } from '../config'
|
||||
import BookshelfView from '../views/BookshelfView.vue'
|
||||
import bookshelfcss from '../assets/bookshelf.css?raw'
|
||||
import { waitForElement } from '../utils'
|
||||
import { userState } from '../api/user'
|
||||
|
||||
const CONTAINER_ID = 'fqa-bookshelf-root'
|
||||
const STYLE_ID = 'fqa-bookshelf-style'
|
||||
@@ -102,7 +103,7 @@ async function mainHook(_previous?: string): Promise<void> {
|
||||
}
|
||||
|
||||
function filter(path: string, _query: URLSearchParams, _hash: string) {
|
||||
return isBookshelfPath(path) || !!app
|
||||
return (isBookshelfPath(path) || !!app) && userState.isLogin
|
||||
}
|
||||
|
||||
const _exports: HookConfig[] = [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HookConfig } from "../config"
|
||||
import { settings } from "../settings"
|
||||
|
||||
// disable report
|
||||
const blackList = [
|
||||
@@ -16,6 +17,8 @@ const BLOCKED_BODY = JSON.stringify({
|
||||
})
|
||||
|
||||
function checkBlack(url: string): boolean {
|
||||
// 设置里关掉后即时放行,无需刷新页面
|
||||
if (!settings.blockReport) return false
|
||||
return blackList.some(black => url.includes(black))
|
||||
}
|
||||
|
||||
|
||||
+27
-9
@@ -1,11 +1,14 @@
|
||||
import { type HookConfig } from '../config'
|
||||
import { getChapter } from '../api/content'
|
||||
import { getBookInfoAndCatalog } from '../api/book'
|
||||
import { decryptComicImage } from '../utils/crypto'
|
||||
import { applyBookCss } from '../api/bookcss'
|
||||
import { processFootnotes, bindFootnoteInteraction } from '../utils/footnote'
|
||||
import { settings } from '../settings'
|
||||
import { fetch } from '../config'
|
||||
// import defaultcss from '../assets/default.css?raw';
|
||||
import { cloneElement } from '../utils';
|
||||
import { type Book } from '../types'
|
||||
import { decryptComicImage } from '../crypto/content';
|
||||
// import moment from 'moment'
|
||||
|
||||
let currentBook: Book | null = null
|
||||
@@ -36,20 +39,34 @@ async function insertContent() {
|
||||
}).__INITIAL_STATE__
|
||||
const chapterTitle = chapter.novel_data?.title || pageState?.reader?.chapterData?.title
|
||||
if (typeof chapter.content === 'string') {
|
||||
// TODO: 支持部分书籍的CSS内容
|
||||
// 书籍自带的排版样式(css_map),作用域限定在正文容器内后注入
|
||||
void applyBookCss(chapter.novel_data?.css_map, '#fqa-reader-content')
|
||||
const dp = new DOMParser()
|
||||
const doc = dp.parseFromString(chapter.content, 'text/html')
|
||||
const body = doc.body
|
||||
const article = body.querySelector('article')
|
||||
const toProcess = article || body
|
||||
// XHTML 里的 <link href="Styles/xxx.css"> 是 EPUB 相对路径,页面里解析不到,
|
||||
// 留着只会产生 404 请求,样式已由 applyBookCss 处理
|
||||
body.querySelectorAll('link[rel="stylesheet"]').forEach((el) => el.remove())
|
||||
let article = body.querySelector('article')
|
||||
let toProcess = article || body
|
||||
// 章内注释:把裂图标记换成可点的上标序号,并重排文末注释列表
|
||||
processFootnotes(toProcess)
|
||||
|
||||
for (let i = 0; i < toProcess.childNodes.length; i++) {
|
||||
if (i < 1 && (toProcess.childNodes[i] as HTMLElement)?.innerHTML.includes(chapterTitle)) {
|
||||
// console.log('Child node:', toProcess.childNodes[i] as HTMLElement)
|
||||
if (i < 2 && (toProcess.childNodes[i] as HTMLElement)?.innerHTML?.includes(chapterTitle)) {
|
||||
// remove duplicate title
|
||||
toProcess.removeChild(toProcess.childNodes[i] as HTMLElement)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!article) {
|
||||
article = document.createElement('article')
|
||||
article.innerHTML = toProcess.innerHTML
|
||||
toProcess = article
|
||||
}
|
||||
|
||||
const readerContainer = document.querySelector("div.muye-reader-content:not(.fqa)")
|
||||
if (readerContainer) {
|
||||
// 防止插入多次
|
||||
@@ -58,13 +75,14 @@ async function insertContent() {
|
||||
scriptContainer = cloneElement(readerContainer) as HTMLDivElement
|
||||
scriptContainer.id = 'fqa-reader-content'
|
||||
scriptContainer.classList.add('fqa')
|
||||
scriptContainer.classList.remove('noselect') // allow text selection
|
||||
if (settings.allowCopy) scriptContainer.classList.remove('noselect')
|
||||
readerContainer.insertAdjacentElement('beforebegin', scriptContainer)
|
||||
}
|
||||
scriptContainer.innerHTML = ''
|
||||
// 隐藏原来的内容div
|
||||
readerContainer.classList.add('fqa-hide')
|
||||
scriptContainer.appendChild(toProcess)
|
||||
bindFootnoteInteraction(scriptContainer)
|
||||
}
|
||||
} else if (chapter.content.picInfos) { // comic
|
||||
if (chapter.content.encrypt) {
|
||||
@@ -92,7 +110,7 @@ async function insertContent() {
|
||||
scriptContainer.id = 'fqa-reader-content'
|
||||
scriptContainer.classList.add('fqa')
|
||||
scriptContainer.classList.add('fqa-comic-reader')
|
||||
scriptContainer.classList.remove('noselect')
|
||||
if (settings.allowCopy) scriptContainer.classList.remove('noselect')
|
||||
readerContainer.insertAdjacentElement('beforebegin', scriptContainer)
|
||||
}
|
||||
scriptContainer.innerHTML = ''
|
||||
@@ -156,7 +174,7 @@ async function insertContent() {
|
||||
scriptContainer.id = 'fqa-reader-content'
|
||||
scriptContainer.classList.add('fqa')
|
||||
scriptContainer.classList.add('fqa-comic-reader')
|
||||
scriptContainer.classList.remove('noselect')
|
||||
if (settings.allowCopy) scriptContainer.classList.remove('noselect')
|
||||
readerContainer.insertAdjacentElement('beforebegin', scriptContainer)
|
||||
}
|
||||
scriptContainer.innerHTML = ''
|
||||
@@ -170,7 +188,7 @@ async function insertContent() {
|
||||
let muyeReaderSubtitle = document.querySelector("div.muye-reader-subtitle")
|
||||
document.querySelector('#fqa-subtitle')?.remove() // prevent duplicate subtitle
|
||||
if (muyeReaderSubtitle) {
|
||||
// 脱离原页面 Vue tree ,防止被二次修改
|
||||
// 脱离原页面防止被二次修改
|
||||
let _cloned = cloneElement(muyeReaderSubtitle)
|
||||
muyeReaderSubtitle.classList.add('fqa-hide')
|
||||
_cloned.id = 'fqa-subtitle'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { getDetailedUserInfo, userState } from '../api/user'
|
||||
import type { HookConfig } from '../config'
|
||||
import bookshelf from '../assets/bookshelf.svg?raw'
|
||||
import { openSettings } from '../settingsPanel'
|
||||
import { cloneElement } from '../utils'
|
||||
|
||||
function formatReadingTime(readBookTime: bigint): string {
|
||||
let minutes = readBookTime / 60_000n
|
||||
@@ -26,6 +28,12 @@ function createMenuItem(text: string, icon?: string, onclick?: () => void): HTML
|
||||
if (icon) {
|
||||
const iconDiv = document.createElement('div')
|
||||
iconDiv.classList.add('slogin-user-avatar__menu-item__icon')
|
||||
if (document.querySelector('div.muye-reader-dark')) {
|
||||
console.log('isdark')
|
||||
iconDiv.classList.add('fqa-icon-dark')
|
||||
} else {
|
||||
console.log('islight')
|
||||
}
|
||||
iconDiv.innerHTML = icon
|
||||
inner1.appendChild(iconDiv)
|
||||
}
|
||||
@@ -62,6 +70,8 @@ async function mainHook(_previous?: string): Promise<void> {
|
||||
window.open('https://fanqienovel.com/bookshelf', '_blank')
|
||||
})
|
||||
secondDiv.insertAdjacentElement('afterend', thirdDiv)
|
||||
// 助手设置固定放在菜单最后
|
||||
menuInner.appendChild(createMenuItem('助手设置', undefined, openSettings))
|
||||
}
|
||||
|
||||
const scan = (root: HTMLElement) => {
|
||||
@@ -89,16 +99,69 @@ async function mainHook(_previous?: string): Promise<void> {
|
||||
scan(document.body)
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录时没有用户菜单,改为复制「注册」按钮做一个「助手设置」入口。
|
||||
*/
|
||||
async function guestHook(_previous?: string): Promise<void> {
|
||||
const injected = new WeakSet<HTMLElement>()
|
||||
|
||||
const inject = (registerBtn: HTMLElement) => {
|
||||
const parent = registerBtn.parentElement
|
||||
if (!parent || injected.has(parent)) return
|
||||
if (parent.querySelector('.fqa-settings-entry')) return
|
||||
injected.add(parent)
|
||||
|
||||
const entry = cloneElement(registerBtn)
|
||||
entry.classList.add('fqa-settings-entry')
|
||||
entry.textContent = '助手设置'
|
||||
entry.addEventListener('click', event => {
|
||||
// 原按钮带跳转逻辑,别让它继续冒泡
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
openSettings()
|
||||
})
|
||||
registerBtn.insertAdjacentElement('afterend', entry)
|
||||
}
|
||||
|
||||
const scan = (root: ParentNode) => {
|
||||
root.querySelectorAll<HTMLElement>('.slogin-user-avatar__buttons__item').forEach(el => {
|
||||
if (el.classList.contains('fqa-settings-entry')) return
|
||||
if (el.textContent?.trim() === '注册') inject(el)
|
||||
})
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(mutations => {
|
||||
for (const mutation of mutations) {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node instanceof HTMLElement) scan(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
observer.observe(document.body, { childList: true, subtree: true })
|
||||
|
||||
scan(document)
|
||||
}
|
||||
|
||||
function filter(path: string, _query: URLSearchParams, _hash: string): boolean {
|
||||
return userState.isLogin && !path.startsWith('/writer') && !path.startsWith('/welfare')
|
||||
}
|
||||
|
||||
function guestFilter(path: string, _query: URLSearchParams, _hash: string): boolean {
|
||||
return !userState.isLogin && !path.startsWith('/writer') && !path.startsWith('/welfare')
|
||||
}
|
||||
|
||||
const _exports: HookConfig[] = [
|
||||
{
|
||||
id: 'userHook',
|
||||
event: 'load',
|
||||
filter,
|
||||
handler: mainHook
|
||||
},
|
||||
{
|
||||
id: 'userHook_guest',
|
||||
event: 'load',
|
||||
filter: guestFilter,
|
||||
handler: guestHook
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in new issue
Block a user