見出し画像

EXPO2025大阪万博 来場予約自動化ツールの配布

万博公式サイトで使える来場予約自動化ツールを無料配布します。EXPO2025デジタルチケット | 2025年日本国際博覧会協会からの予約にのみ対応しています。iOSのMakeoverで動くように書かれたコードですが、勿論AndroidでもFirefoxなどを用いてJSを弄ることができれば可能です。PCでも、適宜Tampermonkeyなど用いてください。使い方は、以下のコードをコピー&ペーストで万博サイトで動くように書き込み(Makeoverを使用している場合は、JSの方にペーストしてください)、来場予約の画面で希望の入場時刻・ゲートを選択(満員でも選択できます)し、右下に表示されたオンオフトグルスイッチをオンにして、その画面を開いたままで放置することです。予約を取り終えると、自動で停止するようになっています。

私自身Noteの仕様をよくわかっていませんが、随時更新しようと考えています。
(8/7 更新)
更新の通知は、以下のTwitterアカウントで行います。フォローしていただけると励みになります。不具合などに関しても、以下のアカウントでお願いします。
Twitter


(function() {
    'use strict';

    let running = false;
    let loopController = null;

    const container = document.createElement('div');
    container.id = 'auto-reserve-toggle';
    Object.assign(container.style, {
        position: 'fixed',
        bottom: '20px',
        right: '20px',
        zIndex: '9999',
        backgroundColor: 'rgba(255,255,255,0.85)',
        padding: '10px 16px',
        borderRadius: '12px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
        fontFamily: 'sans-serif',
        display: 'flex',
        alignItems: 'center',
        gap: '10px',
        userSelect: 'none'
    });

    const label = document.createElement('label');
    label.textContent = '自動予約';
    Object.assign(label.style, {
        fontSize: '14px',
        fontWeight: 'bold',
        color: '#333',
        cursor: 'pointer',
    });

    const toggle = document.createElement('input');
    toggle.type = 'checkbox';
    toggle.id = 'toggle-switch';
    toggle.style.display = 'none';

    const slider = document.createElement('span');
    Object.assign(slider.style, {
        position: 'relative',
        display: 'inline-block',
        width: '96px',
        height: '52px',
        backgroundColor: '#ccc',
        borderRadius: '52px',
        transition: '0.4s',
        cursor: 'pointer',
    });

    const circle = document.createElement('span');
    Object.assign(circle.style, {
        position: 'absolute',
        height: '44px',
        width: '44px',
        left: '4px',
        top: '4px',
        backgroundColor: 'white',
        borderRadius: '50%',
        transition: '0.4s',
    });

    slider.appendChild(circle);
    container.appendChild(label);
    container.appendChild(toggle);
    container.appendChild(slider);
    document.body.appendChild(container);


    const removeDisabledAttrs = () => {
    if (!running) {
        document.querySelectorAll('[data-disabled="true"]').forEach(el => {
            el.removeAttribute('data-disabled');
        });
    }
};

    const updateReservationButtons = () => {
    if (running) {
        document.querySelectorAll('div[role="button"].style_main__button__Z4RWX').forEach(el => {
            el.setAttribute('data-disabled', 'true');
        });
    }
};

    const observer = new MutationObserver(removeDisabledAttrs);
    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['data-disabled']
    });
    removeDisabledAttrs();

    slider.onclick = () => {
        toggle.checked = !toggle.checked;
        slider.style.backgroundColor = toggle.checked ? '#4cd964' : '#ccc';
        circle.style.transform = toggle.checked ? 'translateX(44px)' : 'translateX(0)';
        running = toggle.checked;
        updateReservationButtons();
        if (running) {
            startLoop();
        } else {
            stopLoop();
        }
    };

    const sleep = ms => new Promise(r => setTimeout(r, ms));

    const clickIfExists = async selector => {
        const btn = document.querySelector(selector);
        if (btn) {
            await sleep(500 + Math.random() * 3000);
            btn.click();
            return true;
        }
        return false;
    };

    async function startLoop() {
        console.log('自動処理開始');
        loopController = true;

        while (loopController && running) {
            console.log('ループ開始');

            const clicked1 = await clickIfExists('button.basic-btn.type2.style_full__ptzZq');
            if (!clicked1) {
                console.log('最初のボタンが見つかりませんでした');
                break;
            }
            await sleep(500);

            const clicked2 = await clickIfExists('button.style_next_button__N_pbs');
            if (!clicked2) {
                console.log('次のボタンが見つかりませんでした');
            }
            await sleep(500);

            let found = false;
            const timeout = 10000;
            const interval = 300;
            const maxTries = timeout / interval;

            for (let i = 0; i < maxTries; i++) {
                if (!running) return;

                const normalClose = document.querySelector('a.basic-btn.type3.modal-close');
                const finalClose = document.querySelector('a.basic-btn.type3.modal-close.style_close_btn__FGHTz');

                if (normalClose && !finalClose) {
                    await sleep(500 + Math.random() * 1000);
                    normalClose.click();
                    console.log('通常の閉じるボタンをクリック');
                    found = true;
                    break;
                }

                if (finalClose) {
                    await sleep(500 + Math.random() * 1000);
                    console.log('特別な閉じるボタンをクリック → 処理終了');
                    toggle.checked = false;
                    slider.style.backgroundColor = '#ccc';
                    circle.style.transform = 'translateX(0)';
                    running = false;
                    return;
                }

                await sleep(interval);
            }

            if (!found) {
                console.log('モーダルボタンが見つかりませんでした(待機後)');
                break;
            }

            await sleep(500);
        }

        console.log('ループ停止');
    }

    function stopLoop() {
        loopController = false;
        console.log('自動処理オフ');
        removeDisabledAttrs();
    }
})();

このツールの使用は、自己責任でのご利用をお願いします。本ツールを使用する際は、ご自身の判断と責任のもとで行ってください。本ツールの利用により生じた、いかなる結果、帰結についても筆者は責任を負いません。また、会期最終日までの動作も保証できません。


本ツールを使用した方は、チップで応援していただけると励みになります。

いいなと思ったら応援しよう!

コメント

5
Ponta
Ponta

こちらは通期パスや夏パスでも可能ですか?満員になっている場合、その時間帯の選択は出来ないのですが…

ペンギン
ペンギン

可能です。

Ponta
Ponta

夏パスは8月分までしか表示されないので出来ませんでした…ですが返信ありがとうございます<(_ _)>

chi
chi

この方法は複数枚のチケットまとめて予約でも可能ですか??
その場合、特別な設定が必要でしょうか?

またさらに、パビリオンの先着予約に関しても同様の方法があるのでしょうか?

ログイン または 会員登録 するとコメントできます。
EXPO2025大阪万博 来場予約自動化ツールの配布|ペンギン
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1