熊貓 Eagle 支援

自動開啟 Exhentai 原圖並將其加入 Eagle

// ==UserScript==
// @name         熊貓 Eagle 支援
// @name:ja      Exhentaiイーグルサポート
// @name:en      Exhentai Eagle Supper
// @description  自動開啟 Exhentai 原圖並將其加入 Eagle
// @description:ja  Exhentaiのオリジナル畫像を自動的に開き、Eagleに追加します
// @description:en  Automatically open Exhentai original images and add them to Eagle
// @author       Max
// @namespace    https://greasyfork.org/zh-TW/users/1021017-max46656
// @version      1.0.2
// @match        *://exhentai.org/s/*
// @match        *://e-hentai.org/s/*
// @match        *://exhentai.org/g/*
// @match        *://e-hentai.org/g/*
// @match        *://*.hath.network*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        window.close
// @icon         https://exhentai.org/favicon.ico
// @license MPL2.0
// ==/UserScript==

class PicTrioFactory {
    constructor() {
        this.url = window.location.href;
        this.observer = new MutationObserver(this.checkTitleChange.bind(this));
        this.observer.observe(document.querySelector('title'), { subtree: true, characterData: true, childList: true });
    }

    checkTitleChange(mutations) {
        if (this.url !== window.location.href) {
            this.url = window.location.href;
            this.createInstance();
        }
    }

    getToWork() {
        const currentUrl = window.location.href;
        if (currentUrl.match(/.*\.hath\.network.*\/(h|om)/)) {
            const eagleAdder = new EagleImageAdder();
            eagleAdder.addImageToEagle();
        } else if (currentUrl.match(/.*:\/\/(ex|e-)?hentai\.org\/s\/.*/)) {
            const opener = new OriginalPicOpener();
            opener.savePicInfo();
            opener.openOriginalPic();
        } else if (currentUrl.match(/.*:\/\/(ex|e-)?hentai\.org\/g\/.*/)) {
            const albumManager = new AlbumPageManager();
            albumManager.addButton();
            albumManager.saveAlbumInfo();
        }
    }
}


class AlbumPageManager {
    constructor() {
        this.isAuto = GM_getValue('isAuto', false);
    }

    addButton() {
        const container = document.querySelector('#gd2');
        if (!container) return;

        const button = document.createElement('button');
        this.updateButtonText(button);
        this.styleButton(button);

        button.addEventListener('click', () => {
            this.isAuto = !this.isAuto;
            GM_setValue('isAuto', this.isAuto);
            this.updateButtonText(button);
        });

        container.appendChild(button);
    }

    updateButtonText(button) {
        button.textContent = this.isAuto ? 'AutoEagle: On' : 'AutoEagle: Off';
    }

    styleButton(button) {
        button.style.padding = '5px';
        button.style.backgroundColor = 'rgb(79, 83, 91)';
        button.style.color = 'rgb(241, 241, 242)';
        button.style.border = '1px solid #ccc';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.margin = '5px';
    }

    saveAlbumInfo() {
        const urlID = window.location.pathname.split('/')[2];
        let albumTitle = document.title.replace(/ - ExHentai\.org$/, '').replace(/ - E-Hentai\.org$/, '');
        //console.log("albumTitle",albumTitle);
        const albumData = GM_getValue('albumData', {});
        albumData[urlID] = {
            albumUrl: window.location.href,
            albumTitle: albumTitle,
        };
        GM_setValue('albumData', albumData);
    }
}

class OriginalPicOpener {
    constructor() {
        this.isAuto = GM_getValue('isAuto', false);
        //console.log("isAuto", this.isAuto);
    }

    openOriginalPic() {
        if (!this.isAuto) return;

        const links = document.querySelectorAll('a');
        for (const link of links) {
            if (link.textContent.includes('original')) {
                link.click();
                break;
            }
        }
    }

    savePicInfo() {
        const currentUrl = window.location.href;
        const picIDMatch = currentUrl.match(/\/s\/(.*?)\/(.*?)$/);
        if (!picIDMatch) return;

        const albumID = picIDMatch[2].split('-')[0];
        const picID = picIDMatch[1];
        const albumData = GM_getValue('albumData', {});
        const albumInfo = albumData[albumID] || {};
        const picData = GM_getValue('picData', {});
        picData[picID] = {
            albumUrl: albumInfo.albumUrl,
            albumTitle: albumInfo.albumTitle,
        };
        GM_setValue('picData', picData);
        //console.log(albumInfo);
    }
}

class EagleImageAdder {
    constructor() {
        this.EAGLE_SERVER_URL = "http://localhost:41595";
        this.EAGLE_IMPORT_API_URL = `${this.EAGLE_SERVER_URL}/api/item/addFromURL`;
        this.isAuto = GM_getValue('isAuto', true);
    }

    addImageToEagle() {
        if (!this.isAuto) return;

        const imageData = this.getImageData();
        GM_xmlhttpRequest({
            url: this.EAGLE_IMPORT_API_URL,
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            data: JSON.stringify(imageData),
            onload: function(response) {
                console.log('Image added to Eagle:', response);
                if (response.status >= 200 && response.status < 300) {
                    window.close();
                } else {
                    console.error('Failed to add image to Eagle:', response);
                }
            },
            onerror: function(error) {
                console.error('Failed to add image to Eagle:', error);
            }
        });
    }

    getImageData() {
        const imageUrl = window.location.href;
        let picIDMatch = imageUrl.match(/\/h\/(.{10})/);
        if (picIDMatch) {
        } else {
            // 第二種正則表達式匹配 om/ 格式的 URL
            picIDMatch = imageUrl.match(/\/om\/\d+\/(.{10})/);
        }
        //console.log(picIDMatch);
        const picID = picIDMatch[1];
        //console.log("picIDMatch",picIDMatch);
        const picData = GM_getValue('picData', {});
        const picInfo = picData[picID] || {};
        //console.log("picID",picID,"picInfo",picInfo);
        return {
            url: imageUrl,
            name: `${picInfo.albumTitle} - ${imageUrl.split('/').pop()}`,
            website: picInfo.albumUrl ?? imageUrl
        };
    }
}

class DataCleaner {
    constructor() {
        this.registerMenu();
    }

    registerMenu() {
        GM_registerMenuCommand('Clean Old Data', this.cleanOldData.bind(this));
    }

    cleanOldData() {
        const albumData = GM_getValue('albumData', {});
        const picData = GM_getValue('picData', {});

        let albumDataDeleted = 0;
        let picDataDeleted = 0;

        for (const key in albumData) {
            delete albumData[key];
            albumDataDeleted++;
        }

        for (const key in picData) {
            delete picData[key];
            picDataDeleted++;
        }

        GM_setValue('albumData', albumData);
        GM_setValue('picData', picData);

        console.log(`Old data cleaned. Deleted ${albumDataDeleted} albumData and ${picDataDeleted} picData entries.`);
    }
}

const picPage = new PicTrioFactory();
picPage.getToWork();
const dataCleaner = new DataCleaner();