// ==UserScript== // @name wplace-infinite-bookmarks // @name:ja WPlace Unlimited Favorites⭐ - 無制限保存 & エクスポート機能 // @namespace https://greasyfork.org/users/1508363 // @version 1.0.1 // @description WPlace Unlimited Favorites⭐ - Unlimited favorites storage on [WPlace.live], with backup & restore functionality, full Blue Marble support, integrated UI type // @description:ja WPlace.liveで無制限のお気に入り保存、バックアップ・復元機能付き、Blue Marble完全対応、UI統合型 // @author Defaulter,gissehel // @homepage https://github.com/gissehel/userscripts // @supportURL https://github.com/gissehel/userscripts/issues // @match https://wplace.live/* // @icon https://www.google.com/s2/favicons?sz=64&domain=wplace.live // @grant GM_setValue // @grant GM_getValue // @grant GM.setValue // @grant GM.getValue // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/547101/wplace-infinite-bookmarks.user.js // @updateURL https://update.greasyfork.icu/scripts/547101/wplace-infinite-bookmarks.meta.js // ==/UserScript== const script_name = GM_info?.script?.name || 'no-name' const script_version = GM_info?.script?.version || 'no-version' const script_id = `${script_name} ${script_version}` console.log(`Begin - ${script_id}`) // @main_begin{wplace-infinite-bookmarks} const i18n = { ja: { "save_button": "保存", "modal_title": "お気に入り", "export": "エクスポート", "import": "インポート", "no_favorites_to_export": "エクスポートするお気に入りがありません", "favorites_exported": "件のお気に入りをエクスポートしました", "export_error": "エクスポートエラー", "import_error": "インポートエラー", "export_failed": "エクスポートに失敗しました", "import_failed": "インポートに失敗しました", "imported": "件のお気に入りをインポートしました", "invalid_file_format": "無効なファイル形式です", "confirm_import_1": "", "confirm_import_2": "件のお気に入りをインポートしますか?\n既存のデータは保持されます。", "unable_to_retrieve_location": "位置情報を取得できませんでした。マップをクリックしてから保存してください。", "enter_favorite_name": "お気に入り名を入力してください", "location": "地点", "location_retrieval_error": "位置取得エラー", "favorite_saved": "を保存しました", "favorite_retrieval_error": "お気に入り取得エラー", "confirm_delete": "このお気に入りを削除しますか?", "favorite_deleted": "削除しました", "favorite_count": "保存済み", "items": "件", "no_favorites": "お気に入りがありません", "add_favorites": "下の「保存」ボタンから追加してください", }, en: { "save_button": "Save", "modal_title": "Favorites", "export": "Export", "import": "Import", "no_favorites_to_export": "No favorites to export", "favorites_exported": " favorites exported", "export_error": "Export error", "import_error": "Import error", "export_failed": "Export failed", "import_failed": "Import failed", "imported": " favorites imported", "invalid_file_format": "Invalid file format", "confirm_import_1": "Do you want to import", "confirm_import_2": "favorites?\nExisting data will be preserved.", "unable_to_retrieve_location": "Unable to retrieve location information. Please click on the map and then save.", "enter_favorite_name": "Please enter a name for the favorite", "location": "Location", "location_retrieval_error": "Location retrieval error", "favorite_saved": "Favorite saved", "favorite_retrieval_error": "Favorite retrieval error", "confirm_delete": "Are you sure you want to delete this favorite?", "favorite_deleted": "Deleted", "favorite_count": "Saved", "items": "items", "no_favorites": "No favorites", "add_favorites": "Please add from the 'Save' button below", }, fr: { "save_button": "Enregistrer", "modal_title": "Favoris", "export": "Exporter", "import": "Importer", "no_favorites_to_export": "Aucun favori à exporter", "favorites_exported": " favoris exportés", "export_error": "Erreur d'exportation", "import_error": "Erreur d'importation", "export_failed": "Échec de l'exportation", "import_failed": "Échec de l'importation", "imported": " favoris importés", "invalid_file_format": "Format de fichier invalide", "confirm_import_1": "Voulez-vous importer", "confirm_import_2": "favoris ?\nLes données existantes seront préservées.", "unable_to_retrieve_location": "Impossible de récupérer les informations de localisation. Veuillez cliquer sur la carte puis enregistrer.", "enter_favorite_name": "Veuillez entrer un nom pour le favori", "location": "Emplacement", "location_retrieval_error": "Erreur de récupération de la localisation", "favorite_saved": "Favori enregistré", "favorite_retrieval_error": "Erreur de récupération des favoris", "confirm_delete": "Êtes-vous sûr de vouloir supprimer ce favori ?", "favorite_deleted": "Supprimé", "favorite_count": "Enregistré", "items": "éléments", "no_favorites": "Aucun favori", "add_favorites": "Veuillez ajouter depuis le bouton 'Enregistrer' ci-dessous", }, es: { "save_button": "Guardar", "modal_title": "Favoritos", "export": "Exportar", "import": "Importar", "no_favorites_to_export": "No hay favoritos para exportar", "favorites_exported": " favoritos exportados", "export_error": "Error de exportación", "import_error": "Error de importación", "export_failed": "Error al exportar", "import_failed": "Error al importar", "imported": " favoritos importados", "invalid_file_format": "Formato de archivo inválido", "confirm_import_1": "¿Desea importar", "confirm_import_2": "favoritos?\nLos datos existentes se conservarán.", "unable_to_retrieve_location": "No se puede recuperar la información de ubicación. Haga clic en el mapa y luego guarde.", "enter_favorite_name": "Por favor, ingrese un nombre para el favorito", "location": "Ubicación", "location_retrieval_error": "Error al recuperar la ubicación", "favorite_saved": "Favorito guardado", "favorite_retrieval_error": "Error al recuperar los favoritos", "confirm_delete": "¿Está seguro de que desea eliminar este favorito?", "favorite_deleted": "Eliminado", "favorite_count": "Guardado", "items": "elementos", "no_favorites": "No hay favoritos", "add_favorites": "Por favor, agregue desde el botón 'Guardar' a continuación", } }; const getLang = (i18n) => { const userLang = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language; const langKey = userLang.split("-")[0]; return i18n[langKey] || i18n['en']; } const _ = getLang(i18n); class WPlaceExtendedFavorites { constructor() { this.STORAGE_KEY = 'wplace_extended_favorites'; this.init(); } init() { this.observeAndInit(); } observeAndInit() { // Button configurations const buttonConfigs = [ { id: 'favorite-btn', selector: `[title="${_.modal_title}"]`, containerSelector: 'button[title="Toggle art opacity"]', create: this.createFavoriteButton.bind(this) }, { id: 'save-btn', selector: '[data-wplace-save="true"]', containerSelector: '.hide-scrollbar.flex.max-w-full.gap-1\\.5.overflow-x-auto', create: this.createSaveButton.bind(this) } ]; // Start generic button observer this.startButtonObserver(buttonConfigs); // Create modal setTimeout(() => this.createModal(), 2000); } // Generic Button Observer System startButtonObserver(configs) { const ensureButtons = () => { configs.forEach(config => { if (!document.querySelector(config.selector)) { const container = document.querySelector(config.containerSelector); if (container) { config.create(container); } } }); }; // Observe DOM changes const observer = new MutationObserver(() => { setTimeout(ensureButtons, 100); }); observer.observe(document.body, { childList: true, subtree: true }); // Initial placement & periodic check setTimeout(ensureButtons, 1000); setInterval(ensureButtons, 5000); } // Create Favorite Button createFavoriteButton(toggleButton) { const container = toggleButton.parentElement; if (!container) return; const button = document.createElement('button'); button.className = 'btn btn-lg sm:btn-xl btn-square shadow-md text-base-content/80 ml-2 z-30'; button.title = _.modal_title; button.innerHTML = ` `; button.addEventListener('click', () => this.openModal()); container.appendChild(button); console.log('⭐ Favorite button added'); } // Create Save Button createSaveButton(container) { const button = document.createElement('button'); button.className = 'btn btn-primary btn-soft'; button.setAttribute('data-wplace-save', 'true'); button.innerHTML = ` ${_.save_button} `; button.addEventListener('click', () => this.addFavorite()); container.appendChild(button); console.log('⭐ Save button added'); } // Create Modal createModal() { const modal = document.createElement('dialog'); modal.id = 'favorite-modal'; modal.className = 'modal'; modal.innerHTML = `
${_.no_favorites}
${_.add_favorites}