// ==UserScript==
// @name [E/Ex-Hentai] AutoLogin
// @name:zh-TW [E/Ex-Hentai] 自動登入
// @name:zh-CN [E/Ex-Hentai] 自动登入
// @name:ja [E/Ex-Hentai] 自動ログイン
// @name:ko [E/Ex-Hentai] 자동 로그인
// @name:ru [E/Ex-Hentai] Автоматический вход
// @name:en [E/Ex-Hentai] AutoLogin
// @version 2025.08.23-Beta
// @author Canaan HS
// @description E/Ex - 共享帳號登入、自動獲取 Cookies、手動輸入 Cookies、本地備份以及查看備份,自動檢測登入
// @description:zh-TW E/Ex - 共享帳號登入、自動獲取 Cookies、手動輸入 Cookies、本地備份以及查看備份,自動檢測登入
// @description:zh-CN E/Ex - 共享帐号登录、自动获取 Cookies、手动输入 Cookies、本地备份以及查看备份,自动检测登录
// @description:ja E/Ex - 共有アカウントでのログイン、クッキーの自動取得、クッキーの手動入力、ローカルバックアップおよびバックアップの表示、自動ログイン検出
// @description:ko E/Ex - 공유 계정 로그인, 자동으로 쿠키 가져오기, 쿠키 수동 입력, 로컬 백업 및 백업 보기, 자동 로그인 감지
// @description:ru E/Ex - Вход в общий аккаунт, автоматическое получение cookies, ручной ввод cookies, локальное резервное копирование и просмотр резервных копий, автоматическое определение входа
// @description:en E/Ex - Shared account login, automatic cookie retrieval, manual cookie input, local backup, and backup viewing, automatic login detection
// @noframes
// @connect *
// @match *://e-hentai.org/*
// @match *://exhentai.org/*
// @icon https://e-hentai.org/favicon.ico
// @license MPL-2.0
// @namespace https://greasyfork.org/users/989635
// @supportURL https://github.com/Canaan-HS/MonkeyScript/issues
// @require https://update.greasyfork.org/scripts/487608/1647211/SyntaxLite_min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-jgrowl/1.5.1/jquery.jgrowl.min.js
// @resource jgrowl-css https://cdnjs.cloudflare.com/ajax/libs/jquery-jgrowl/1.5.1/jquery.jgrowl.min.css
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_addValueChangeListener
// @run-at document-start
// ==/UserScript==
(async () => {
const domain = Lib.$domain;
const {
Transl
} = Language();
(async function ImportStyle() {
let show_style, button_style, button_hover, jGrowl_style, acc_style;
if (domain === "e-hentai.org") {
button_hover = "color: #8f4701;";
jGrowl_style = "background-color: #5C0D12; color: #fefefe;";
show_style = "background-color: #fefefe; border: 3px ridge #34353b;";
acc_style = "color: #5C0D12; background-color: #fefefe; border: 2px solid #B5A4A4;";
button_style = "color: #5C0D12; border: 2px solid #B5A4A4; background-color: #fefefe;";
} else if (domain === "exhentai.org") {
button_hover = "color: #989898;";
jGrowl_style = "background-color: #fefefe; color: #5C0D12;";
show_style = "background-color: #34353b; border: 2px ridge #5C0D12;";
acc_style = "color: #f1f1f1; background-color: #34353b; border: 2px solid #8d8d8d;";
button_style = "color: #fefefe; border: 2px solid #8d8d8d; background-color: #34353b;";
Lib.addStyle(`
body {
padding: 2px;
color: #f1f1f1;
text-align: center;
background: #34353b;
}
`);
}
Lib.addStyle(`
${GM_getResourceText("jgrowl-css")}
.jGrowl {
${jGrowl_style}
top: 2rem;
left: 50%;
width: auto;
z-index: 9999;
font-size: 1.3rem;
border-radius: 2px;
text-align: center;
white-space: nowrap;
transform: translateX(-50%);
}
.modal-background {
top: 50%;
left: 50%;
opacity: 0;
width: 100%;
height: 100%;
z-index: 8888;
overflow: auto;
position: fixed;
transition: 0.6s ease;
background-color: rgba(0,0,0,0);
transform: translate(-50%, -50%) scale(0.3);
}
.acc-modal {
${show_style}
width: 18%;
overflow: auto;
margin: 11rem auto;
border-radius: 10px;
}
.acc-select-flex {
display: flex;
align-items: center;
flex-direction: initial;
justify-content: space-around;
}
.acc-button-flex {
display: flex;
padding: 0 0 15px 0;
justify-content: center;
}
.acc-select {
${acc_style}
padding: 4px;
min-width: 10rem;
margin: 1.1rem 1.4rem 1.5rem 1.4rem;
font-weight: bold;
cursor: pointer;
font-size: 1.2rem;
text-align: center;
border-radius: 5px;
}
.show-modal {
${show_style}
width: 25%;
padding: 1.5rem;
overflow: auto;
margin: 5rem auto;
text-align: left;
border-radius: 10px;
border-collapse: collapse;
}
.modal-button {
${button_style}
top: 0;
margin: 3% 2%;
font-size: 14px;
font-weight: bold;
border-radius: 3px;
}
.modal-button:hover, .modal-button:focus {
${button_hover}
cursor: pointer;
text-decoration: none;
}
.set-modal {
${show_style}
width: 30%;
padding: 0.3rem;
overflow: auto;
border-radius: 10px;
text-align: center;
border-collapse: collapse;
margin: 2% auto 8px auto;
}
.set-box {
display: flex;
margin: 0.6rem;
font-weight: bold;
flex-direction: column;
align-items: flex-start;
}
.set-list {
width: 95%;
font-weight: 550;
font-size: 1.1rem;
text-align: center;
}
hr {
width: 98%;
opacity: 0.2;
border: 1px solid;
margin-top: 1.3rem;
}
label {
margin: 0.4rem;
font-size: 0.9rem;
}
.cancelFavorite {
float: left;
cursor: pointer;
font-size: 1.7rem;
padding: 10px 0 0 20px;
}
.cancelFavorite:hover {
opacity: 0.5;
}
.addFavorite {
float: left;
cursor: pointer;
font-size: 1.7rem;
padding: 10px 0 0 20px;
transition: transform 0.2s ease;
}
.addFavorite:hover {
animation: heartbeat 1.5s infinite;
}
@keyframes heartbeat {
0% {
transform: scale(1);
}
25% {
transform: scale(1.1);
}
50% {
transform: scale(1);
}
75% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.lc {
padding: 1rem 0 !important;
}
.unFavorite {
font-size: 2rem;
position: relative;
display: inline-block;
transition: transform 0.2s ease;
}
.unFavorite:hover {
animation: shake 0.8s ease-in-out infinite;
}
@keyframes shake {
0% {
left: 0;
}
25% {
left: -5px;
}
50% {
left: 5px;
}
75% {
left: -5px;
}
100% {
left: 0;
}
}
`, "AutoLogin-Style");
})();
(async function Main($Cookie, $Shared) {
let Share = Lib.getV("Share", {});
if (typeof Share === "string") {
Share = JSON.parse(Share);
}
const url = Lib.$url;
const Post_Page = /https:\/\/[^\/]+\/g\/\d+\/[a-zA-Z0-9]+/;
const Favorites_Page = /https:\/\/[^\/]+\/favorites.php/;
const CreateMenu = async Modal => {
Lib.$q(".modal-background")?.remove();
$("body").append(Modal.replace(/>\s+</g, "><"));
requestAnimationFrame(() => {
$(".modal-background").css({
opacity: "1",
"background-color": "rgba(0,0,0,0.7)",
transform: "translate(-50%, -50%) scale(1)"
});
});
};
const DeleteMenu = async () => {
const modal = $(".modal-background");
modal.css({
opacity: "0",
"pointer-events": "none",
"background-color": "rgba(0,0,0,0)",
transform: "translate(-50%, -50%) scale(0)"
});
setTimeout(() => {
modal.remove();
}, 1300);
};
const Expand = async () => {
Lib.regMenu({
[Transl("📜 自動獲取")]: AutoGetCookie,
[Transl("📝 手動輸入")]: ManualSetting,
[Transl("🔍 查看保存")]: ViewSaveCookie,
[Transl("🔃 手動注入")]: CookieInjection,
[Transl("🗑️ 清除登入")]: ClearLogin
}, {
name: "Expand"
});
};
const Collapse = async () => {
for (let i = 1; i <= 5; i++) {
Lib.unMenu("Expand-" + i);
}
};
const MenuToggle = async () => {
const state = Lib.getV("Expand", false), disp = state ? Transl("📁 摺疊菜單") : Transl("📂 展開菜單");
Lib.regMenu({
[disp]: {
func: () => {
state ? Lib.setV("Expand", false) : Lib.setV("Expand", true);
MenuToggle();
},
hotkey: "c",
close: false
}
}, {
name: "Switch"
});
state ? Expand() : Collapse();
};
const LoginToggle = async () => {
const cookie = Boolean(Lib.getJV("E/Ex_Cookies"));
const state = Lib.getV("Login", cookie);
const disp = state ? Transl("🟢 啟用檢測") : Transl("🔴 禁用檢測");
Lib.regMenu({
[disp]: {
func: () => {
if (state) Lib.setV("Login", false); else if (cookie) Lib.setV("Login", true); else {
alert(Transl("無保存的 Cookie, 無法啟用自動登入"));
return;
}
LoginToggle();
},
close: false
}
}, {
name: "Check"
});
Lib.regMenu({
[Transl("🍪 共享登入")]: SharedLogin
});
MenuToggle();
};
const GlobalMenuToggle = async () => {
Lib.storeListen(["Login", "Expand"], listen => {
listen.far && LoginToggle();
});
};
async function Injection() {
const cookie = Lib.getJV("E/Ex_Cookies");
const login = Lib.getV("Login", Boolean(cookie));
if (login && cookie) {
let CurrentTime = new Date();
let DetectionTime = Lib.local("DetectionTime");
DetectionTime = DetectionTime ? new Date(DetectionTime) : new Date(CurrentTime.getTime() + 11 * 60 * 1e3);
const Conversion = Math.abs(DetectionTime - CurrentTime) / (1e3 * 60);
if (Conversion >= 10) $Cookie.Verify(cookie);
}
if (Post_Page.test(url)) CreateFavoritesButton(); else if (Favorites_Page.test(url)) AddCustomFavorites();
LoginToggle();
GlobalMenuToggle();
}
async function SharedLogin() {
const Igneous = $Cookie.Get().igneous;
const AccountQuantity = Object.keys(Share).length;
let Select = $(`<select id="account-select" class="acc-select"></select>`), Value;
for (let i = 1; i <= AccountQuantity; i++) {
if (Share[i][0].value === Igneous) Value = i;
Select.append($("<option>").attr({
value: i
}).text(`${Transl("帳戶")} ${i}`));
}
CreateMenu(`
<div class="modal-background">
<div class="acc-modal">
<h1>${Transl("帳戶選擇")}</h1>
<div class="acc-select-flex">${Select.prop("outerHTML")}</div>
<div class="acc-button-flex">
<button class="modal-button" id="update">${Transl("更新")}</button>
<button class="modal-button" id="login">${Transl("登入")}</button>
</div>
</div>
</div>
`);
if (AccountQuantity === 0) {
Growl(Transl("首次使用請先更新"), "jGrowl", 2500);
$("#account-select").append($("<option>")).prop("disabled", true);
} else if (Value) $("#account-select").val(Value);
$(".modal-background").on("click", function (click) {
click.stopImmediatePropagation();
const target = click.target;
if (target.id === "login") {
$Cookie.ReAdd(Share[+$("#account-select").val()]);
} else if (target.id === "update") {
$Shared.Update().then(Data => {
if (Data) {
Share = Data;
Lib.setJV("Share", Data);
setTimeout(SharedLogin, 600);
}
});
} else if (target.className === "modal-background") {
DeleteMenu();
}
});
}
async function Cookie_Show(cookies) {
CreateMenu(`
<div class="modal-background">
<div class="show-modal">
<h1 style="text-align: center;">${Transl("確認選擇的 Cookies")}</h1>
<pre><b>${JSON.stringify(cookies, null, 4)}</b></pre>
<div style="text-align: right;">
<button class="modal-button" id="save">${Transl("確認保存")}</button>
<button class="modal-button" id="close">${Transl("取消退出")}</button>
</div>
</div>
</div>
`);
$(".modal-background").on("click", function (click) {
click.stopImmediatePropagation();
const target = click.target;
if (target.id === "save") {
Lib.setJV("E/Ex_Cookies", cookies);
Growl(Transl("保存成功!"), "jGrowl", 1500);
DeleteMenu();
} else if (target.className === "modal-background" || target.id === "close") {
DeleteMenu();
}
});
}
async function AutoGetCookie() {
let cookie_box = [];
for (const [name, value] of Object.entries($Cookie.Get())) {
cookie_box.push({
name: name,
value: value
});
}
cookie_box.length > 1 ? Cookie_Show(cookie_box) : alert(Transl("未獲取到 Cookies !!\n\n請先登入帳戶"));
}
async function ManualSetting() {
CreateMenu(`
<div class="modal-background">
<div class="set-modal">
<h1>${Transl("設置 Cookies")}</h1>
<form id="set_cookies">
<div id="input_cookies" class="set-box">
<label>[igneous]:</label><input class="set-list" type="text" name="igneous" placeholder="${Transl("要登入 Ex 才需要填寫")}"><br>
<label>[ipb_member_id]:</label><input class="set-list" type="text" name="ipb_member_id" placeholder="${Transl("必填項目")}" required><br>
<label>[ipb_pass_hash]:</label><input class="set-list" type="text" name="ipb_pass_hash" placeholder="${Transl("必填項目")}" required><hr>
<h3>${Transl("下方選填 也可不修改")}</h3>
<label>[sl]:</label><input class="set-list" type="text" name="sl" value="dm_2"><br>
<label>[sk]:</label><input class="set-list" type="text" name="sk"><br>
</div>
<button type="submit" class="modal-button" id="save">${Transl("確認保存")}</button>
<button class="modal-button" id="close">${Transl("退出選單")}</button>
</form>
</div>
</div>
`);
let cookie;
const textarea = $("<textarea>").attr({
style: "margin: 1.15rem auto 0 auto",
rows: 18,
cols: 40,
readonly: true
});
$("#set_cookies").on("submit", function (submit) {
submit.preventDefault();
submit.stopImmediatePropagation();
cookie = Array.from($("#set_cookies .set-list")).map(function (input) {
const value = $(input).val();
return value.trim() !== "" ? {
name: $(input).attr("name"),
value: value
} : null;
}).filter(Boolean);
textarea.val(JSON.stringify(cookie, null, 4));
$("#set_cookies div").append(textarea);
Growl(Transl("[確認輸入正確] 按下退出選單保存"), "jGrowl", 2500);
});
$(".modal-background").on("click", function (click) {
click.stopImmediatePropagation();
const target = click.target;
if (target.className === "modal-background" || target.id === "close") {
click.preventDefault();
target.id === "close" && cookie && Lib.setJV("E/Ex_Cookies", cookie);
DeleteMenu();
}
});
}
async function ViewSaveCookie() {
CreateMenu(`
<div class="modal-background">
<div class="set-modal">
<h1>${Transl("當前設置 Cookies")}</h1>
<div id="view_cookies" style="margin: 0.6rem"></div>
<button class="modal-button" id="save">${Transl("更改保存")}</button>
<button class="modal-button" id="close">${Transl("退出選單")}</button>
</div>
</div>
`);
const cookie = Lib.getJV("E/Ex_Cookies", {});
const textarea = $("<textarea>").attr({
rows: 20,
cols: 50,
id: "view_SC",
style: "margin-top: 1.25rem;"
});
textarea.val(JSON.stringify(cookie, null, 4));
$("#view_cookies").append(textarea);
$(".modal-background").on("click", function (click) {
click.stopImmediatePropagation();
const target = click.target;
if (target.id === "save") {
Lib.setJV("E/Ex_Cookies", JSON.parse($("#view_SC").val()));
Growl(Transl("已保存變更"), "jGrowl", 1500);
DeleteMenu();
} else if (target.className === "modal-background" || target.id === "close") {
DeleteMenu();
}
});
}
async function CookieInjection() {
try {
const cookie = Lib.getJV("E/Ex_Cookies");
if (cookie === null) throw new Error("No Cookies");
$Cookie.ReAdd(cookie);
} catch (error) {
alert(Transl("未檢測到可注入的 Cookies !!\n\n請從選單中進行設置"));
}
}
async function ClearLogin() {
$Cookie.Delete();
location.reload();
}
function CreateFavoritesButton() {
Lib.waitEl(["#gd1 div", "#gd2", "#gmid"], ([thumbnail, container, info]) => {
const path = location.pathname;
const save_key = md5(path);
const Favorites = Lib.getV("Favorites", {});
const favorite = Favorites[save_key];
const addfavorite = async Favorites => {
return new Promise((resolve, reject) => {
try {
const img = getComputedStyle(thumbnail);
const score = getComputedStyle(info.$q(".ir"));
const icon = info.$q("#gdc div");
const artist = info.$q("#gdn a");
const title = container.$q("#gj").$text() || container.$q("#gn").$text();
const [, gid, tid] = path.match(/\/g\/([^\/]+)\/([^\/]+)\//);
const detail = info.$q("#gdd");
const posted = detail.$q("tr:nth-child(1) .gdt2").$text();
const length = detail.$q("tr:nth-child(6) .gdt2").$text();
const tagData = new Map();
for (const a of info.$qa("#taglist tr a")) {
const tags = a.id.slice(3).replace(/[_]/g, " ").split(":");
if (!tagData.has(tags[0])) tagData.set(tags[0], []);
tagData.get(tags[0]).push(tags[1]);
}
const data = JSON.stringify({
gid: gid,
tid: tid,
domain: domain,
posted: posted,
length: length,
key: save_key,
tags: [...tagData],
score: score.backgroundPosition,
post_title: title,
artist_link: artist.href,
artist_text: artist.$text(),
icon_text: icon.$text(),
icon_class: icon.className,
img_width: img.width,
img_height: img.height,
img_url: img.background.match(/url\(["']?(.*?)["']?\)/)[1],
favorited_time: Lib.getDate("{year}-{month}-{date} {hour}:{minute}")
});
Lib.setV("Favorites", Object.assign(Favorites, {
[save_key]: LZString.compress(data, 9)
}));
resolve();
} catch (error) {
console.error(error);
reject();
}
});
};
favorite && addfavorite(Favorites);
const favoriteButton = Lib.createElement(container, "div", {
class: favorite ? "cancelFavorite" : "addFavorite",
text: favorite ? Transl("💘 取消收藏") : Transl("💖 添加收藏"),
on: {
type: "click",
listener: () => {
const Favorites = Lib.getV("Favorites", {});
if (Favorites[save_key]) {
delete Favorites[save_key];
Lib.setV("Favorites", Favorites);
favoriteButton.$text(Transl("💖 添加收藏"));
favoriteButton.$replaceClass("cancelFavorite", "addFavorite");
return;
}
addfavorite(Favorites).then(() => {
favoriteButton.$text(Transl("💘 取消收藏"));
favoriteButton.$replaceClass("addFavorite", "cancelFavorite");
});
}
}
});
}, {
raf: true
});
}
function httpRequest(url, func) {
GM_xmlhttpRequest({
method: "GET",
url: url,
responseType: "document",
onload: response => {
if (response.status === 200) {
func(response.response);
}
}
});
}
function AddCustomFavorites() {
const Favorites = Lib.getV("Favorites");
if (Favorites && Object.keys(Favorites).length > 0) {
Lib.waitEl(".ido", ido => {
let delete_object = "tr";
const select = ido.$q(".searchnav div:last-of-type select option[selected='selected']");
const usertags = {};
const favoritDB = Object.values(Favorites);
const mode = !select ? "t" : select.value;
if (!select) {
const newform = Lib.createElement("form", {
id: "favform",
name: "favform",
action: "",
method: "post",
innerHTML: `<input id="ddact" name="ddact" type="hidden" value=""><div class="itg gld"></div>`
});
ido.appendChild(newform);
}
if (mode === "t") delete_object = ".gl1t";
const RenderTags = async function () {
const nodes = [];
const tree = document.createTreeWalker(ido, NodeFilter.SHOW_TEXT, {
acceptNode: node => {
const parent = node.parentNode;
if (parent?.nodeName === "DIV" && parent.hasAttribute("title") && !parent.hasAttribute("id")) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_REJECT;
}
});
while (tree.nextNode()) {
nodes.push(tree.currentNode.parentElement);
}
nodes.forEach(node => {
const tags = usertags[node.title];
tags && (node.style.cssText = tags.cssText);
});
};
const GetTags = async function () {
if (Object.keys(usertags).length > 0) {
RenderTags();
return;
}
httpRequest("https://exhentai.org/mytags", root => {
for (const user of root.$qa("div[id^='usertag_']:not(#usertag_0)")) {
const input = user.$q("div:nth-of-type(2) input");
if (input.checked) {
const tag = user.$q("div.gt");
usertags[tag.title] = tag.style;
}
}
RenderTags();
});
};
let count = 0;
const fragment = Lib.createFragment;
const RenderWait = requestIdleCallback || ((cb, _) => requestAnimationFrame(cb));
const RenderCard = async function () {
if (fragment.hasChildNodes()) {
ido.$q("tbody")?.prepend(fragment);
ido.$q("#favform .gld")?.prepend(fragment);
requestAnimationFrame(GetTags);
}
};
for (const data of favoritDB) {
const json = JSON.parse(LZString.decompress(data));
const Pages = `<div>${json.length}</div>`;
const PostUrl = `<a href="https://${json.domain}/g/${json.gid}/${json.tid}/">`;
const PostName = `<div class="glink">${json.post_title}</div>`;
const Glfnote = `<div class="glfnote" style="display:none" id="favnote_${json.gid}"></div>`;
const Thumbnail = `<div class="${json.icon_class}">${json.icon_text}</div>`;
const ThumbnailCN = Thumbnail.replace('class="cs', 'class="cn');
const Position = `<div class="ir" style="background-position:${json.score};opacity:1"></div>`;
const PreviewImg = `<img style="height:${json.img_height}; width:${json.img_width};" alt="${json.post_title}" title="${json.post_title}" src="${json.img_url}">`;
const FullPreview = `
<div class="glcut" id="ic${json.gid}"></div>
<div class="glthumb" id="it${json.gid}" style="top:-179px;height:400px">
<div>${PreviewImg}</div>
`;
const Posted = `
<div style="border-color:#000;background-color:rgba(0,0,0,.1)"
onclick="popUp('https://${json.domain}/gallerypopups.php?gid=${json.gid}&t=${json.tid}&act=addfav',675,415)"
id="posted_${json.gid}" title="Favorites 0">${json.posted}
</div>
`;
const Postedpop = Posted.replace("posted_", "postedpop_");
const Gldown = `
<div class="gldown">
<a href="https://${json.domain}/gallerytorrents.php?gid=${json.gid}&t=${json.tid}"
onclick="return popUp('https://${json.domain}/gallerytorrents.php?gid=${json.gid}&t=${json.tid}',610,590)"
rel="nofollow"><img src="https://${json.domain}/img/t.png" alt="T" title="Show torrents">
</a>
</div>
`;
const unFavorite = `
<div class="lc">
<div id="${json.key}" class="unFavorite">💔</div>
</div>
`;
if (mode === "m" || mode === "p") {
const tr = Lib.createElement("tr");
tr.$iHtml(`
<td class="gl1m glcat">${Thumbnail}</td>
<td class="gl2m">
${FullPreview}
<div>
<div>
${Thumbnail}
${Postedpop}
</div>
<div>
${Position}
${Pages}
</div>
</div>
</div>
${Posted}
</td>
<td class="gl6m">${Gldown}</td>
<td class="gl3m glname" onmouseover="show_image_pane(${json.gid});preload_pane_image(0,0)" onmouseout="hide_image_pane()">
${PostUrl}
${PostName}
${Glfnote}
</a>
</td>
<td class="gl4m">
${Position}
</td>
<td class="glfm glfav">${json.favorited_time}</td>
<td class="glfm" style="text-align:center; padding-left:3px">
${unFavorite}
</td>
`.replace(/>\s+</g, "><"));
fragment.prepend(tr);
} else if (mode === "l") {
const tr = Lib.createElement("tr");
const posted = json.posted.split(" ");
tr.$iHtml(`
<tr>
<td class="gl1c glcat">${ThumbnailCN}</td>
<td class="gl2c">
${FullPreview}
<div>
<div>
${ThumbnailCN}
${Postedpop}
</div>
<div>
${Position}
${Pages}
</div>
</div>
</div>
<div>
${Posted}
${Position}
${Gldown}
</div>
</td>
<td class="gl3c glname" onmouseover="show_image_pane(${json.gid});preload_pane_image(0,0)" onmouseout="hide_image_pane()">
${PostUrl}
${PostName}
<div>
${(() => {
let count = 0;
let result = "";
for (const [tagCategory, tagList] of json.tags) {
for (const tag of tagList) {
if (count >= 10) break;
result += `<div class="gt" title="${tagCategory}:${tag}">${tag}</div>`;
count++;
}
if (count >= 10) break;
}
return result;
})()}
</div>
${Glfnote}
</a>
</td>
<td class="glfc glfav">
<p>${posted[0]}</p>
<p>${posted[1]}</p>
</td>
<td class="glfc" style="text-align:center; padding-left:3px">
${unFavorite}
</td>
</tr>
`.replace(/>\s+</g, "><"));
fragment.prepend(tr);
} else if (mode === "e") {
const tr = Lib.createElement("tr");
tr.$iHtml(`
<tr>
<td class="gl1e" style="width:250px">
<div style="height: ${json.img_height}; width:250px">
${PostUrl}
${PreviewImg}
</a>
</div>
</td>
<td class="gl2e">
<div>
<div class="gl3e">
${ThumbnailCN}
${Posted}
${Position}
<div><a href="${json.artist_link}">${json.artist_text}</a></div>
${Pages}
${Gldown}
<div>
<p>Favorited:</p><p>${json.favorited_time}</p>
</div>
</div>
${PostUrl}
<div class="gl4e glname" style="min-height:${json.img_height}">
${PostName}
<div>
<table>
<tbody>
${json.tags.map(([tagCategory, tagList]) => {
return `
<tr>
<td class="tc">${tagCategory}</td>
<td>
${tagList.map(tag => `<div class="gtl" title="${tagCategory}:${tag}">${tag}</div>`).join("")}
</td>
</tr>
`;
}).join("")}
</tbody>
</table>
</div>
${Glfnote}
</div>
</a>
</div>
</td>
<td class="glfe" style="text-align:center; padding-left:8px">
${unFavorite}
</td>
</tr>
`.replace(/>\s+</g, "><"));
fragment.prepend(tr);
} else if (mode === "t") {
const div = Lib.createElement("div", {
class: "gl1t"
});
div.$iHtml(`
<div class="gl4t glname glft">
<div>
${PostUrl}
<span class="glink">${json.post_title}</span>
</a>
</div>
<div style="transform: translateY(-70%);">
${unFavorite}
</div>
</div>
<div class="gl3t" style="height: ${json.img_height}; width:250px">
${PostUrl}
${PreviewImg}
</a>
</div>
${Glfnote}
<div class="gl5t">
<div>
${Thumbnail}
${Posted}
</div>
<div>
${Position}
${Pages}
${Gldown}
</div>
</div>
`.replace(/>\s+</g, "><"));
fragment.prepend(div);
}
++count;
if (count === 50) {
count = 0;
RenderWait(RenderCard, {
timeout: 1e3
});
}
}
RenderCard();
Lib.onEvent(ido, "click", event => {
const target = event.target;
if (target.className === "unFavorite") {
const Favorites = Lib.getV("Favorites");
delete Favorites[target.id];
Lib.setV("Favorites", Favorites);
target.closest(delete_object).remove();
}
});
});
}
}
return {
Injection: Injection
};
})(CookieFactory(), SharedFactory()).then(Main => {
Main.Injection();
});
async function Growl(message, theme, life) {
$.jGrowl(`  ${message}  `, {
theme: theme,
life: life,
speed: "slow"
});
}
function SharedFactory() {
async function Get() {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
responseType: "json",
url: "https://raw.githubusercontent.com/Canaan-HS/Script-DataBase/refs/heads/main/Share/ExShare.json",
onload: response => {
if (response.status === 200) {
const data = response.response;
if (typeof data === "object" && Object.keys(data).length > 0) {
resolve(data);
} else {
console.error(Transl("請求為空數據"));
resolve({});
}
} else {
console.error(Transl("連線異常,更新地址可能是錯的"));
resolve({});
}
},
onerror: error => {
console.error(Transl("請求錯誤: "), error);
resolve({});
}
});
});
}
async function Update() {
const Shared = await Get();
if (Object.keys(Shared).length > 0) {
const localHash = md5(Lib.getV("Share", ""));
const remoteHash = md5(JSON.stringify(Shared));
if (localHash !== remoteHash) {
Growl(Transl("共享數據更新完成"), "jGrowl", 1500);
return Shared;
} else {
Growl(Transl("共享數據無需更新"), "jGrowl", 1500);
}
} else {
Growl(Transl("共享數據獲取失敗"), "jGrowl", 2500);
}
return false;
}
return {
Update: Update
};
}
function CookieFactory() {
const Today = new Date();
Today.setFullYear(Today.getFullYear() + 1);
const Expires = Today.toUTCString();
const UnixUTC = new Date(0).toUTCString();
let RequiredCookie = ["ipb_member_id", "ipb_pass_hash"];
if (domain == "exhentai.org") RequiredCookie.unshift("igneous");
return {
Get: () => {
return Lib.cookie().split("; ").reduce((acc, cookie) => {
const [name, value] = cookie.split("=");
acc[decodeURIComponent(name)] = decodeURIComponent(value);
return acc;
}, {});
},
Add: function (CookieObject) {
Lib.local("DetectionTime", {
value: Lib.getDate()
});
for (const Cookie of CookieObject) {
Lib.cookie(`${encodeURIComponent(Cookie.name)}=${encodeURIComponent(Cookie.value)}; domain=.${domain}; path=/; expires=${Expires};`);
}
location.reload();
},
Delete: function () {
Object.keys(this.Get()).forEach(Name => {
Lib.cookie(`${Name}=; expires=${UnixUTC}; path=/;`);
Lib.cookie(`${Name}=; expires=${UnixUTC}; path=/; domain=.${domain}`);
});
},
ReAdd: function (Cookies) {
this.Delete();
this.Add(Cookies);
},
Verify: function (Cookies) {
const Cookie = this.Get();
const VCookie = new Set(Object.keys(Cookie));
const Result = RequiredCookie.every(key => VCookie.has(key) && Cookie[key] !== "mystery");
if (!Result) {
this.ReAdd(Cookies);
} else {
Lib.local("DetectionTime", {
value: Lib.getDate()
});
}
}
};
}
function Language() {
const Word = Lib.translMatcher({
Traditional: {},
Simplified: {
"🍪 共享登入": "🍪 共享登录",
"🟢 啟用檢測": "🟢 启用检测",
"🔴 禁用檢測": "🔴 禁用检测",
"📂 展開菜單": "📂 展开菜单",
"📁 摺疊菜單": "📁 折叠菜单",
"📜 自動獲取": "📜 自动获取",
"📝 手動輸入": "📝 手动输入",
"🔍 查看保存": "🔍 查看已保存",
"🔃 手動注入": "🔃 手动注入",
"🗑️ 清除登入": "🗑️ 清除登录信息",
"💖 添加收藏": "💖 添加收藏",
"💘 取消收藏": "💘 取消收藏",
"帳戶": "账号",
"更新": "更新",
"登入": "登录",
"首次使用請先更新": "首次使用请先更新",
"確認選擇的 Cookies": "确认所选 Cookies",
"確認保存": "确认保存",
"取消退出": "取消",
"退出選單": "关闭菜单",
"保存成功!": "保存成功!",
"更改保存": "保存更改",
"已保存變更": "更改已保存",
"設置 Cookies": "设置 Cookies",
"要登入 Ex 才需要填寫": "仅登录 Ex 时需要填写",
"必填項目": "必填项",
"下方選填 也可不修改": "以下为选填项,可不修改",
"[確認輸入正確] 按下退出選單保存": "[确认输入无误] 点击关闭菜单保存",
"當前設置 Cookies": "当前 Cookies 设置",
"帳戶選擇": "选择账号",
"未獲取到 Cookies !!\n\n請先登入帳戶": "未获取到 Cookies!\n\n请先登录账号",
"未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "未检测到可注入的 Cookies!\n\n请在菜单中进行设置",
"共享數據更新完成": "共享数据更新完成",
"共享數據無需更新": "共享数据无需更新",
"共享數據獲取失敗": "共享数据获取失败",
"無保存的 Cookie, 無法啟用自動登入": "没有已保存的 Cookie,无法启用自动登录",
"請求為空數據": "请求数据为空",
"連線異常,更新地址可能是錯的": "连接异常,更新地址可能不正确",
"請求錯誤: ": "请求错误:"
},
Japan: {
"🍪 共享登入": "🍪 共有ログイン",
"🟢 啟用檢測": "🟢 検出を有効化",
"🔴 禁用檢測": "🔴 検出を無効化",
"📂 展開菜單": "📂 メニュー展開",
"📁 摺疊菜單": "📁 メニュー折りたたみ",
"📜 自動獲取": "📜 自動取得",
"📝 手動輸入": "📝 手動入力",
"🔍 查看保存": "🔍 保存を表示",
"🔃 手動注入": "🔃 手動注入",
"🗑️ 清除登入": "🗑️ ログインをクリア",
"💖 添加收藏": "💖 お気に入りに追加",
"💘 取消收藏": "💘 お気に入りから削除",
"帳戶": "アカウント",
"更新": "更新",
"登入": "ログイン",
"首次使用請先更新": "初めてご利用の際は、先に更新してください",
"確認選擇的 Cookies": "選択したCookieを確認",
"確認保存": "保存を確認",
"取消退出": "終了をキャンセル",
"退出選單": "メニューを終了",
"保存成功!": "保存に成功しました!",
"更改保存": "変更を保存",
"已保存變更": "変更が保存されました",
"設置 Cookies": "Cookieを設定",
"要登入 Ex 才需要填寫": "Exログインにのみ必要",
"必填項目": "必須項目",
"下方選填 也可不修改": "以下は任意、変更しなくても構いません",
"[確認輸入正確] 按下退出選單保存": "[入力が正しいことを確認] メニュー終了を押して保存",
"當前設置 Cookies": "現在のCookie設定",
"帳戶選擇": "アカウント選択",
"未獲取到 Cookies !!\n\n請先登入帳戶": "Cookieを取得できませんでした!\n\nまずアカウントにログインしてください",
"未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "注入可能なCookieが検出されませんでした!\n\nメニューから設定してください",
"共享數據更新完成": "共有データの更新が完了しました",
"共享數據無需更新": "共有データの更新は不要です",
"共享數據獲取失敗": "共有データの取得に失敗しました",
"無保存的 Cookie, 無法啟用自動登入": "保存されたCookieがないため、自動ログインを有効にできません",
"請求為空數據": "リクエストにデータがありません",
"連線異常,更新地址可能是錯的": "接続エラー、更新アドレスが間違っている可能性があります",
"請求錯誤: ": "リクエストエラー: "
},
Korea: {
"🍪 共享登入": "🍪 공유 로그인",
"🟢 啟用檢測": "🟢 감지 활성화",
"🔴 禁用檢測": "🔴 감지 비활성화",
"📂 展開菜單": "📂 메뉴 펼치기",
"📁 摺疊菜單": "📁 메뉴 접기",
"📜 自動獲取": "📜 자동 가져오기",
"📝 手動輸入": "📝 수동 입력",
"🔍 查看保存": "🔍 저장된 항목 보기",
"🔃 手動注入": "🔃 수동 주입",
"🗑️ 清除登入": "🗑️ 로그인 정보 삭제",
"💖 添加收藏": "💖 즐겨찾기에 추가",
"💘 取消收藏": "💘 즐겨찾기 제거",
"確認選擇的 Cookies": "선택한 쿠키 확인",
"帳戶": "계정",
"更新": "업데이트",
"登入": "로그인",
"首次使用請先更新": "처음 사용하기 전에 먼저 업데이트해 주세요",
"確認保存": "저장 확인",
"取消退出": "종료 취소",
"退出選單": "메뉴 종료",
"保存成功!": "저장 성공!",
"更改保存": "변경사항 저장",
"已保存變更": "변경사항이 저장되었습니다",
"設置 Cookies": "쿠키 설정",
"要登入 Ex 才需要填寫": "Ex 로그인에만 필요",
"必填項目": "필수 항목",
"下方選填 也可不修改": "아래는 선택사항, 변경하지 않아도 됩니다",
"[確認輸入正確] 按下退出選單保存": "[입력이 정확한지 확인] 메뉴 종료를 눌러 저장",
"當前設置 Cookies": "현재 설정된 쿠키",
"帳戶選擇": "계정 선택",
"未獲取到 Cookies !!\n\n請先登入帳戶": "쿠키를 가져오지 못했습니다!\n\n먼저 계정에 로그인해 주세요",
"未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "주입 가능한 쿠키가 감지되지 않았습니다!\n\n메뉴에서 설정해 주세요",
"共享數據更新完成": "공유 데이터 업데이트 완료",
"共享數據無需更新": "공유 데이터 업데이트 불필요",
"共享數據獲取失敗": "공유 데이터 가져오기 실패",
"無保存的 Cookie, 無法啟用自動登入": "저장된 쿠키가 없어 자동 로그인을 활성화할 수 없습니다",
"請求為空數據": "요청에 데이터가 없습니다",
"連線異常,更新地址可能是錯的": "연결 오류, 업데이트 주소가 잘못되었을 수 있습니다",
"請求錯誤: ": "요청 오류: "
},
Russia: {
"🍪 共享登入": "🍪 Общий вход",
"🟢 啟用檢測": "🟢 Включить обнаружение",
"🔴 禁用檢測": "🔴 Отключить обнаружение",
"📂 展開菜單": "📂 Развернуть меню",
"📁 摺疊菜單": "📁 Свернуть меню",
"📜 自動獲取": "📜 Автоматическое получение",
"📝 手動輸入": "📝 Ручной ввод",
"🔍 查看保存": "🔍 Просмотр сохраненного",
"🔃 手動注入": "🔃 Ручное внедрение",
"🗑️ 清除登入": "🗑️ Очистить вход",
"💖 添加收藏": "💖 Добавить в избранное",
"💘 取消收藏": "💘 Удалить из избранного",
"帳戶": "Аккаунт",
"更新": "Обновить",
"登入": "Войти",
"首次使用請先更新": "Пожалуйста, обновите перед первым использованием",
"確認選擇的 Cookies": "Подтвердить выбранные Cookies",
"確認保存": "Подтвердить сохранение",
"取消退出": "Отменить выход",
"退出選單": "Выйти из меню",
"保存成功!": "Сохранение успешно!",
"更改保存": "Сохранить изменения",
"已保存變更": "Изменения сохранены",
"設置 Cookies": "Настройка Cookies",
"要登入 Ex 才需要填寫": "Требуется только для входа в Ex",
"必填項目": "Обязательное поле",
"下方選填 也可不修改": "Необязательно ниже, изменения не требуются",
"[確認輸入正確] 按下退出選單保存": "[Подтвердите правильность ввода] Нажмите Выйти из меню для сохранения",
"當前設置 Cookies": "Текущие настройки Cookies",
"帳戶選擇": "Выбор аккаунта",
"未獲取到 Cookies !!\n\n請先登入帳戶": "Cookies не получены !!\n\nПожалуйста, сначала войдите в аккаунт",
"未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "Не обнаружены Cookies для внедрения !!\n\nПожалуйста, настройте в меню",
"共享數據更新完成": "Обновление общих данных завершено",
"共享數據無需更新": "Обновление общих данных не требуется",
"共享數據獲取失敗": "Ошибка получения общих данных",
"無保存的 Cookie, 無法啟用自動登入": "Нет сохраненных cookies, невозможно включить автоматический вход",
"請求為空數據": "Запрос содержит пустые данные",
"連線異常,更新地址可能是錯的": "Ошибка соединения, адрес обновления может быть неверным",
"請求錯誤: ": "Ошибка запроса: "
},
English: {
"🍪 共享登入": "🍪 Shared Login",
"🟢 啟用檢測": "🟢 Enable Detection",
"🔴 禁用檢測": "🔴 Disable Detection",
"📂 展開菜單": "📂 Expand Menu",
"📁 摺疊菜單": "📁 Collapse Menu",
"📜 自動獲取": "📜 Auto Retrieve",
"📝 手動輸入": "📝 Manual Input",
"🔍 查看保存": "🔍 View Saved",
"🔃 手動注入": "🔃 Manual Injection",
"🗑️ 清除登入": "🗑️ Clear Login",
"💖 添加收藏": "💖 Add to Favorites",
"💘 取消收藏": "💘 Remove from Favorites",
"帳戶": "Account",
"更新": "Update",
"登入": "Login",
"首次使用請先更新": "Please update before first use",
"確認選擇的 Cookies": "Confirm Selected Cookies",
"確認保存": "Confirm Save",
"取消退出": "Cancel Exit",
"退出選單": "Exit Menu",
"保存成功!": "Save Successful!",
"更改保存": "Save Changes",
"已保存變更": "Changes Saved",
"設置 Cookies": "Set Cookies",
"要登入 Ex 才需要填寫": "Required for Ex Login Only",
"必填項目": "Required Field",
"下方選填 也可不修改": "Optional Fields Below - No Changes Required",
"[確認輸入正確] 按下退出選單保存": "[Confirm Input is Correct] Press Exit Menu to Save",
"當前設置 Cookies": "Current Cookie Settings",
"帳戶選擇": "Account Selection",
"未獲取到 Cookies !!\n\n請先登入帳戶": "No Cookies Retrieved!\n\nPlease Login First",
"未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "No Injectable Cookies Detected!\n\nPlease Configure in Menu",
"共享數據更新完成": "Shared Data Update Complete",
"共享數據無需更新": "Shared Data Update Not Needed",
"共享數據獲取失敗": "Shared Data Retrieval Failed",
"無保存的 Cookie, 無法啟用自動登入": "No Saved Cookies - Unable to Enable Auto-Login",
"請求為空數據": "Request Contains No Data",
"連線異常,更新地址可能是錯的": "Connection Error - Update Address May Be Incorrect",
"請求錯誤: ": "Request Error: "
}
});
return {
Transl: Str => Word[Str] ?? Str
};
}
})();