Greasy Fork is available in English.
Sets the tour's starting point from the itinerary and prevents booking the same club in the same week. Includes selector fixes, a simplified settings form, a stop button, and complete storage cleanup. Now with dynamic start/end dates.
当前为
// ==UserScript==
// @name Popmundo Fixed Itinerary Booker
// @namespace http://tampermonkey.net/
// @version 4.9
// @description Sets the tour's starting point from the itinerary and prevents booking the same club in the same week. Includes selector fixes, a simplified settings form, a stop button, and complete storage cleanup. Now with dynamic start/end dates.
// @author Gemini
// @match https://*.popmundo.com/*
// @grant GM_addStyle
// @grant unsafeWindow
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- ⚙️ DYNAMIC DATE CONFIGURATION ---
const getFormattedDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const today = new Date();
const sevenDaysFromNow = new Date();
sevenDaysFromNow.setDate(today.getDate() + 7);
// --- ⚙️ DEFAULT CONFIGURATION ---
const DEFAULTS = {
INITIAL_CITY: "são paulo",
SHOW_TIMES: ["14:00:00", "22:00:00"],
SHOWS_PER_CITY: 1,
BLOCK_TWO_SHOWS_IN_CITY_AT_SAME_DATE: true,
REQUIRE_5_STARS: true,
TARGET_CLUB_RANGE: { min: 80, max: 1500 },
INITIAL_DATE: getFormattedDate(today),
FINAL_DATE: getFormattedDate(sevenDaysFromNow),
ARTIST_ID: "xxxxxxxx", // replace if artist ID
};
const BOOK_SHOW_PATH = `/World/Popmundo.aspx/Artist/BookShow/${DEFAULTS.ARTIST_ID}`;
const TOUR_ITINERARY = [
{ city: "rio de janeiro", travelHours: 3 }, { city: "são paulo", travelHours: 3 },
{ city: "buenos aires", travelHours: 6 }, { city: "são paulo", travelHours: 6 },
{ city: "mexico city", travelHours: 12 }, { city: "los angeles", travelHours: 6 },
{ city: "seattle", travelHours: 8 }, { city: "chicago", travelHours: 8 },
{ city: "nashville", travelHours: 2 }, { city: "chicago", travelHours: 2 },
{ city: "toronto", travelHours: 3 }, { city: "montreal", travelHours: 6 },
{ city: "new york", travelHours: 6 }, { city: "london", travelHours: 18 },
{ city: "brussels", travelHours: 2 }, { city: "paris", travelHours: 3 },
{ city: "barcelona", travelHours: 6 }, { city: "madrid", travelHours: 3 },
{ city: "porto", travelHours: 3 }, { city: "madrid", travelHours: 3 },
{ city: "milan", travelHours: 4 }, { city: "rome", travelHours: 2 },
{ city: "budapest", travelHours: 3 }, { city: "belgrade", travelHours: 2 },
{ city: "dubrovnik", travelHours: 2 }, { city: "sarajevo", travelHours: 2 },
{ city: "belgrade", travelHours: 2 }, { city: "bucharest", travelHours: 3 },
{ city: "sofia", travelHours: 2 }, { city: "istanbul", travelHours: 3 },
{ city: "izmir", travelHours: 2 }, { city: "antalya", travelHours: 2 },
{ city: "ankara", travelHours: 2 }, { city: "baku", travelHours: 2 },
{ city: "kyiv", travelHours: 5 }, { city: "moscow", travelHours: 2 },
{ city: "tallinn", travelHours: 4 }, { city: "stockholm", travelHours: 2 },
{ city: "vilnius", travelHours: 2 }, { city: "warsaw", travelHours: 2 },
{ city: "berlin", travelHours: 3 }, { city: "copenhagen", travelHours: 3 },
{ city: "tromsø", travelHours: 4 }, { city: "copenhagen", travelHours: 4 },
{ city: "tallinn", travelHours: 3 }, { city: "helsinki", travelHours: 2 },
{ city: "tallinn", travelHours: 2 }, { city: "tromsø", travelHours: 3 },
{ city: "berlin", travelHours: 5 }, { city: "glasgow", travelHours: 4 },
{ city: "london", travelHours: 4 }, { city: "amsterdam", travelHours: 5 },
{ city: "istanbul", travelHours: 8 }, { city: "ankara", travelHours: 3 },
{ city: "singapore", travelHours: 16 }, { city: "jakarta", travelHours: 3 },
{ city: "singapore", travelHours: 3 }, { city: "shanghai", travelHours: 6 },
{ city: "manila", travelHours: 4 }, { city: "singapore", travelHours: 7 },
{ city: "melbourne", travelHours: 9 }, { city: "johannesburg", travelHours: 34 },
];
// --- END OF CONFIGURATION ---
// --- Helper Functions ---
const SELECTORS = {
city: '#ctl00_cphLeftColumn_ctl01_ddlCities', day: '#ctl00_cphLeftColumn_ctl01_ddlDays', hour: '#ctl00_cphLeftColumn_ctl01_ddlHours',
findClubsBtn: '#ctl00_cphLeftColumn_ctl01_btnFindClubs', clubsTable: '#tableclubs', bookShowBtn: '#ctl00_cphLeftColumn_ctl01_btnBookShow',
dialogButtons: 'body > div:nth-child(4) > div.ui-dialog-buttonpane.ui-widget-content.ui-helper-clearfix > div > button:nth-child(1)'
};
const delay = ms => new Promise(res => setTimeout(res, ms));
const formatDate = (date) => date.toISOString().split('T')[0];
const getWeekStartDate = (dateStr) => {
const date = new Date(dateStr);
const day = date.getUTCDay(); // Use UTC to avoid timezone shifts; Sunday - 0, Monday - 1
const diff = date.getUTCDate() - day + (day === 0 ? -6 : 1); // adjust when day is Sunday
const monday = new Date(date.setUTCDate(diff));
return monday.toISOString().split('T')[0];
};
const skipToShow = (c, r) => {
const BOOK_SHOW_URL_FULL = `https://${window.location.hostname}${BOOK_SHOW_PATH}`;
console.error(`${r}. Skipping.`);
localStorage.setItem('popmundo_show_index', c + 1);
window.location.href = BOOK_SHOW_URL_FULL;
};
/**
* Creates and injects a "Stop" button onto the page.
*/
function addStopButton() {
if (document.getElementById('pm-stop-script-btn')) return;
GM_addStyle(`
#pm-stop-script-btn {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10001;
padding: 10px 20px;
background-color: #d9534f;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
#pm-stop-script-btn:hover {
background-color: #c9302c;
}
#pm-stop-script-btn:disabled {
background-color: #999;
cursor: not-allowed;
}
`);
const stopButton = document.createElement('button');
stopButton.id = 'pm-stop-script-btn';
stopButton.textContent = 'Stop Booker';
stopButton.onclick = () => {
sessionStorage.setItem('popmundo_script_stopped', 'true');
stopButton.textContent = 'Stopping...';
stopButton.disabled = true;
console.log('🛑 Script stop requested. It will halt and clean up on the next step/page load.');
alert('The script will stop and clear its data.');
};
document.body.appendChild(stopButton);
}
/**
* Creates and displays a settings form overlay.
* @param {object} defaults - The default values for the form fields.
* @returns {Promise<object>} A promise that resolves with the new settings or rejects if cancelled.
*/
function showSettingsForm(defaults) {
return new Promise((resolve, reject) => {
GM_addStyle(`
#pm-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; align-items: center; justify-content: center; }
#pm-settings-form { background: #f0f0f0; padding: 25px; border-radius: 8px; font-family: sans-serif; display: grid; grid-template-columns: auto 1fr; gap: 12px; border: 1px solid #ccc; color: #333; }
#pm-settings-form h2 { grid-column: 1 / -1; margin: 0 0 10px; text-align: center; }
#pm-settings-form label { font-weight: bold; padding-right: 10px; text-align: right; align-self: center;}
#pm-settings-form input, #pm-settings-form select { padding: 5px; border-radius: 4px; border: 1px solid #ccc; }
#pm-settings-form .buttons { grid-column: 1 / -1; text-align: center; margin-top: 15px; }
#pm-settings-form button { padding: 8px 16px; border: none; border-radius: 5px; cursor: pointer; margin: 0 10px; color: white; }
#tm-save-settings { background: #4CAF50; } #tm-cancel-settings { background: #f44336; }
`);
const overlay = document.createElement('div');
overlay.id = 'pm-settings-overlay';
const form = document.createElement('div');
form.id = 'pm-settings-form';
const createInputRow = (labelText, inputId, inputType, value, attributes = {}) => {
const label = document.createElement('label');
label.htmlFor = inputId;
label.textContent = labelText;
const input = document.createElement('input');
input.id = inputId;
input.type = inputType;
input.value = value;
for (const [key, val] of Object.entries(attributes)) {
input.setAttribute(key, val);
}
form.append(label, input);
};
form.innerHTML = '<h2>Tour Booker Settings</h2>';
// --- Create Start City Select Dropdown ---
const uniqueCities = [...new Set(TOUR_ITINERARY.map(leg => leg.city))].sort((a, b) => a.localeCompare(b));
const cityLabel = document.createElement('label');
cityLabel.htmlFor = 'tm-initial-city';
cityLabel.textContent = 'Start City:';
const citySelect = document.createElement('select');
citySelect.id = 'tm-initial-city';
uniqueCities.forEach(city => {
const option = document.createElement('option');
const cleanCity = city.toLowerCase();
option.value = cleanCity;
option.textContent = city.charAt(0).toUpperCase() + city.slice(1);
if (cleanCity === defaults.INITIAL_CITY.toLowerCase()) {
option.selected = true;
}
citySelect.appendChild(option);
});
form.append(cityLabel, citySelect);
createInputRow('Start Date:', 'tm-initial-date', 'date', defaults.INITIAL_DATE);
createInputRow('Final Date:', 'tm-final-date', 'date', defaults.FINAL_DATE);
// --- Create Show Times Multi-Select ---
const availableShowTimes = ["14:00:00", "16:00:00", "18:00:00", "20:00:00", "22:00:00"];
const showTimesLabel = document.createElement('label');
showTimesLabel.htmlFor = 'tm-show-times';
showTimesLabel.textContent = 'Show Times:';
showTimesLabel.style.alignSelf = 'start';
const showTimesSelect = document.createElement('select');
showTimesSelect.id = 'tm-show-times';
showTimesSelect.multiple = true;
showTimesSelect.style.height = '100px';
availableShowTimes.forEach(time => {
const option = document.createElement('option');
option.value = time;
option.textContent = time;
if (defaults.SHOW_TIMES.includes(time)) {
option.selected = true;
}
showTimesSelect.appendChild(option);
});
form.append(showTimesLabel, showTimesSelect);
createInputRow('Shows Per City:', 'tm-shows-per-city', 'number', defaults.SHOWS_PER_CITY, { min: '1', style: 'width: 80px;' });
createInputRow('Club Price Min:', 'tm-club-min', 'number', defaults.TARGET_CLUB_RANGE.min, { min: '0', style: 'width: 80px;' });
createInputRow('Club Price Max:', 'tm-club-max', 'number', defaults.TARGET_CLUB_RANGE.max, { min: '0', style: 'width: 80px;' });
const blockSameDayLabel = document.createElement('label');
blockSameDayLabel.htmlFor = 'tm-block-same-day';
blockSameDayLabel.textContent = 'Block Multiple Shows in Same Day:';
const blockSameDayCheckbox = document.createElement('input');
blockSameDayCheckbox.id = 'tm-block-same-day';
blockSameDayCheckbox.type = 'checkbox';
blockSameDayCheckbox.checked = defaults.BLOCK_TWO_SHOWS_IN_CITY_AT_SAME_DATE;
blockSameDayCheckbox.style.justifySelf = 'start';
form.append(blockSameDayLabel, blockSameDayCheckbox);
const starLabel = document.createElement('label');
starLabel.htmlFor = 'tm-5star';
starLabel.textContent = 'Require 5 Stars:';
const starCheckbox = document.createElement('input');
starCheckbox.id = 'tm-5star';
starCheckbox.type = 'checkbox';
starCheckbox.checked = defaults.REQUIRE_5_STARS;
starCheckbox.style.justifySelf = 'start';
form.append(starLabel, starCheckbox);
const buttonsDiv = document.createElement('div');
buttonsDiv.className = 'buttons';
buttonsDiv.innerHTML = '<button id="tm-save-settings">Save and Start</button><button id="tm-cancel-settings">Cancel</button>';
form.appendChild(buttonsDiv);
overlay.appendChild(form);
document.body.appendChild(overlay);
document.getElementById('tm-save-settings').onclick = () => {
const selectedShowTimes = Array.from(document.getElementById('tm-show-times').selectedOptions).map(option => option.value);
const newSettings = {
INITIAL_CITY: document.getElementById('tm-initial-city').value,
INITIAL_DATE: document.getElementById('tm-initial-date').value,
FINAL_DATE: document.getElementById('tm-final-date').value,
SHOW_TIMES: selectedShowTimes,
SHOWS_PER_CITY: parseInt(document.getElementById('tm-shows-per-city').value, 10),
BLOCK_TWO_SHOWS_IN_CITY_AT_SAME_DATE: document.getElementById('tm-block-same-day').checked,
TARGET_CLUB_RANGE: {
min: parseInt(document.getElementById('tm-club-min').value, 10),
max: parseInt(document.getElementById('tm-club-max').value, 10)
},
REQUIRE_5_STARS: document.getElementById('tm-5star').checked,
ARTIST_ID: defaults.ARTIST_ID
};
document.body.removeChild(overlay);
resolve(newSettings);
};
document.getElementById('tm-cancel-settings').onclick = () => {
document.body.removeChild(overlay);
reject(new Error("User cancelled settings."));
};
});
}
const handleConfirmationPopup = async () => {
await delay(1000 + Math.random() * 500);
for (let i = 0; i < 20; i++) {
const yesButton = Array.from(document.querySelectorAll(SELECTORS.dialogButtons)).find(b => b.textContent.trim() === 'Yes');
if (yesButton) {
console.log('✅ "Yes" button found. Simulating a more realistic click...');
await delay(800 + Math.random() * 400);
const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: unsafeWindow });
const mouseUpEvent = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: unsafeWindow });
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: unsafeWindow });
yesButton.dispatchEvent(mouseDownEvent); await delay(60 + Math.random() * 40); yesButton.dispatchEvent(mouseUpEvent); yesButton.dispatchEvent(clickEvent); return;
}
await delay(1000);
}
console.error('Confirmation pop-up "Yes" button not found in time.');
};
/**
* ✅ CORRECTED FUNCTION
* This function generates the full tour itinerary based on the user's settings.
* The original problem was here: if a show's scheduled time exceeded the FINAL_DATE,
* it would stop all tour planning immediately.
*
* The fix: Instead of halting the entire process (`break tourPlanningLoop`), the code
* now uses a simple `break`. This stops adding more shows for the *current city*
* but allows the script to continue planning for the *next city* in the itinerary.
* This directly addresses the "go to next city" behavior you described.
* The logic for BLOCK_TWO_SHOWS_IN_CITY_AT_SAME_DATE was already correct in how it
* forced the next show to a new day; the issue was just the premature termination.
*/
function generateAndStoreTour(settings) {
console.log(`🗺️ Planning tour, starting itinerary at ${settings.INITIAL_CITY}...`);
const tour = [];
let lastActionTime = new Date(settings.INITIAL_DATE);
const finalDate = new Date(settings.FINAL_DATE);
finalDate.setHours(23, 59, 59, 999); // Ensure comparison includes the entire final day
const findNextShowSlot = (startTime) => {
let searchTime = new Date(startTime);
const sortedShowTimes = [...new Set(settings.SHOW_TIMES)].sort((a, b) => a.localeCompare(b));
while (true) {
for (const showTime of sortedShowTimes) {
const [hour, minute, second] = showTime.split(':');
let potentialShowTime = new Date(searchTime);
potentialShowTime.setHours(hour, minute, second, 0);
if (potentialShowTime > searchTime) {
return potentialShowTime;
}
}
searchTime.setDate(searchTime.getDate() + 1);
searchTime.setHours(0, 0, 0, 0);
}
};
let startingIndex = TOUR_ITINERARY.findIndex(leg => leg.city.toLowerCase() === settings.INITIAL_CITY.toLowerCase());
if (startingIndex === -1) {
console.warn(`Initial city "${settings.INITIAL_CITY}" not found in itinerary. Starting from the beginning of the list.`);
startingIndex = 0;
}
const activeItinerary = TOUR_ITINERARY.slice(startingIndex);
tourPlanningLoop:
for (let i = 0; i < activeItinerary.length; i++) {
const leg = activeItinerary[i];
let cityArrivalTime = new Date(lastActionTime);
if (i > 0) {
cityArrivalTime.setHours(cityArrivalTime.getHours() + leg.travelHours);
}
let lastShowTimeInCity = cityArrivalTime;
for (let j = 0; j < settings.SHOWS_PER_CITY; j++) {
let searchStartTime;
// This logic correctly forces the search for the next show to start on the following day if the setting is active.
if (tour.length > 0) {
console.log(leg.city.toLowerCase() ,"==", tour.at(-1).city.toLowerCase())
if (j > 0 && settings.BLOCK_TWO_SHOWS_IN_CITY_AT_SAME_DATE && leg.city.toLowerCase() == tour.at(-1).city.toLowerCase()) {
break;
}
}
searchStartTime = new Date(lastShowTimeInCity);
const nextShowTime = findNextShowSlot(searchStartTime);
// --- 🛠️ FIX APPLIED HERE ---
// If the next show is past the final date, stop adding shows for THIS city and move to the next.
if (nextShowTime > finalDate) {
console.log(`🛑 Final date ${settings.FINAL_DATE} reached while planning for ${leg.city}. Moving to the next city.`);
break; // This now only breaks the inner loop (for shows per city), not the 'tourPlanningLoop'.
}
tour.push({
city: leg.city,
date: formatDate(nextShowTime),
time: nextShowTime.toTimeString().split(' ')[0]
});
lastShowTimeInCity = new Date(nextShowTime);
}
lastActionTime = new Date(lastShowTimeInCity);
}
localStorage.setItem('popmundo_planned_tour', JSON.stringify(tour));
console.log("✅ Tour planned and saved!", tour);
return tour;
}
async function runAutoBooker() {
if (sessionStorage.getItem('popmundo_script_stopped') === 'true') {
console.log('Script execution halted by user. Cleaning up all tour data.');
const stopBtn = document.getElementById('pm-stop-script-btn');
if (stopBtn) stopBtn.remove();
sessionStorage.removeItem('popmundo_script_stopped');
sessionStorage.removeItem('popmundo_session_settings');
sessionStorage.removeItem('popmundo_restore_selections');
localStorage.removeItem('popmundo_show_index');
localStorage.removeItem('popmundo_planned_tour');
localStorage.removeItem('popmundo_booked_clubs');
localStorage.removeItem('popmundo_booking_redirect');
console.log('✅ All script data cleared.');
return;
}
const BOOK_SHOW_URL_FULL = `https://${window.location.hostname}${BOOK_SHOW_PATH}`;
if (localStorage.getItem('popmundo_booking_redirect')) { localStorage.removeItem('popmundo_booking_redirect'); window.location.href = BOOK_SHOW_URL_FULL; }
if (!window.location.href.includes(BOOK_SHOW_PATH)) return;
addStopButton();
const restoreSelections = sessionStorage.getItem('popmundo_restore_selections');
if (restoreSelections) {
try {
const { city, date, time } = JSON.parse(restoreSelections);
console.log(`🔄 Restoring selections after page reload: ${city}, ${date}, ${time}`);
const cityDropdown = document.querySelector(SELECTORS.city);
if (cityDropdown) {
const cityOption = Array.from(cityDropdown.options).find(opt =>
opt.text.toLowerCase().localeCompare(city, undefined, { sensitivity: 'accent' }) === 0
);
if (cityOption) cityDropdown.value = cityOption.value;
}
const dayDropdown = document.querySelector(SELECTORS.day);
if (dayDropdown) dayDropdown.value = date;
const hourDropdown = document.querySelector(SELECTORS.hour);
if (hourDropdown) hourDropdown.value = time;
sessionStorage.removeItem('popmundo_restore_selections');
await delay(1000);
document.querySelector(SELECTORS.findClubsBtn).click();
return;
} catch (e) {
console.error("Error restoring selections:", e);
sessionStorage.removeItem('popmundo_restore_selections');
}
}
let sessionSettings;
const savedSettings = sessionStorage.getItem('popmundo_session_settings');
if (savedSettings && savedSettings !== 'undefined' && savedSettings !== 'null') { try { sessionSettings = JSON.parse(savedSettings); } catch (e) { console.error("Could not parse saved settings, clearing them.", e); sessionStorage.removeItem('popmundo_session_settings'); } }
if (!sessionSettings) { try { sessionSettings = await showSettingsForm(DEFAULTS); sessionStorage.setItem('popmundo_session_settings', JSON.stringify(sessionSettings)); console.log("Settings confirmed and saved for this session.", sessionSettings); } catch (error) { console.log(error.message); return; } }
let tourJson = localStorage.getItem('popmundo_planned_tour');
if (!tourJson) { tourJson = JSON.stringify(generateAndStoreTour(sessionSettings)); }
const TOUR_LIST = JSON.parse(tourJson);
let currentIndex = parseInt(localStorage.getItem('popmundo_show_index') || '0', 10);
let bookedClubs = JSON.parse(localStorage.getItem('popmundo_booked_clubs') || '{}');
if (currentIndex >= TOUR_LIST.length) {
console.log('🎉 Tour finished! Cleaning up all tour data.');
alert('Tour finished!');
const stopBtn = document.getElementById('pm-stop-script-btn');
if (stopBtn) stopBtn.remove();
sessionStorage.removeItem('popmundo_script_stopped');
sessionStorage.removeItem('popmundo_session_settings');
sessionStorage.removeItem('popmundo_restore_selections');
localStorage.removeItem('popmundo_show_index');
localStorage.removeItem('popmundo_planned_tour');
localStorage.removeItem('popmundo_booked_clubs');
localStorage.removeItem('popmundo_booking_redirect');
console.log('✅ All script data cleared.');
return;
}
const currentShow = TOUR_LIST[currentIndex];
console.log(`▶️ Processing show ${currentIndex + 1}/${TOUR_LIST.length}: ${currentShow.city} on ${currentShow.date} at ${currentShow.time}`);
const cityDropdown = document.querySelector(SELECTORS.city);
if (!cityDropdown) return;
const clubsTable = document.querySelector(SELECTORS.clubsTable);
const selectedCityText = cityDropdown.options[cityDropdown.selectedIndex].text.toLowerCase();
if (selectedCityText.localeCompare(currentShow.city, undefined, { sensitivity: 'accent' }) !== 0) {
const cityOption = Array.from(cityDropdown.options).find(opt =>
opt.text.toLowerCase().localeCompare(currentShow.city, undefined, { sensitivity: 'accent' }) === 0
);
if (cityOption) {
sessionStorage.setItem('popmundo_restore_selections', JSON.stringify({
city: currentShow.city, date: currentShow.date, time: currentShow.time
}));
cityDropdown.value = cityOption.value;
cityDropdown.dispatchEvent(new Event('change', { bubbles: true }));
return;
} else {
skipToShow(currentIndex, `City not found: ${currentShow.city}`);
}
return;
}
if (!clubsTable) {
document.querySelector(SELECTORS.day).value = currentShow.date;
document.querySelector(SELECTORS.hour).value = currentShow.time;
await delay(300);
document.querySelector(SELECTORS.findClubsBtn).click();
return;
}
const rows = clubsTable.querySelectorAll('tbody tr');
const validClubs = [];
const currentShowWeekStart = getWeekStartDate(currentShow.date);
rows.forEach(row => {
const ratingCell = row.cells[2];
let starRequirementMet = !sessionSettings.REQUIRE_5_STARS;
if (sessionSettings.REQUIRE_5_STARS && ratingCell) { const sortKeySpan = ratingCell.querySelector('span.sortkey'); if (sortKeySpan && parseInt(sortKeySpan.textContent, 10) === 50) { starRequirementMet = true; } }
const priceCell = row.cells[row.cells.length - 1];
let priceRequirementMet = false;
let price = 0;
if (priceCell) { price = parseFloat(priceCell.textContent.trim().replace(/\s*M\$$/, '').replace(/\./g, '').replace(',', '.')); if (price >= sessionSettings.TARGET_CLUB_RANGE.min && price <= sessionSettings.TARGET_CLUB_RANGE.max) { priceRequirementMet = true; } }
const clubName = row.cells[0].textContent.trim();
const lastBookingDate = bookedClubs[clubName];
let isAlreadyBookedThisWeek = false;
if (lastBookingDate) {
const lastBookingWeekStart = getWeekStartDate(lastBookingDate);
if (lastBookingWeekStart === currentShowWeekStart) {
isAlreadyBookedThisWeek = true;
console.log(`Club "${clubName}" was already booked in the week of ${currentShowWeekStart}. Skipping.`);
}
}
if (starRequirementMet && priceRequirementMet && !isAlreadyBookedThisWeek) {
validClubs.push({ price: price, rowElement: row });
}
});
let targetRow = null;
if (validClubs.length > 0) {
validClubs.sort((a, b) => b.price - a.price);
const bestClub = validClubs[0];
console.log(`Found ${validClubs.length} valid clubs. Selecting best one: ${bestClub.rowElement.cells[0].textContent.trim()} (Price: ${bestClub.price})`);
targetRow = bestClub.rowElement;
}
if (targetRow) {
const selectedClubName = targetRow.cells[0].textContent.trim();
bookedClubs[selectedClubName] = currentShow.date;
localStorage.setItem('popmundo_booked_clubs', JSON.stringify(bookedClubs));
targetRow.querySelector('input[type="radio"]').click();
await delay(400 + Math.random() * 200);
localStorage.setItem('popmundo_show_index', currentIndex + 1);
localStorage.setItem('popmundo_booking_redirect', 'true');
document.querySelector(SELECTORS.bookShowBtn).click();
await handleConfirmationPopup();
} else {
let reason = `No available club found`;
if (sessionSettings.REQUIRE_5_STARS) reason += ` with 5 stars`;
reason += ` in range [${sessionSettings.TARGET_CLUB_RANGE.min}-${sessionSettings.TARGET_CLUB_RANGE.max}] in ${currentShow.city}`;
skipToShow(currentIndex, reason);
}
}
runAutoBooker();
})();