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();
}
})();
このツールの使用は、自己責任でのご利用をお願いします。本ツールを使用する際は、ご自身の判断と責任のもとで行ってください。本ツールの利用により生じた、いかなる結果、帰結についても筆者は責任を負いません。また、会期最終日までの動作も保証できません。
本ツールを使用した方は、チップで応援していただけると励みになります。
コメント
144日前だと、6時間じゃ厳しいかもですね。
3日前直後、2日前の8時、当日朝が狙い目です
もっと動かしとかなきゃ行けないのか、熱くなり充電切れそう😂
3日前直後とは23日→24日の深夜1時頃?と25日の8時、という認識で合ってますか?
合ってます
ありがとうございます!始発チャレンジしたいので、東変更頑張ります!