Pubu 電子書城優化腳本 (Pubu Helper)
簡介
版本: 3.1.2.1
更新日期: 2026-01-29
功能摘要:
書名完整顯示:解決 Pubu 網頁版書名過長被截斷(...)的問題,並智慧調整行高。
已購書籍標記:自動偵測已購買的書,在書名與詳細頁標題加上「綠色背景」,避免重複購買。
EPUB 下載警示:在書籍詳細頁,若該書不提供 EPUB 下載,會自動在標題旁顯示紅色的
(不提供EPUB下載)警示。智慧快取:減少伺服器請求,支援手動/自動更新已購書單。

📂 腳本原始碼 (Script Code)
// ==UserScript==
// @name Pubu書名全顯示(智慧多行+搜尋頁與書籍頁已購書綠底+快取)
// @namespace http://tampermonkey.net/
// @version 3.1.2.1
// @description Pubu書名完整顯示,避免覆蓋,智能調整顯示行數,已購書綠底,本地快取可手動/自動更新;新增不提供EPUB警示
// @author Your Name
// @match https://www.pubu.com.tw/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=www.pubu.com.tw
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// ====== 全域CSS修正 ======
const style = document.createElement('style');
style.textContent = `
/* 解除 line-clamp 限制 */
.info-name.show h3 a.text-reset.js-ecGtmClick {
-webkit-line-clamp: unset !important; /* 移除 WebKit 的行數限制 */
-webkit-box-orient: unset !important; /* 移除 WebKit 的排列方向限制 */
overflow: visible !important; /* 允許內容超出元素邊界 */
text-overflow: clip !important; /* 超出部分直接裁剪,不顯示省略號 */
display: block !important; /* 確保元素可以多行顯示 */
}
/* 動態調整 info-name 區塊的最小高度 */
.info-name.show {
min-height: auto !important; /* 高度自動,由內容決定 */
}
/* 確保 info-others (出版社、作者等資訊) 不會被書名覆蓋 */
.info-others.text-truncate {
margin-top: 3px !important; /* 增加一些頂部間距,避免與上方書名重疊 */
}
/* 已購書綠底樣式 */
.pubu-owned-title {
background: linear-gradient(90deg, #a8ff78 0%, #78ffd6 100%);
color: #222 !important;
border-radius: 4px;
padding: 2px 4px;
transition: background 0.3s;
}
/* 更新按鈕樣式 */
.pubu-update-btn {
font-size: 14px !important;
line-height: 21px !important;
color: #222 !important;
cursor: pointer;
padding: 6px 16px;
border: none;
background: none;
width: 100%;
text-align: left;
}
.pubu-update-btn:disabled {
color: #aaa !important;
cursor: not-allowed;
}
`;
document.head.appendChild(style);
// ====== 本地快取相關 ======
const LS_KEY = 'pubu_owned_book_ids';
const LS_TIME_KEY = 'pubu_owned_book_ids_time';
const UPDATE_INTERVAL = 24 * 60 * 60 * 1000; // 24小時
// 取得本地快取
function getCachedOwnedIds() {
try {
const ids = JSON.parse(localStorage.getItem(LS_KEY) || '[]');
return new Set(ids);
} catch {
return new Set();
}
}
// 設定本地快取
function setCachedOwnedIds(ids) {
localStorage.setItem(LS_KEY, JSON.stringify([...ids]));
localStorage.setItem(LS_TIME_KEY, Date.now().toString());
}
// 取得最後更新時間
function getLastUpdateTime() {
return parseInt(localStorage.getItem(LS_TIME_KEY) || '0', 10);
}
// 判斷是否需要自動更新
function needAutoUpdate() {
const last = getLastUpdateTime();
return !last || (Date.now() - last > UPDATE_INTERVAL);
}
// ====== 取得所有已購電子書ID ======
/**
* 透過 AJAX 方式抓取 /all/bookshelf?page=1,2... 取得所有已購買電子書ID
* 每頁 rows=50,加快抓取速度
* 回傳 Set<string>,每個元素為電子書ID
*/
async function fetchAllOwnedBookIds(onProgress) {
const ownedIds = new Set();
let page = 1;
const rows = 50; // 每頁抓取50本,加快速度
while (true) {
const url = `/all/bookshelf?page=${page}&rows=${rows}&sort=0&queryShelf=&category=1`;
try {
const resp = await fetch(url, { credentials: 'include' });
const html = await resp.text();
// 解析所有 /ebook/123456 連結
const matches = [...html.matchAll(/href="\/ebook\/(\d+)/g)];
if (matches.length === 0) break; // 沒有更多書,結束
matches.forEach(m => ownedIds.add(m[1]));
if (typeof onProgress === 'function') onProgress(page, ownedIds.size);
page++;
} catch (e) {
break;
}
}
return ownedIds;
}
// ====== 移除樣式限制的核心函式 (主要移除造成截斷的樣式) ======
const unlockTitles = (element) => {
let parent = element;
while (parent) {
if (parent.style) {
parent.style.whiteSpace = 'normal'; // 允許文字正常換行
parent.style.overflow = 'visible'; // 允許內容溢出
parent.style.textOverflow = 'clip'; // 溢出時裁剪
//parent.style.maxWidth = 'none'; // 不限制最大寬度
parent.style.webkitLineClamp = 'unset'; // 再次確保移除 WebKit 內核瀏覽器的行數截斷限制
parent.style.webkitBoxOrient = 'unset'; // 再次確保移除 WebKit 內核瀏覽器的排列方向限制
}
parent = parent.parentElement;
}
void element.offsetHeight; // 強制瀏覽器進行重繪,以確保樣式更改立即生效
};
// ====== 主要處理邏輯:書名全顯示+已購書綠底+(新)不提供EPUB警示 ======
function processTitles(ownedIds) {
// ====== 通用,用於搜索結果,書名全顯示+已購書綠底 ======
document.querySelectorAll('.info-name h3 a.text-reset').forEach(link => {
// 如果連結文字包含省略號,或者文字內容與 title 屬性不符,則更新
if (link.textContent.includes('...') || link.title && link.textContent !== link.title) {
link.textContent = link.title; // 將連結文字設定為完整的書名
unlockTitles(link); // 解除相關元素的樣式限制
} else if (!link.title && link.textContent.length > 21) {
unlockTitles(link); // 解除相關元素的樣式限制
}
// 動態調整 h3 (書名標題) 及其父容器的高度
const titleLength = link.textContent.length;
// 假設每行約 10 個中文字元來估算行數 (可依實際情況調整此數值)
const estimatedLines = Math.ceil(titleLength / 10);
// 預設行高約為 21px (可依實際情況調整此數值)
const lineHeight = 21;
const minHeight = estimatedLines * lineHeight;
// 動態設定 h3 元素的最小高度
link.parentNode.style.minHeight = minHeight + 'px';
// 動態設定 .info-name 容器的最小高度,確保有足夠空間容納多行書名
link.parentNode.parentNode.style.minHeight = minHeight + 'px';
// ====== 標記已購買書籍:比對ID,命中即加上綠底class ======
const match = link.href.match(/\/ebook\/(\d+)/);
if (match && ownedIds.has(match[1])) {
link.classList.add('pubu-owned-title');
} else {
link.classList.remove('pubu-owned-title');
}
});
// ====== 檢查當前頁面是否為書籍詳細頁 ======
const currentMatch = window.location.href.match(/\/ebook\/(\d+)/);
if (currentMatch) {
// 1. 若已購買,標記綠底
if (ownedIds.has(currentMatch[1])) {
const h1 = document.querySelector('h1.h4');
if (h1) {
h1.classList.add('pubu-owned-title');
}
}
// 2. 檢查是否不提供 EPUB 下載 (新增功能)
// 尋找包含 "提供 Adobe DRM" 的標籤所在 col-4
const drmCols = document.querySelectorAll('.col-4');
for (const col of drmCols) {
// 檢查是否包含 "提供 Adobe DRM" 文字
if (col.textContent.trim().includes('提供 Adobe DRM')) {
// 找到同一 row 中的下一個 col-8 (內容區塊)
const contentCol = col.nextElementSibling;
if (contentCol && contentCol.classList.contains('col-8')) {
// 檢查內容是否包含 "不提供EPUB"
if (contentCol.textContent.includes('不提供EPUB')) {
const h1 = document.querySelector('h1.h4');
// 避免重複添加提示 (檢查是否已有 .pubu-no-epub-warning)
if (h1 && !h1.querySelector('.pubu-no-epub-warning')) {
const warningSpan = document.createElement('span');
warningSpan.className = 'pubu-no-epub-warning';
warningSpan.style.cssText = 'color: red !important; font-size: 16px !important; font-weight: bold; margin-left: 10px; vertical-align: middle;';
warningSpan.textContent = '(不提供EPUB下載)';
h1.appendChild(warningSpan);
}
}
}
break; // 找到標籤後即可停止搜尋
}
}
}
// ====== 檢查當前頁面是否為購物車 ======
if (window.location.href.match(/\/cart/)) {
document.querySelectorAll('li.booktitle p a').forEach(link => {
const cartMatch = link.href.match(/\/ebook\/(\d+)/);
if (cartMatch && ownedIds.has(cartMatch[1])) {
link.classList.add('pubu-owned-title');
} else {
link.classList.remove('pubu-owned-title');
}
});
}
}
// ====== 高效能 MutationObserver 配置,用於監聽 DOM 變化 ======
function setupObserver(ownedIds) {
const process = () => processTitles(ownedIds);
const observer = new MutationObserver(mutations => {
mutations.some(mutation => {
if (mutation.type === 'childList') {
process();
return true;
}
return false;
});
});
const contentNode = document.querySelector('#search-list-content') || document.documentElement;
observer.observe(contentNode, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
// 初始化執行,並兼容單頁應用程式 (SPA) 的路由變化
const init = () => {
process();
window.requestAnimationFrame(process);
};
init();
window.addEventListener('popstate', init);
window.addEventListener('pushstate', init);
window.addEventListener('replacestate', init);
}
// ====== UI:插入手動更新按鈕到會員下拉選單 ======
function insertUpdateButton() {
// 判斷是否為手機模式,寬度 <= 768px 視為手機
const isMobile = /Mobi|Android/i.test(navigator.userAgent) || window.matchMedia("(max-width: 768px)").matches;
// const isMobile = window.matchMedia("(max-width: 768px)").matches;
// 嘗試尋找目標下拉選單
const menu = isMobile
? document.querySelector('.pl-4')
: document.querySelector('.dropdown-menu[aria-labelledby="dropdownMenu-login"]');
// 防止重複插入
if (!menu || menu.querySelector('.pubu-update-btn')) return;
console.log('已插入按鈕');
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'dropdown-item pubu-update-btn';
btn.textContent = '更新已購書(Pubu Userscript)';
// 事件直接綁定到目前按鈕,避免 mutation observer dom 重建造成引用失效
btn.addEventListener('click', async function(e) {
const el = e.currentTarget; // 總是取得真正被點擊的這顆按鈕
el.disabled = true;
el.textContent = '更新中...';
ownedIds = await fetchAllOwnedBookIds();
setCachedOwnedIds(ownedIds);
processTitles(ownedIds);
el.textContent = '更新完成!';
setTimeout(() => {
el.textContent = '再次更新已購書';
el.disabled = false;
}, 2000);
});
// 插入到「登出」按鈕上方
const logout = menu.querySelector('#logout');
if (logout) {
menu.insertBefore(btn, logout);
} else {
menu.appendChild(btn);
}
}
// ====== 主流程 ======
let ownedIds = getCachedOwnedIds();
let observerStarted = false;
// 處理搜尋頁面標記
function startObserverIfNeeded() {
//if (!observerStarted && document.querySelector('.info-name.show h3 a.text-reset.js-ecGtmClick')) {
if (!observerStarted && document.querySelector('.info-name h3 a.text-reset')) {
setupObserver(ownedIds);
observerStarted = true;
} else if(!observerStarted && window.location.href.match(/\/cart/)) {
setupObserver(ownedIds);
observerStarted = true;
}
}
// 嘗試自動更新(每天一次)
async function tryAutoUpdate() {
if (needAutoUpdate()) {
ownedIds = await fetchAllOwnedBookIds();
setCachedOwnedIds(ownedIds);
processTitles(ownedIds);
}
}
// 嘗試插入更新按鈕(因為會員下拉選單是動態生成,需監控)
function setupUpdateButton() {
const tryInsert = () => {
insertUpdateButton();
};
// 監控下拉選單出現
const obs = new MutationObserver(tryInsert);
obs.observe(document.body, { childList: true, subtree: true });
// 頁面載入時先嘗試一次
tryInsert();
}
// ====== 啟動 ======
setupUpdateButton();
startObserverIfNeeded();
tryAutoUpdate();
// SPA動態頁面切換時重新啟動標記
window.addEventListener('popstate', startObserverIfNeeded);
window.addEventListener('pushstate', startObserverIfNeeded);
window.addEventListener('replacestate', startObserverIfNeeded);
})();
🔧 新手安裝指南 (電腦版 Chrome/Edge/Firefox)
若您是在電腦上使用,請依照以下步驟安裝:
安裝擴充功能:
新增腳本:
安裝完後,點擊瀏覽器右上角的 Tampermonkey 圖示(黑色正方形圖示)。
選擇「添加新腳本 (Create a new script)」。
貼上程式碼:
刪除編輯器內原有的所有內容。
複製上方「腳本原始碼」區塊內的所有程式碼。
貼上到編輯器中。
儲存:
- 按下
Ctrl + S或點擊左上角的「文件 (File)」>「保存 (Save)」。
- 按下
完成:
- 回到 Pubu 網站重新整理頁面,即可看到效果。
📱 新手安裝指南 (Android Firefox 版)
若您想在手機上使用,目前以 Android 版 Firefox 支援度最好,請參考以下步驟:
下載瀏覽器:
- 請至 Google Play 商店下載並安裝 Firefox 瀏覽器。
安裝擴充套件:
打開 Firefox App。
點擊右下角(或右上角)的「三點選單」圖示。
選擇「擴充套件 (Add-ons)」。
在推薦列表中找到 Tampermonkey,點擊
+安裝。
新增腳本:
安裝後,再次點擊「三點選單」,您會看到最下方出現了
Tampermonkey,點擊進入。點擊 Dashboard(儀表板)或「+」號來新增腳本。
(小技巧):由於手機複製貼上大量程式碼較不便,建議先將上方程式碼複製到手機的筆記本 App,再全選複製,貼入 Tampermonkey 的編輯視窗。
儲存並使用:
貼上後,點擊編輯器選單中的「保存 (Save)」。
用 Firefox 開啟 Pubu 網站並登入,腳本即會自動運作。
手機版也能在側邊欄選單底部看到「更新已購書」的按鈕。