見出し画像

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

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

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


// ==UserScript==
// @name         来場自動予約
// @namespace    http://tampermonkey.net/
// @version      2025-08-20
// @description  自動予約をON/OFFできるトグルUIをページ右下に表示します
// @author       You
// @match        https://ticket.expo2025.or.jp/*
// @icon         https://ticket.expo2025.or.jp/favicon.ico
// @grant        none
// ==/UserScript==

(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();
    }
})();

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


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

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

コメント

14
ペンギン
ペンギン

4日前だと、6時間じゃ厳しいかもですね。
3日前直後、2日前の8時、当日朝が狙い目です

laimelu
laimelu

もっと動かしとかなきゃ行けないのか、熱くなり充電切れそう😂
3日前直後とは23日→24日の深夜1時頃?と25日の8時、という認識で合ってますか?

ペンギン
ペンギン

合ってます

laimelu
laimelu

ありがとうございます!始発チャレンジしたいので、東変更頑張ります!

ログイン または 会員登録 するとコメントできます。
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