// ==UserScript== // @name YNO Expeditions Enhancer // @namespace http://tampermonkey.net/ // @version 1.2.1 // @description Expansion Script for Expeditions on YNO. // @author Zebraed // @tag Enhancement // @match https://ynoproject.net/* // @icon https://ynoproject.net/2kki/images/badge/compass_diamond.gif // @license MIT // @supportURL https://github.com/Zebraed/yno-userscript // @installURL https://raw.githubusercontent.com/Zebraed/yno-userscript/refs/heads/main/monkey/expeditions-enhancer.user.js // @updateURL https://raw.githubusercontent.com/Zebraed/yno-userscript/refs/heads/main/monkey/expeditions-enhancer.user.js // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; let depthVisible = false; let nextLocationDepthHTML = ''; let temporaryPollingInterval = null; let temporaryPollingTimeout = null; let lastRecordedLoc = null; let lastRecordedTime = 0; const THRESHOLD_MS = 2000; let latestDepthInfo = null; let latestMapName = null; let lastToastTime = 0; const TOAST_INTERVAL = 3000; function getGameId() { if (typeof window === 'undefined' || window.gameId == null) return ''; return String(window.gameId); } function startTemporaryPolling() { if (temporaryPollingInterval) clearInterval(temporaryPollingInterval); if (temporaryPollingTimeout) clearTimeout(temporaryPollingTimeout); temporaryPollingInterval = setInterval(() => { findNextLocationAndShow(); }, 500); temporaryPollingTimeout = setTimeout(() => { clearInterval(temporaryPollingInterval); temporaryPollingInterval = null; temporaryPollingTimeout = null; }, 5000); } function initialPolling() { let attempts = 0; const maxAttempts = 10000 / 500; const interval = setInterval(() => { attempts++; if (findNextLocationAndShow() === true || attempts >= maxAttempts) { clearInterval(interval); } }, 500); } function cloneWithComputedStyle(element) { const clone = element.cloneNode(true); const computedStyle = window.getComputedStyle(element); let styleString = ""; for (let i = 0; i < computedStyle.length; i++) { const prop = computedStyle[i]; styleString += `${prop}: ${computedStyle.getPropertyValue(prop)}; `; } clone.style.cssText = styleString; if ( element.classList.contains('depthFillContainer') || element.classList.contains('maxDepthFillContainer') || element.classList.contains('minDepthFillContainer') || element.classList.contains('depthOutlineContainer') ) { clone.style.position = 'absolute'; clone.style.top = '0'; clone.style.left = '0'; clone.style.margin = '0'; clone.style.padding = '0'; clone.style.transform = 'none'; } const childIcons = clone.querySelectorAll('.starIcon.icon'); childIcons.forEach(icon => { icon.style.margin = '0'; icon.style.padding = '0'; icon.style.transform = 'none'; icon.style.top = '0'; icon.style.left = '0'; }); return clone; } const styleElem = document.createElement('style'); styleElem.textContent = ` .hidden { display: none !important; } #nextDestinationInfoWrapper { width: 100% !important; display: flex; padding-bottom: 8px; } #nextDestinationStars { text-align: end; display: flex !important; justify-content: flex-end; flex-wrap: nowrap; align-items: center; position: relative; transform: translateX(-60px); padding-bottom: 10px !important; margin-top: 4px !important; } .icon.fillIcon.iconButton { background: transparent; border: none; cursor: pointer; -webkit-appearance: button; appearance: button; padding: 0; margin-right: 4px; } #toggleNextDestinationIcon { transition: margin-top 0.5s ease-in-out, padding 0.5s ease-in-out; align-self: baseline; padding: 0 4px 0 0; z-index: 1; } #nextDestinationStars .starContainer { width: 14px; height: 14px; position: relative; display: inline-block; margin: 0 2px 0 0; } .starContainer .depthContainer { position: absolute; top: 0; left: 0; } #nextDestinationStars .starContainer .starIcon.icon { width: 14px !important; height: 14px !important; margin: 0 !important; display: inline-block !important; } #nextDestinationStars svg { width: 14px !important; height: 14px !important; } #expeditionSettingsModal .toast-child { margin-left: 20px; } #expeditionSettingsModal .formControls { list-style: none !important; margin: 0 !important; padding: 0 !important; } #expeditionSettingsModal .formControlRow { display: flex !important; align-items: center !important; margin-bottom: 8px !important; padding: 4px 0 !important; } #expeditionSettingsModal .formControlRow label { margin: 0 !important; margin-right: 10px !important; flex: 0 0 auto !important; } #expeditionSettingsModal .formControlRow > div { display: flex !important; align-items: center !important; } #expeditionSettingsModal .checkboxButton { vertical-align: middle !important; margin: 0 !important; padding: 0 !important; } `; document.head.appendChild(styleElem); const CONFIG_KEY = 'toastConfig'; const defaultConfig = { enableToast: true, autoHideToast: true, enableAllFeatures: true, enableExpeditionsLog: true }; function loadConfig() { try { return Object.assign({}, defaultConfig, JSON.parse(localStorage.getItem(CONFIG_KEY))); } catch { return { ...defaultConfig }; } } function saveConfig(cfg) { localStorage.setItem(CONFIG_KEY, JSON.stringify(cfg)); } const config = loadConfig(); function getLangKey() { try { return JSON.parse(localStorage.getItem('config'))?.lang || 'en'; } catch { return 'en'; } } const toastLabel = { ja: "次の目的地", en: "Next destination", fr: "Prochaine destination", es: "Siguiente ubicación", de: "Nächster Ort", zh: "下一个地点", ko: "다음 장소", it: "Prossima destinazione", pl: "Następna lokalizacja", ro: "Locație următoare", tr: "Sonraki konum", ru: "Следующее место", vi: "Địa điểm tiếp theo", ar: "الموقع التالي", eo: "Sekva loko", pt: "Próxima localização" }; const uiText = { expeditionsButton: { ja: 'ドリームラリー', en: 'Expeditions', fr: 'Expéditions', es: 'Expediciones', de: 'Expeditionen', zh: '梦远征', ko: '탐험', it: 'Spedizioni', pl: 'Ekspedycje', ro: 'Expediții', tr: 'Seyahatler', ru: 'Походs', vi: 'Thám hiểm', ar: 'الرحلات الاستكشافية', eo: 'Ekspediĉoj', pt: 'Expedições' }, title: { ja: 'ドリームラリー設定', en: 'Expedition Settings', fr: 'Paramètres d\'expédition', es: 'Configuración de Expedición', de: 'Expeditionseinstellungen', zh: '梦远征设置', ko: '탐험 설정', it: 'Impostazioni Spedizioni', pl: 'Ustawienia Ekspedycji', ro: 'Setări expediție', tr: 'Seyahat Ayarları', ru: 'Настройки походs', vi: 'Cài đặt thám hiểm', ar: 'إعدادات الرحلات', eo: 'Ekspediĉaj agordoj', pt: 'Configurações de Expedição' }, enableToastLabel: { ja: '目的地到達通知を有効にする', en: 'Enable Notification', fr: 'Activer la notification', es: 'Habilitar notificación', de: 'Benachrichtigung aktivieren', zh: '启用通知', ko: '알림 활성화', it: 'Abilita notifica', pl: 'Włącz powiadomienie', ro: 'Activează notificarea', tr: 'Bildirimleri etkinleştir', ru: 'Включить уведомление', vi: 'Bật thông báo', ar: 'تفعيل الإشعار', eo: 'Aktivigi notifikon', pt: 'Ativar notificação' }, autoHideToastLabel: { ja: '目的地到達通知を自動で閉じる', en: 'Auto-hide Notification', fr: 'Masquer automatiquement la notification', es: 'Ocultar automáticamente la notificación', de: 'Benachrichtigung automatisch ausblenden', zh: '自动关闭通知', ko: '알림 자동 닫기', it: 'Chiudi notifica automaticamente', pl: 'Automatycznie ukryj powiadomienie', ro: 'Ascundere automată notificare', tr: 'Bildirimi otomatik kapat', ru: 'Автоматически скрывать уведомление', vi: 'Tự động ẩn thông báo', ar: 'إغلاق الإشعار تلقائياً', eo: 'Aŭtomate kaŝi notifikon', pt: 'Ocultar notificação automaticamente' }, reset: { ja: 'リセット', en: 'Reset', fr: 'Réinitialiser', es: 'Restablecer', de: 'Zurücksetzen', zh: '重置', ko: '초기화', it: 'Ripristina', pl: 'Zresetuj', ro: 'Resetare', tr: 'Sıfırla', ru: 'Сброс', vi: 'Đặt lại', ar: 'إعادة تعيين', eo: 'Restarigi', pt: 'Redefinir' }, displayFixed: { ja: '画面に固定表示', en: 'Fixed on Screen', fr: 'Fixé à l\'écran', es: 'Fijado en pantalla', de: 'Auf dem Bildschirm fixiert', zh: '固定在屏幕上', ko: '화면에 고정', it: 'Fisso sullo schermo', pl: 'Na stałe na ekranie', ro: 'Fix pe ecran', tr: 'Ekranda sabit', ru: 'Закреплено на экране', vi: 'Cố định trên màn', ar: 'مثبت على الشاشة', eo: 'Fiksita sur ekrano', pt: 'Fixo na tela' }, expeditionsLogHeader: { ja: '到達場所ログ', en: 'Destination Logs', fr: 'Journaux de destination', es: 'Registros de destino', de: 'Zielprotokolle', zh: '目的地日志', ko: '도달 위치 로그', it: 'Registri delle destinazioni', pl: 'Rejestry miejsc docelowych', ro: 'Jurnale de destinație', tr: 'Hedef Kayıtları', ru: 'Журналы пунктов назначения', vi: 'Nhật ký điểm đến', ar: 'سجلات الوجهة', eo: 'Protokoloj de celolokoj', pt: 'Registros de Destino' }, selectDate: { ja: '日付選択', en: 'Select Date', fr: 'Sélectionner la date', es: 'Seleccionar fecha', de: 'Datum auswählen', zh: '选择日期', ko: '날짜 선택', it: 'Seleziona data', pl: 'Wybierz datę', ro: 'Selectează data', tr: 'Tarih Seç', ru: 'Выбрать дату', vi: 'Chọn ngày', ar: 'اختر التاريخ', eo: 'Elektu daton', pt: 'Selecionar data' }, readLog: { ja: 'ログ表示', en: 'Show Log', fr: 'Afficher le journal', es: 'Mostrar registro', de: 'Protokoll anzeigen', zh: '显示日志', ko: '로그 표시', it: 'Mostra registro', pl: 'Pokaż dziennik', ro: 'Afișează jurnalul', tr: 'Günlüğü göster', ru: 'Показать журнал', vi: 'Hiển thị nhật ký', ar: 'عرض السجل', eo: 'Montri protokolon', pt: 'Exibir registro' }, downloadLog: { ja: 'ダウンロード', en: 'Download', fr: 'Télécharger', es: 'Descargar', de: 'Herunterladen', zh: '下载', ko: '다운로드', it: 'Scarica', pl: 'Pobierz', ro: 'Descarcă', tr: 'İndir', ru: 'Скачать', vi: 'Tải xuống', ar: 'تنزيل', eo: 'Elŝuti', pt: 'Baixar' }, deleteLog: { ja: '削除', en: 'Delete', fr: 'Supprimer', es: 'Eliminar', de: 'Löschen', zh: '删除', ko: '삭제', it: 'Elimina', pl: 'Usuń', ro: 'Ștergere', tr: 'Sil', ru: 'Удалить', vi: 'Xóa', ar: 'حذف', eo: 'Forigi', pt: 'Excluir' }, noLog: { ja: '(ログがありません)', en: '(No log data)', fr: '(Aucune donnée de journal)', es: '(Sin datos de registro)', de: '(Keine Protokolldaten)', zh: '(无日志数据)', ko: '(로그 데이터 없음)', it: '(Nessun dato di registro)', pl: '(Brak danych dziennika)', ro: '(Nu există date de jurnal)', tr: '(Günlük verisi yok)', ru: '(Никаких журналов)', vi: '(Không có dữ liệu nhật ký)', ar: '(لا يوجد سجل)', eo: '(Neniu protokoldato disponebla)', pt: '(Sem dados de registro)' }, enableLogLabel: { ja: '到達場所ログを有効にする', en: 'Enable Destination Logs', fr: 'Activer les journaux de destination', es: 'Habilitar registros de destino', de: 'Zielprotokolle aktivieren', zh: '启用目的地日志', ko: '도달 위치 로그 활성화', it: 'Abilita registri delle destinazioni', pl: 'Włącz rejestry miejsc docelowych', ro: 'Activează jurnalele de destinație', tr: 'Hedef Günlüklerini Etkinleştir', ru: 'Включить журнал пунктов назначения', vi: 'Bật nhật ký điểm đến', ar: 'قم بتمكين سجلات الوجهة', eo: 'Ebligi cellokajn protokolojn', pt: 'Ativar registros de destino' }, reachedCountLabel: { ja: '到達数', en: 'Reached', fr: 'Atteints', es: 'Alcanzados', de: 'Erreicht', zh: '已到达', ko: '도달함', it: 'Raggiunti', pl: 'Osiągnięte', ro: 'Atingeri', tr: 'Ulaşılan', ru: 'Достигнуто', vi: 'Đã đến', ar: 'تم الوصول', eo: 'Alvenintaj', pt: 'Alcançados' } }; function isGameNameElement(el) { if (el.classList.contains('gameLink')) { return true; } return false; } function getLocalizedMapName(detailsContainer) { if (!detailsContainer) return ''; let candidateChildren = Array.from(detailsContainer.children); candidateChildren = candidateChildren.filter(el => { if (isGameNameElement(el)) return false; if (!el.innerText.trim()) return false; return true; }); if (candidateChildren.length === 0) { return ''; } else if (candidateChildren.length === 1) { return candidateChildren[0].innerText.trim(); } else { // is this necessary? return candidateChildren[0].innerText.trim(); } } function showMessage(html, type) { let wrapper = document.getElementById('nextDestinationInfoWrapper'); if (!wrapper) { wrapper = document.createElement('div'); wrapper.id = 'nextDestinationInfoWrapper'; wrapper.className = 'info'; const toggleButton = document.createElement('button'); toggleButton.id = 'toggleNextDestinationIcon'; toggleButton.className = 'icon fillIcon iconButton'; toggleButton.setAttribute('data-i18n', '[title]tooltips.chat.toggleNextLocation'); toggleButton.setAttribute('i18n-options', '{}'); toggleButton.innerHTML = ` `; toggleButton.addEventListener('click', () => { let starsDiv = document.getElementById('nextDestinationStars'); if (!starsDiv) return; depthVisible = !depthVisible; if (depthVisible) { starsDiv.classList.remove('hidden'); } else { starsDiv.classList.add('hidden'); } }); wrapper.appendChild(toggleButton); const labelSpan = document.createElement('span'); labelSpan.id = 'nextDestinationLabel'; const refLabel = document.getElementById('locationLabel'); labelSpan.className = refLabel ? refLabel.className : 'infoLabel nowrap'; wrapper.appendChild(labelSpan); const textSpan = document.createElement('span'); textSpan.id = 'nextDestinationText'; const refText = document.getElementById('locationText'); textSpan.className = refText ? refText.className : 'infoText nofilter'; textSpan.style.textAlign = 'right'; wrapper.appendChild(textSpan); const chatboxInfo = document.getElementById('chatboxInfo'); if (chatboxInfo) { chatboxInfo.appendChild(wrapper); } else { document.body.appendChild(wrapper); } } const lang = getLangKey(); const labelText = toastLabel[lang] || toastLabel.en; document.getElementById('nextDestinationLabel').textContent = labelText + ': '; const textEl = document.getElementById('nextDestinationText'); textEl.innerHTML = html; textEl.style.textAlign = 'right'; let starsDiv = document.getElementById('nextDestinationStars'); if (!starsDiv) { starsDiv = document.createElement('div'); starsDiv.id = 'nextDestinationStars'; const refText = document.getElementById('locationText'); if (refText) { starsDiv.className = refText.className; } if (!depthVisible) { starsDiv.classList.add('hidden'); } const chatboxInfo = document.getElementById('chatboxInfo'); if (chatboxInfo) { chatboxInfo.appendChild(starsDiv); } else { document.body.appendChild(starsDiv); } } starsDiv.innerHTML = nextLocationDepthHTML; } function findNextLocationAndShow() { if (!config.enableAllFeatures) { const wrapper = document.getElementById('nextDestinationInfoWrapper'); if (wrapper) wrapper.style.display = 'none'; return false; } const wrapper = document.getElementById('nextDestinationInfoWrapper'); if (wrapper) wrapper.style.display = ''; const entries = document.querySelectorAll('.eventLocationListEntry'); for (const entry of entries) { const checkbox = entry.querySelector('.checkbox'); const isIncomplete = checkbox && !checkbox.classList.contains('toggled'); if (!isIncomplete) continue; const gameLinkEl = entry.querySelector('.gameLink'); if (gameLinkEl) continue; const detailsContainer = entry.querySelector('.detailsContainer'); if (!detailsContainer) continue; const mapName = getLocalizedMapName(detailsContainer); const depthInfo = getDepthInfo(detailsContainer) latestMapName = mapName; latestDepthInfo = [depthInfo[0], depthInfo[1]]; let placeElement = null; for (const child of detailsContainer.children) { if (!child.innerText.trim()) continue; if (isGameNameElement(child)) continue; placeElement = child; break; } if (!placeElement) continue; const clone = placeElement.cloneNode(true); let placeHTML = `
${clone.outerHTML}
`; const depthOutline = entry.querySelector('.detailsContainer .depthContainer.depthOutlineContainer'); const outlineHTML = depthOutline ? cloneWithComputedStyle(depthOutline).outerHTML : ''; const fillElements = entry.querySelectorAll( '.detailsContainer .depthContainer.depthFillContainer, ' + '.detailsContainer .depthContainer.maxDepthFillContainer, ' + '.detailsContainer .depthContainer.minDepthFillContainer' ); let fillHTML = ''; fillElements.forEach(el => { const cloneEl = cloneWithComputedStyle(el); fillHTML += cloneEl.outerHTML; }); if (outlineHTML) { nextLocationDepthHTML = `
` + outlineHTML + fillHTML + `
`; } else { nextLocationDepthHTML = ''; } showMessage(placeHTML, 'expedition'); return true; } nextLocationDepthHTML = ''; latestDepthInfo = null; latestMapName = null; return false; } function callExpeditionUpdate() { return new Promise((resolve) => { setTimeout(() => { const foundNext = findNextLocationAndShow(); requestAnimationFrame(() => { if (foundNext && config.enableToast && config.enableAllFeatures) { const now = Date.now(); if (now - lastToastTime > TOAST_INTERVAL) { lastToastTime = now; const lang = getLangKey(); const labelText = toastLabel[lang] || toastLabel.en; const nextDestinationHTML = document.getElementById('nextDestinationText').innerHTML; const toast = showToastMessage( '', 'expedition', true, undefined, !config.autoHideToast ); if (navigator.userAgent.includes('Firefox')) { setTimeout(() => { const messageEl = toast.querySelector('.toastMessage'); if (messageEl) { messageEl.innerHTML = `${labelText}: ${nextDestinationHTML}`; messageEl.style.transform = 'translateZ(0.1px)'; messageEl.style.willChange = 'transform'; } messageEl.style.visibility = 'hidden'; setTimeout(() => { messageEl.style.visibility = 'visible'; }, 250); }, 250); } else { const messageEl = toast.querySelector('.toastMessage'); if (messageEl) { messageEl.innerHTML = `${labelText}: ${nextDestinationHTML}`; } } } } resolve(); }); }, 600); }); } function hookExpedition() { const interval = setInterval(() => { let wrappedCount = 0; if (typeof window.onClaimEventLocationPoints === 'function') { if (!window.onClaimEventLocationPoints._expeditionWrapped) { const origFn = window.onClaimEventLocationPoints; window.onClaimEventLocationPoints = function(loc, free, result) { origFn.call(this, loc, free, result); if (result < 0) { return; } const now = Date.now(); if (loc === lastRecordedLoc && (now - lastRecordedTime) < THRESHOLD_MS) { return; } lastRecordedLoc = loc; lastRecordedTime = now; const mapName = latestMapName || loc; const depthInfo = latestDepthInfo || [0, 0]; startTemporaryPolling(); callExpeditionUpdate() .then(() => { if (config.enableExpeditionsLog) { addExpeditionsLog(getGameId(), mapName, depthInfo); } }) .catch(err => { console.error("callExpeditionUpdate Error:", err); }); }; window.onClaimEventLocationPoints._expeditionWrapped = true; } wrappedCount++; } if (typeof window.onUpdateEventPeriod === 'function') { if (!window.onUpdateEventPeriod._expeditionWrapped) { const origFn = window.onUpdateEventPeriod; window.onUpdateEventPeriod = function(eventPeriod) { origFn.call(this, eventPeriod); latestMapName = null; latestDepthInfo = null; callExpeditionUpdate(); }; window.onUpdateEventPeriod._expeditionWrapped = true; } wrappedCount++; } if (typeof window.fetchAndUpdatePlayerInfo === 'function') { if (!window.fetchAndUpdatePlayerInfo._expeditionWrapped) { const origFn = window.fetchAndUpdatePlayerInfo; window.fetchAndUpdatePlayerInfo = function() { const ret = origFn.apply(this, arguments); if (ret && typeof ret.then === 'function') { ret.then(() => callExpeditionUpdate()); } else { setTimeout(callExpeditionUpdate, 2000); } return ret; }; window.fetchAndUpdatePlayerInfo._expeditionWrapped = true; } wrappedCount++; } if (wrappedCount === 3) { clearInterval(interval); } }, 300); } function scheduleDailyUpdate() { const now = new Date(); const nextUTC0 = new Date(Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 0, 0, 0, 0 )); const delayMs = nextUTC0.getTime() - now.getTime(); setTimeout(() => { latestMapName = null; latestDepthInfo = null; findNextLocationAndShow(); scheduleDailyUpdate(); }, delayMs); } function waitForUI() { const uiInterval = setInterval(() => { const settingsModal = document.getElementById('settingsModal'); if (settingsModal?.querySelector('.buttonRow')) { clearInterval(uiInterval); injectUI(); document.getElementById('lang')?.addEventListener('change', () => { setTimeout(injectUI, 300); setTimeout(findNextLocationAndShow, 2000); }); } }, 400); } hookExpedition(); waitForUI(); if (document.readyState === "complete") { initialPolling(); scheduleDailyUpdate(); } else { window.addEventListener('load', () => { initialPolling(); scheduleDailyUpdate(); }); } function getCurrentUTCDateString() { const now = new Date(); const year = now.getUTCFullYear(); const month = String(now.getUTCMonth() + 1).padStart(2, '0'); const day = String(now.getUTCDate()).padStart(2, '0'); return `${year}${month}${day}`; } function addExpeditionsLog(gameId, locationName, depthInfo) { let logText = ''; if ( !depthInfo[1] ){ logText = `${locationName} (${depthInfo[0]})`; } else { logText = `${locationName} (${depthInfo[0]}, ${depthInfo[1]})`; } const dateStr = getCurrentUTCDateString(); const key = `expeditionsLog_${gameId}_${dateStr}`; console.log('[ExpeditionLog] Logging:', logText); const rawData = localStorage.getItem(key); let logArr = []; try { logArr = rawData ? JSON.parse(rawData) : []; } catch (err) { console.warn("[DEBUG] parse error on existing data:", err); logArr = []; } logArr.push(logText); localStorage.setItem(key, JSON.stringify(logArr)); } function refreshExpeditionsLogDates(gameId) { const modal = document.getElementById('expeditionSettingsModal'); if (!modal) return; const expeditionsLogDateSelect = modal.querySelector('#expeditionsLogDateSelect'); if (!expeditionsLogDateSelect) return; expeditionsLogDateSelect.innerHTML = ''; const dateList = []; for (let i = 0; i < localStorage.length; i++) { const keyName = localStorage.key(i); if (keyName && keyName.startsWith(`expeditionsLog_${gameId}_`)) { const dateStr = keyName.replace(`expeditionsLog_${gameId}_`, ''); dateList.push(dateStr); } } dateList.sort().reverse(); for (let dateStr of dateList) { const option = document.createElement('option'); option.value = dateStr; option.textContent = dateStr; expeditionsLogDateSelect.appendChild(option); } } function parseDepth(containerEl) { if (!containerEl) return 0; const starIcons = containerEl.querySelectorAll('.starIcon.icon'); let depthTotal = 0; starIcons.forEach(iconEl => { if (iconEl.classList.contains('fillIcon')) { depthTotal += 1.0; } else { depthTotal += 0.5; } }); return depthTotal; } function getDepthInfo(detailsContainer) { if (!detailsContainer) return [0, 0]; const minEl = detailsContainer.querySelector('.depthContainer.minDepthFillContainer'); const maxEl = detailsContainer.querySelector('.depthContainer.maxDepthFillContainer'); const fallbackEl = detailsContainer.querySelector('.depthContainer.depthFillContainer'); let actualDepth = 0; let maxDepth = 0; if (minEl) { actualDepth = parseDepth(minEl); } else if (fallbackEl) { actualDepth = parseDepth(fallbackEl); } if (maxEl) { maxDepth = parseDepth(maxEl); } return [actualDepth, maxDepth]; } function showExpeditionsLog(gameId, dateStr, lang, retry = 10) { const key = `expeditionsLog_${gameId}_${dateStr}`; const raw = localStorage.getItem(key); let logArr = []; if (raw) { try { logArr = JSON.parse(raw); if (!Array.isArray(logArr)) throw new Error('Invalid log format'); logArr = logArr.filter(item => typeof item === 'string' && item.trim()); } catch (err) { console.warn('[ExpeditionLog] Failed to parse or clean log data:', err); logArr = []; } } const tryDisplay = () => { const expeditionsLogDisplay = document.getElementById('expeditionsLogDisplay'); const countLabel = document.getElementById('expeditionsLogCount'); if (!expeditionsLogDisplay) { if (retry > 0) { setTimeout(() => showExpeditionsLog(gameId, dateStr, lang, retry - 1), 200); } return; } expeditionsLogDisplay.innerHTML = ''; countLabel.textContent = ''; if (!logArr.length) { expeditionsLogDisplay.textContent = uiText.noLog[lang] || uiText.noLog.en; if (countLabel) countLabel.textContent = ''; return; } const ul = document.createElement('ul'); logArr.forEach((locationName) => { const li = document.createElement('li'); li.textContent = locationName; ul.appendChild(li); }); expeditionsLogDisplay.appendChild(ul); if (countLabel) countLabel.textContent = `${uiText.reachedCountLabel[lang]}: ${logArr.length}`; }; tryDisplay(); } function injectUI() { document.getElementById('expeditionSettingsModal')?.remove(); document.getElementById('openExpeditionSettingsButton')?.remove(); const settingsModal = document.getElementById('settingsModal'); const buttonRow = settingsModal?.querySelector('.buttonRow'); if (!buttonRow) return; const lang = getLangKey(); const openButton = document.createElement('button'); openButton.type = 'button'; openButton.id = 'openExpeditionSettingsButton'; openButton.innerText = uiText.expeditionsButton[lang] || uiText.expeditionsButton.en; openButton.classList.add('unselectable'); buttonRow.appendChild(openButton); function getUiLogText(textObj, key, lang) { if (textObj[key]?.[lang]) { return textObj[key][lang]; } else { return textObj[key].en; } } const modal = document.createElement('div'); modal.id = 'expeditionSettingsModal'; modal.classList.add('modal', 'hidden'); modal.style.opacity = '1'; modal.innerHTML = `

${uiText.title[lang]}

${ getUiLogText(uiText, 'expeditionsLogHeader', lang) }

${ getUiLogText(uiText, 'noLog', lang) }
`; settingsModal.insertAdjacentElement('afterend', modal); const enableAllFeaturesToggleButton = modal.querySelector('#enableAllFeaturesToggleButton'); enableAllFeaturesToggleButton.onclick = () => { config.enableAllFeatures = !config.enableAllFeatures; enableAllFeaturesToggleButton.classList.toggle('toggled', config.enableAllFeatures); saveConfig(config); const wrapper = document.getElementById('nextDestinationInfoWrapper'); if (wrapper) { wrapper.style.setProperty('display', config.enableAllFeatures ? '' : 'none', 'important'); } const starsDiv = document.getElementById('nextDestinationStars'); if (starsDiv) { starsDiv.style.setProperty('display', config.enableAllFeatures ? '' : 'none', 'important'); } }; const enableToastToggleButton = modal.querySelector('#enableToastToggleButton'); enableToastToggleButton.onclick = () => { config.enableToast = !config.enableToast; enableToastToggleButton.classList.toggle('toggled', config.enableToast); const autoHideRow = modal.querySelector('#autoHideToastRow'); if (autoHideRow) { autoHideRow.style.setProperty('display', config.enableToast ? 'flex' : 'none', 'important'); } saveConfig(config); }; const autoHideToastToggleButton = modal.querySelector('#autoHideToastToggleButton'); autoHideToastToggleButton.onclick = () => { config.autoHideToast = !config.autoHideToast; autoHideToastToggleButton.classList.toggle('toggled', config.autoHideToast); saveConfig(config); }; const enableExpeditionsLogToggleButton = modal.querySelector('#enableExpeditionsLogToggleButton'); enableExpeditionsLogToggleButton.onclick = () => { config.enableExpeditionsLog = !config.enableExpeditionsLog; enableExpeditionsLogToggleButton.classList.toggle('toggled', config.enableExpeditionsLog); saveConfig(config); const expeditionsLogHeader = document.getElementById('expeditionsLogHeader'); const expeditionsLogControls = document.getElementById('expeditionsLogControls'); if (!config.enableExpeditionsLog) { expeditionsLogHeader.classList.add('hidden'); expeditionsLogControls.classList.add('hidden'); } else { expeditionsLogHeader.classList.remove('hidden'); expeditionsLogControls.classList.remove('hidden'); } }; modal.querySelector('#resetExpeditionSettings').onclick = () => { config.enableToast = defaultConfig.enableToast; config.autoHideToast = defaultConfig.autoHideToast; config.enableAllFeatures = defaultConfig.enableAllFeatures; config.enableExpeditionsLog = defaultConfig.enableExpeditionsLog; enableToastToggleButton.classList.toggle('toggled', config.enableToast); autoHideToastToggleButton.classList.toggle('toggled', config.autoHideToast); enableAllFeaturesToggleButton.classList.toggle('toggled', config.enableAllFeatures); enableExpeditionsLogToggleButton.classList.toggle('toggled', config.enableExpeditionsLog); const autoHideRow = modal.querySelector('#autoHideToastRow'); if (autoHideRow) { autoHideRow.style.setProperty('display', config.enableToast ? 'flex' : 'none', 'important'); } const wrapper = document.getElementById('nextDestinationInfoWrapper'); if (wrapper) { wrapper.style.display = config.enableAllFeatures ? '' : 'none'; } saveConfig(config); findNextLocationAndShow(); const expeditionsLogHeader = document.getElementById('expeditionsLogHeader'); const expeditionsLogControls = document.getElementById('expeditionsLogControls'); if (!config.enableExpeditionsLog) { expeditionsLogHeader.classList.add('hidden'); expeditionsLogControls.classList.add('hidden'); } else { expeditionsLogHeader.classList.remove('hidden'); expeditionsLogControls.classList.remove('hidden'); } }; openButton.onclick = () => { const gid = getGameId(); refreshExpeditionsLogDates(gid); openModal('expeditionSettingsModal', null, 'settingsModal'); const select = document.getElementById('expeditionsLogDateSelect'); if (select && select.options.length > 0) { const latestDate = select.options[0].value; showExpeditionsLog(gid, latestDate, getLangKey()); } }; modal.querySelector('.modalClose')?.addEventListener('click', () => { closeModal(modal.id, 'settingsModal'); }); const expeditionsLogDateSelect = modal.querySelector('#expeditionsLogDateSelect'); expeditionsLogDateSelect.innerHTML = ''; const initGid = getGameId(); for (let i = 0; i < localStorage.length; i++) { const keyName = localStorage.key(i); if (keyName && keyName.startsWith(`expeditionsLog_${initGid}_`)) { const dateStr = keyName.replace(`expeditionsLog_${initGid}_`, ''); const option = document.createElement('option'); option.value = dateStr; option.textContent = dateStr; expeditionsLogDateSelect.appendChild(option); } } const readExpeditionsLogButton = modal.querySelector('#readExpeditionsLogButton'); readExpeditionsLogButton.onclick = () => { const dateStr = expeditionsLogDateSelect.value; if (!dateStr) return; showExpeditionsLog(getGameId(), dateStr, lang); }; const downloadExpeditionsLogButton = modal.querySelector('#downloadExpeditionsLogButton'); downloadExpeditionsLogButton.onclick = () => { const dateStr = expeditionsLogDateSelect.value; if (!dateStr) return; const key = `expeditionsLog_${getGameId()}_${dateStr}`; const raw = localStorage.getItem(key); if (!raw) return; let logArr = []; try { logArr = JSON.parse(raw); } catch (err) { logArr = []; } const content = logArr.join('\n'); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `expeditionsLog_${getGameId()}_${dateStr}.txt`; a.click(); URL.revokeObjectURL(url); }; const deleteExpeditionsLogButton = modal.querySelector('#deleteExpeditionsLogButton'); deleteExpeditionsLogButton.onclick = () => { const dateStr = expeditionsLogDateSelect.value; if (!dateStr) return; const gid = getGameId(); const key = `expeditionsLog_${gid}_${dateStr}`; if (localStorage.getItem(key)) { localStorage.removeItem(key); showExpeditionsLog(gid, dateStr, lang); } }; } })();