E/Ex-Hentai switch + HentaiVerse quick launch + e-hentai login-reward peek
// ==UserScript==
// @name [E/Ex]Switch
// @name:JP [E/Ex]切替
// @name:zh-TW E網 表/裏站切換
// @name:zh-CN E网 表/里站切换
// @namespace https://github.com/awdrrawd
// @version 1.1
// @description E/Ex-Hentai switch + HentaiVerse quick launch + e-hentai login-reward peek
// @description:zh-tw E網表/裏站切換,並在 exhentai 加上 HentaiVerse 按鈕與登入獎勵提示
// @description:zh-cn E网表/里站切换,并在 exhentai 加上 HentaiVerse 按钮与登入奖励提示
// @match https://e-hentai.org/*
// @match https://exhentai.org/*
// @icon https://e-hentai.org/favicon.ico
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect e-hentai.org
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/* =====================================================================
* 0. 共用小工具
* ===================================================================*/
const $el = (tag, attrs) => {
const element = document.createElement(tag);
for (const key in attrs) {
if (key === 'textContent') {
element.textContent = attrs[key];
} else {
element.setAttribute(key, attrs[key]);
}
}
return element;
};
// 語言偵測(沿用原本邏輯)
const getButtonText = () => {
const lang = navigator.language || navigator.userLanguage;
const isTraditionalChinese = /^(zh-TW|zh-HK|zh-Hant)/.test(lang);
const isSimplifiedChinese = /^(zh-CN|zh-Hans)/.test(lang);
const isJapanese = /^ja/.test(lang);
if (isTraditionalChinese) return '[E/Ex]切換';
if (isSimplifiedChinese) return '[E/Ex]切换';
if (isJapanese) return '[E/Ex]切替';
return '[E/Ex]Switch';
};
/* =====================================================================
* 1. 不顯示 [E/Ex]切換 按鈕的頁面清單
* ===================================================================*/
const SWITCH_EXCLUDED_PAGES = [
{ path: '/home.php' },
{ path: '/stats.php' },
{ path: '/hentaiathome.php' },
{ path: '/bitcoin.php' },
{ path: '/hathperks.php' },
{ path: '/exchange.php', params: { t: 'hath' } },
{ path: '/exchange.php', params: { t: 'gp' } },
{ path: '/logs.php', params: { t: 'credits' } },
{ path: '/logs.php', params: { t: 'karma' } },
{ path: '/toplist.php' },
// /bounty.php 不論有沒有 ?act=tops / topt / tope 或根本沒有 query,一律排除
{ path: '/bounty.php' },
{ path: '/news.php' },
// repo.e-hentai.org/bounty_post.php 不在 @match 範圍內,這裡列出僅供備查
];
function matchesRule(rule) {
const { pathname, search } = window.location;
if (rule.path !== pathname) return false;
if (!rule.params) return true;
const params = new URLSearchParams(search);
return Object.entries(rule.params).every(([k, v]) => params.get(k) === v);
}
function isSwitchExcludedPage() {
return SWITCH_EXCLUDED_PAGES.some(matchesRule);
}
/* =====================================================================
* 2. 建立導覽列按鈕的共用函式(改良版)
* ===================================================================*/
function addNavButton({ id, text, href = '#', onClick, title }) {
if (document.getElementById(id)) return null; // 已存在就不重複插入
const nb = document.getElementById('nb');
if (!nb) {
console.error('[E/Ex]Switch: 導覽列 #nb 未找到,無法添加按鈕');
return null;
}
const a = $el('a', { id, href, textContent: text });
if (title) a.title = title;
if (onClick) {
a.addEventListener('click', (e) => {
e.preventDefault();
onClick(e);
});
}
const wrapper = $el('div');
wrapper.appendChild(a);
nb.appendChild(wrapper);
return a;
}
/* =====================================================================
* 3. [E/Ex]切換按鈕
* ===================================================================*/
function initSwitchButton() {
if (isSwitchExcludedPage()) return;
addNavButton({
id: 'switch-btn',
text: getButtonText(),
onClick: () => {
const currentUrl = window.location.href;
const newUrl = currentUrl.startsWith('https://exhentai.org')
? currentUrl.replace('https://exhentai.org', 'https://e-hentai.org')
: currentUrl.replace('https://e-hentai.org', 'https://exhentai.org');
window.location.href = newUrl;
},
});
}
/* =====================================================================
* 4. HentaiVerse 按鈕(只在 exhentai.org 顯示)
* ===================================================================*/
function initHentaiVerseButton() {
if (location.hostname !== 'exhentai.org') return;
if (isSwitchExcludedPage()) return; // 工具頁一律不加,跟 [E/Ex]切換 保持一致
addNavButton({
id: 'hv-btn',
text: 'HentaiVerse',
href: 'https://hentaiverse.org/',
onClick: () => {
if (typeof window.popUp === 'function') {
window.popUp('https://hentaiverse.org/', 1250, 720);
} else {
const w = 1250, h = 720;
window.open(
'https://hentaiverse.org/',
'_hentaiverse',
`toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=${w},height=${h},left=${(screen.width - w) / 2},top=${(screen.height - h) / 2}`
);
}
},
});
}
/* =====================================================================
* 5.(選用)在 exhentai.org 也提示 e-hentai.org 的 HentaiVerse 登入獎勵
* ===================================================================*/
const EVENTPANE_CACHE_KEY = 'eex_eventpane_cache';
const EVENTPANE_TTL_MS = 10 * 60 * 1000; // 10 分鐘節流
function fetchHomeEventPaneHtml() {
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== 'function') {
reject(new Error('GM_xmlhttpRequest 未授權,無法跨網域讀取 e-hentai.org/home.php'));
return;
}
GM_xmlhttpRequest({
method: 'GET',
url: 'https://e-hentai.org/home.php',
onload: (res) => {
try {
const doc = new DOMParser().parseFromString(res.responseText, 'text/html');
const pane = doc.getElementById('eventpane');
resolve(pane ? pane.outerHTML : null);
} catch (e) {
reject(e);
}
},
onerror: reject,
});
});
}
/* =====================================================================
* 執行
* ===================================================================*/
initHentaiVerseButton();
initSwitchButton();
})();