您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Snay.io Public Lib
// ==UserScript== // @name Snay.io Public Skins Lib // @namespace http://tampermonkey.net/ // @version 1.7 // @description Snay.io Public Lib // @author GravityG // @match https://www.snay.io/* // @grant GM_openInTab // @grant GM_info // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js // @license MIT // ==/UserScript== /* global bootstrap */ (async function () { 'use strict'; // Add Bootstrap CSS to the document head const bootstrapCSS = document.createElement('link'); bootstrapCSS.rel = 'stylesheet'; bootstrapCSS.href = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css'; document.head.appendChild(bootstrapCSS); console.log('Bootstrap:', typeof bootstrap); // Add custom CSS for styling, animations, and the navbar button const customCSS = document.createElement('style'); customCSS.innerHTML = ` nav.navbar { background-color: rgb(43, 48, 53) !important; color: white; position: relative; } .btn-shine { position: absolute; top: 50%; left: 14.25rem; /* 9.5rem x 1.5 */ transform: translate(-50%, -50%); padding: 18px 72px; /* 12px 48px x 1.5 */ color: #fff; background: linear-gradient(to right, #bed7f7 10%, #79aef2 10%, #868686 20%); background-position: 0; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: shine 3s infinite linear; animation-fill-mode: forwards; font-weight: 600; font-size: 24px; /* 16px x 1.5 */ text-decoration: none; white-space: nowrap; font-family: "Poppins", sans-serif; } @keyframes shine { 0% { background-position: 0; } 60% { background-position: 215px; } 100% { background-position: 215px; } } .search-container { display: flex; justify-content: center; align-items: center; flex-grow: 1; padding-right: 22.5px; } .skinurl-container { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; flex-grow: 1; padding-right: 22.5px; align-items: flex-start; align-content: flex-start; height: auto; } .search-container .input { border: 3px solid transparent; width: 24.5em; height: 3.25em; padding-left: 1.2em; outline: none; overflow: hidden; background-color: #1d2024; color: white; border-radius: 15px; transition: all 0.5s; font-size: 1.56rem; font-weight: bold; } .search-container .input:hover, .search-container .input:focus { border: 3px solid #4A9DEC !important; /* 2px x 1.5 */ box-shadow: 0px 0px 0px 10.5px rgb(74, 157, 236, 20%) !important; /* 7px x 1.5 */ background-color: #1d2024 !important; } #mySkinsContainer, #favoritesContainer, #skinsContainer { display: flex; grid-template-columns: repeat(auto-fill, minmax(225px, 1fr)); /* 150px x 1.5 */ gap: 30px; /* 20px x 1.5 */ padding: 30px; /* 20px x 1.5 */ width: 100%; height: 100vh; overflow-y: auto; overflow-x: hidden; margin: 0 auto; align-content: flex-start; } #skinsContainer::-webkit-scrollbar { width: 12px; /* 8px x 1.5 */ } #skinsContainer::-webkit-scrollbar-thumb { background-color: #6c757d !important; border-radius: 15px !important; /* 10px x 1.5 */ } #skinsContainer::-webkit-scrollbar-track { background-color: #1d2024 !important; } .skinItem { display: flex; flex-direction: column; align-items: center; justify-content: center; opacity: 0; transform: scale(0.9); filter: blur(10px); transition: all 0.5s ease-in-out; } .skinItem::before { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -60%); width: 213px; height: 213px; background-image: var(--bg); background-size: cover; background-position: center; filter: contrast(1.2) brightness(0.6) saturate(1.75) url(#gaussianBlur); border-radius: 50%; z-index: -1; opacity: 0.9; mask: radial-gradient(circle, rgba(0, 0, 0, 1) 75%, rgba(0, 0, 0, 0) 100%); } .skinItem.loaded { opacity: 1; transform: scale(1); filter: blur(0); } .skinItem > img { width: 195px; /* 130px x 1.5 */ height: 195px; /* 130px x 1.5 */ border-radius: 75%; /* 50% x 1.5 */ object-fit: cover !important; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } .skinItem img:hover { transform: scale(1.1); box-shadow: 0 0 20px rgba(255, 255, 255, 0.5); } .badge { margin-top: 15px; /* 10px x 1.5 */ font-size: 1.35rem; /* 0.9rem x 1.5 */ text-align: center; background-color: #0d6efd !important; color: white !important; padding: 7.5px 15px; /* 5px 10px x 1.5 */ border-radius: 5px; /* 10px x 1.5 */ max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .profile-image { margin-left: 30px; /* 20px x 1.5 */ width: 50px; /* 40px x 1.5 */ height: 50px; /* 40px x 1.5 */ border-radius: 75%; /* 50% x 1.5 */ cursor: pointer; border: 3px solid white; /* 2px x 1.5 */ } .dropdown-menu.custom-dropdown-menu { min-width: 15rem; padding: 0.75rem 0; font-size: 2.25rem; color: var(--bs-body-color); background-color: #1d202496 !important; border-radius: 15px !important; box-shadow: 0 0.75rem 1.5rem rgba(0, 0, 0, 0.15) !important; filter: drop-shadow(2px 9px 9px black); } .dropdown-menu.custom-dropdown-menu>li>a { display: block; padding: 4.5px 30px !important; /* 3px 20px x 1.5 */ clear: both; font-weight: 400 !important; color: #fafafa !important; white-space: nowrap !important; } .dropdown-menu.custom-dropdown-menu>li>a:hover { background-color: #4A9DEC !important; color: white !important; } .dropdown-menu.custom-dropdown-menu .dropdown-item.disabled { color: #6c757d !important; cursor: not-allowed !important; } .multi-button { display: flex; justify-content: center; margin: 1.5rem auto; /* 1rem x 1.5 */ width: fit-content; } .multi-button > button { font-size: 1.8rem; /* 1.2rem x 1.5 */ padding: 0.75em 1.5em; /* 0.5em 1em x 1.5 */ background: #fff; color: #4A5568; border: 0px solid #A0AEC0; margin: 0.15em; /* 0.1em x 1.5 */ transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease; box-shadow: 0 0 0 #BEE3F8; transform: translateY(0); cursor: pointer; font-weight: bold; } .multi-button > button:first-of-type { border-radius: 0.75em 0 0 0.75em; /* 0.5em x 1.5 */ } .multi-button > button:last-of-type { border-radius: 0 0.75em 0.75em 0; /* 0.5em x 1.5 */ } .multi-button > button:hover { background: #D53F8C; color: #fff; box-shadow: 0 0 1.2em 0 rgba(213, 63, 140, 0.8); /* 0 0 0.8em x 1.5 */ transform: translateY(-0.3em); /* -0.2em x 1.5 */ } .multi-button > button.active { background: #D53F8C !important; color: white !important; box-shadow: 0 0 1.2em 0 rgba(213, 63, 140, 0.8) !important; /* 0 0 0.8em x 1.5 */ transform: translateY(-0.3em); /* -0.2em x 1.5 */ } .button1 { position: absolute; /* Positions the button relative to its container */ right: 10px; /* Pushes the button to the far right */ width: 4em; height: 4em; border: none; background: rgba(180, 83, 107, 0.11); border-radius: 5px; transition: background 0.5s; display: flex; /* Flexbox ensures centering */ justify-content: center; align-items: center; } .X, .Y { content: ""; position: absolute; width: 2em; height: 2px; /* Adjust thickness for better visibility */ background-color: #fff; } .X { transform: rotate(45deg); } .Y { transform: rotate(-45deg); } .button1:hover { background-color: rgb(211, 21, 21); } `; document.head.appendChild(customCSS); const customMySkinsCSS = document.createElement('style'); customMySkinsCSS.innerHTML = ` .profile-upload { width: 150px; height: 150px; border-radius: 75%; cursor: pointer; border: 3px solid #add8e6; } .form-container { display: flex; margin-top: 10px !important; justify-content: center; align-items: flex-start; !important; gap: 5px; /* Add space between elements */ margin-top: 0; /* Adjust as needed for spacing from the top */ } .skin-name-input { position: relative; margin: 20px 0; width: 190px; } .skin-name-input input { background-color: transparent; border: 0; border-bottom: 2px #fff solid; display: block; width: 100%; padding: 15px 0; font-size: 18px; color: #fff; } .skin-name-input input:focus, .skin-name-input input:valid { outline: 0; border-bottom-color: lightblue; } .skin-name-input label { position: absolute; top: 15px; left: 0; pointer-events: none; } .skin-name-input label span { display: inline-block; font-size: 18px; min-width: 5px; color: #fff; transition: 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .skin-name-input input:focus + label span, .skin-name-input input:valid + label span { color: lightblue; transform: translateY(-30px); } .submit-btn { background-color: lightblue; border: none; border-radius: 5px; color: #000; font-size: 18px; padding: 10px 15px; cursor: pointer; transition: background-color 0.3s ease; } .submit-btn:hover { background-color: #007acc; color: #fff; } .submit-btn:active { transform: scale(0.95); } .dot-spinner { --uib-size: 2.8rem; --uib-speed: .9s; --uib-color: #183153; position: relative; display: flex; align-items: center; justify-content: flex-start; height: var(--uib-size); width: var(--uib-size); } .dot-spinner__dot { position: absolute; top: 0; left: 0; display: flex; align-items: center; justify-content: flex-start; height: 100%; width: 100%; } .dot-spinner__dot::before { content: ''; height: 20%; width: 20%; border-radius: 50%; background-color: #ffffff; transform: scale(0); opacity: 0.5; animation: pulse0112 calc(var(--uib-speed) * 1.111) ease-in-out infinite; box-shadow: 0 0 20px rgba(18, 31, 53, 0.3); } .dot-spinner__dot:nth-child(2) { transform: rotate(45deg); } .dot-spinner__dot:nth-child(2)::before { animation-delay: calc(var(--uib-speed) * -0.875); } .dot-spinner__dot:nth-child(3) { transform: rotate(90deg); } .dot-spinner__dot:nth-child(3)::before { animation-delay: calc(var(--uib-speed) * -0.75); } .dot-spinner__dot:nth-child(4) { transform: rotate(135deg); } .dot-spinner__dot:nth-child(4)::before { animation-delay: calc(var(--uib-speed) * -0.625); } .dot-spinner__dot:nth-child(5) { transform: rotate(180deg); } .dot-spinner__dot:nth-child(5)::before { animation-delay: calc(var(--uib-speed) * -0.5); } .dot-spinner__dot:nth-child(6) { transform: rotate(225deg); } .dot-spinner__dot:nth-child(6)::before { animation-delay: calc(var(--uib-speed) * -0.375); } .dot-spinner__dot:nth-child(7) { transform: rotate(270deg); } .dot-spinner__dot:nth-child(7)::before { animation-delay: calc(var(--uib-speed) * -0.25); } .dot-spinner__dot:nth-child(8) { transform: rotate(315deg); } .dot-spinner__dot:nth-child(8)::before { animation-delay: calc(var(--uib-speed) * -0.125); } @keyframes pulse0112 { 0%, 100% { transform: scale(0); opacity: 0.5; } 50% { transform: scale(1); opacity: 1; } } #shine2 { display: none; } @media (max-width: 939px) { .container-fluid { flex-direction: column; /* Stack items vertically */ align-items: flex-start; /* Align content to the left */ } .btn-shine { display: none; } .dropdown-menu.custom-dropdown-menu { right: -15rem; } #shine2 { display: block; top: auto; left: 50%; } @media (max-width: 560px) { .search-container { display: flex; justify-content: center; align-items: center; flex-grow: 1; padding-right: 22.5px; height: 10px; } .search-container .input { height: 2.25em; } .profile-image { margin-left: 30px; width: 35px; height: 35px; border-radius: 75%; cursor: pointer; border: 3px solid white; } .multi-button { display: flex; justify-content: center; margin: 1.5rem auto; width: fit-content; height: 30px; align-items: center; } .dropdown-menu.custom-dropdown-menu { right: -5rem; } .skinItem > img { width: 155px; height: 155px; border-radius: 75%; object-fit: cover !important; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } #mySkinsContainer, #favoritesContainer, #skinsContainer { display: flex; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 20px; padding: 30px; width: 100%; height: 100vh; overflow-y: auto; overflow-x: hidden; margin: 0 auto; align-content: flex-start; } .skinItem::before { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -63%); width: 166px; height: 166px; background-image: var(--bg); background-size: cover; background-position: center; filter: contrast(1.2) brightness(0.6) saturate(1.75) url(#gaussianBlur); border-radius: 50%; z-index: -1; opacity: 0.9; mask: radial-gradient(circle, rgba(0, 0, 0, 1) 75%, rgba(0, 0, 0, 0) 100%); } } `; document.head.appendChild(customMySkinsCSS); (function () { // Append CSS to the document for notifications const notificationCSS = ` .notification { display: flex; flex-direction: column; isolation: isolate; position: fixed; bottom: 5%; /* Spawns in the bottom-left corner */ left: 2%; z-index: 9999; width: 18rem; height: auto; background: #29292c; border-radius: 1rem; overflow: hidden; font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; font-size: 16px; --gradient: linear-gradient(to bottom, #2eadff, #3d83ff, #7e61ff); --color: #32a6ff; opacity: 0; transform: translateY(20px) scale(0.9); transition: opacity 0.3s ease, transform 0.3s ease; } .notification.show { opacity: 1; transform: translateY(0) scale(1); } .notification:before { position: absolute; content: ""; inset: 0.0625rem; border-radius: 0.9375rem; background: #18181b; z-index: 2; } .notification:after { position: absolute; content: ""; width: 0.25rem; inset: 0.65rem auto 0.65rem 0.5rem; border-radius: 0.125rem; background: var(--gradient); transition: transform 300ms ease; z-index: 4; } .notification:hover:after { transform: translateX(0.15rem); } .notititle { color: var(--color); padding: 0.65rem 0.25rem 0.4rem 1.25rem; font-weight: 500; font-size: 1.1rem; visibility: visible; /* Ensure visibility */ } .notibody { color: #99999d; padding: 0 1.25rem; font-size: 0.9rem; visibility: visible; /* Ensure visibility */ } .notification.success { --gradient: linear-gradient(to bottom, #28a745, #218838); --color: #28a745; } .notification.error { --gradient: linear-gradient(to bottom, #dc3545, #c82333); --color: #dc3545; } .notification.info { --gradient: linear-gradient(to bottom, #2eadff, #3d83ff); --color: #32a6ff; } `; // Append the CSS to the document const style = document.createElement("style"); style.innerHTML = notificationCSS; document.head.appendChild(style); // Define the notification system as a window property window.showNotification = function (type, title = "Title", body = "Body", duration = 3000) { if (!title || !body) { console.error("Notification requires a title and body."); return; } const notification = document.createElement("div"); notification.className = `notification ${type}`; notification.innerHTML = ` <div class="notititle">${title}</div> <div class="notibody">${body}</div> `; document.body.appendChild(notification); // Trigger animation requestAnimationFrame(() => { notification.classList.add("show"); }); // Auto-remove after duration setTimeout(() => { notification.classList.remove("show"); setTimeout(() => notification.remove(), 300); // Allow transition to finish }, duration); }; // Example usage message in the console console.log("Notification system loaded. Test it by calling showNotification(type, title, body, duration) from the console."); })(); // JavaScript for Tab Switching function setupTabSwitching() { const publicSkinsTab = document.getElementById('publicSkinsTab'); const mySkinsTab = document.getElementById('mySkinsTab'); const favoritesTab = document.getElementById('favoritesTab'); const skinsContainer = document.getElementById('skinsContainer'); const mySkinsContainer = document.getElementById('mySkinsContainer'); const favoritesContainer = document.getElementById('favoritesContainer'); const title = document.getElementById('shine2'); const title2 = document.getElementById('shine1'); // Function to update tab content function switchTab(tabKey) { // Hide all containers by default skinsContainer.style.display = 'none'; mySkinsContainer.style.display = 'none'; favoritesContainer.style.display = 'none'; if (tabKey === 'public') { // Show the skins container for PUBLIC SKINS title.innerText = 'Public Skins'; title2.innerText = 'Public Skins'; skinsContainer.style.display = 'grid'; skinsContainer.innerHTML = ` <div class="dot-spinner"> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> </div>`; // Fetch and refresh skins when switching to PUBLIC SKINS fetchImages() .then((images) => { renderImagesLazy(images, skinsContainer); console.log("Public skins refreshed successfully!"); }) .catch((error) => { skinsContainer.innerHTML = '<p style="color: red;">Failed to refresh skins. Please try again later.</p>'; console.error("Failed to refresh skins:", error); }); } else if (tabKey === 'mySkins') { // Display custom content for MY SKINS tab title.innerText = 'My Skins'; title2.innerText = 'My Skins'; mySkinsContainer.style.display = 'flex'; mySkinsContainer.style.flexDirection = 'column'; mySkinsContainer.style.justifyContent = 'flex-start'; mySkinsContainer.style.alignItems = 'center'; mySkinsContainer.style.marginTop = '10px'; mySkinsContainer.innerHTML = ` <img src="https://i.imghippo.com/files/IFso7215PtQ.png" alt="Preview" class="profile-upload" id="preview" aria-expanded="false"> <div class="search-container skinurl-container"> <input id="skinName" class="input" style="background-color: #454444;" placeholder="Skin name..."> <input id="skinurl" class="input" style="background-color: #454444;" placeholder="Skin url..."> <button id="submitSkin" class="submit-btn"><span>Confirm</span> <span>Skin</span></button> </div> `; // Get the preview image element const previewImg = document.getElementById('preview'); const urlInput = document.getElementById('skinurl'); const nameInput = document.getElementById('skinName'); // Create a hidden file input element const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.png, .jpeg, .jpg, .gif'; // Restrict to specific file types fileInput.style.display = 'none'; // Hide the input // Append the file input to the body document.body.appendChild(fileInput); // Add a click event to the preview image previewImg.addEventListener('click', () => { fileInput.click(); // Trigger the file input's click }); // Enable or disable the URL input based on the preview image state const toggleUrlInput = () => { if (previewImg.src.startsWith('data:image')) { urlInput.value = ''; urlInput.disabled = true; } else { urlInput.disabled = false; } }; previewImg.addEventListener('load', toggleUrlInput); // Handle file input change fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; if (file) { const validExtensions = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; // Allowed MIME types if (validExtensions.includes(file.type)) { const reader = new FileReader(); reader.onload = (e) => { previewImg.src = e.target.result; // Update the image src }; reader.readAsDataURL(file); } else { alert('Please select a valid image file (PNG, JPEG, or GIF).'); } } }); // Handle skin submission document.getElementById('submitSkin').addEventListener('click', async () => { const name = nameInput.value.trim(); // Get and trim the skin name const owner = protoService?.userInfo?.id || 'defaultOwner'; // Ensure owner ID exists // Check if an image is selected const isImageSelected = previewImg.src.startsWith('data:image'); // Validate inputs if (!name) { alert('Please provide a skin name.'); return; } if (!isImageSelected && !urlInput.value) { alert('Please provide a URL or select an image.'); return; } if (!isImageSelected && !urlInput.value.startsWith('https://i.imgur.com')) { alert('URL must start with https://i.imgur.com.'); return; } try { let url; if (isImageSelected) { console.log('Uploading image to Imgur...'); const base64Image = previewImg.src.split(',')[1]; // Extract Base64 data // Upload image to Imgur const imgurResponse = await fetch('https://api.imgur.com/3/image', { method: 'POST', headers: { Authorization: 'Client-ID 8b2cd28516b0976', // Replace with your Imgur Client ID 'Content-Type': 'application/json', }, body: JSON.stringify({ image: base64Image, type: 'base64' }), }); if (!imgurResponse.ok) { const error = await imgurResponse.json(); console.error('Imgur upload failed:', error); throw new Error('Failed to upload image to Imgur.'); } const imgurData = await imgurResponse.json(); url = imgurData.data.link; // Get the Imgur URL console.log('Image uploaded to Imgur:', url); } else { url = urlInput.value.trim(); // Get the user-provided URL } // Send POST request to the new API endpoint const response = await fetch('https://snay.vercel.app/api/addSkin', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, url, owner }), // JSON-encoded body }); // Check response status if (!response.ok) { const errorData = await response.json(); console.error('Server error details:', errorData); throw new Error(`Error ${response.status}: ${errorData.error || 'Unknown error occurred'}`); } const result = await response.json(); console.log('Skin added successfully. Server response:', result); // Notify the user of success showNotification("success", "Success!", "Skin uploaded successfully!."); if (isImageSelected) { previewImg.src = result.url; // Update the preview image to the uploaded Imgur URL } } catch (error) { console.error('Error during skin submission:', error); alert(`An error occurred: ${error.message}`); } }); } else if (tabKey === 'favorites') { // Display custom content for FAVORITES tab title.innerText = 'Favorites'; title2.innerText = 'Favorites'; favoritesContainer.style.display = 'flex'; favoritesContainer.style.justifyContent = 'center'; favoritesContainer.style.alignItems = 'center'; favoritesContainer.innerHTML = ` <div style="text-align: center;"> <img src="https://media.tenor.com/hB9OTbewrikAAAAi/work-work-in-progress.gif" alt="Work in Progress" style="max-width: 100%; height: auto; border-radius: 10px;"> </div>`; } // Update the active tab button styles publicSkinsTab.classList.remove('active'); mySkinsTab.classList.remove('active'); favoritesTab.classList.remove('active'); if (tabKey === 'public') publicSkinsTab.classList.add('active'); else if (tabKey === 'mySkins') mySkinsTab.classList.add('active'); else if (tabKey === 'favorites') favoritesTab.classList.add('active'); } // Add event listeners to each tab button publicSkinsTab.addEventListener('click', () => switchTab('public')); mySkinsTab.addEventListener('click', () => switchTab('mySkins')); favoritesTab.addEventListener('click', () => switchTab('favorites')); // Set PUBLIC SKINS as the default tab switchTab('public'); } // URL of the API endpoint const API_URL = "https://snay.vercel.app/api/skins"; // Fetch images from the API async function fetchImages() { try { console.log("Starting fetch for skins..."); const response = await fetch(API_URL, { method: 'GET', // Explicitly specify GET method }); if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } const { data } = await response.json(); // Extract `data` property from API response console.log("Fetched skins data:", data); return data; } catch (error) { console.error("Error in fetchImages:", error); throw error; } } const svgFilter = ` <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <filter id="gaussianBlur"> <feGaussianBlur in="SourceGraphic" stdDeviation="10" /> </filter> </svg> `; document.body.insertAdjacentHTML('afterbegin', svgFilter); // Render skins in a lazy-loaded manner function renderImagesLazy(images, container) { container.innerHTML = ""; // Clear previous content if (!images || images.length === 0) { container.innerHTML = '<p style="color: white;">No skins found.</p>'; return; } // Keep track of the currently open dropdown let openDropdown = null; images.forEach((image, index) => { // Create the main skin container const skinDiv = document.createElement("div"); skinDiv.className = "skinItem"; skinDiv.style.position = "relative"; skinDiv.style.setProperty("--bg", `url(${image.url})`); // Create the image element const img = document.createElement("img"); img.src = image.url; img.alt = `Owner: ${image.owner}`; // Set the alt attribute to include _id img.title = image.name; // Set the title to the skin's name // Create a badge for the skin name const badge = document.createElement("span"); badge.className = "badge text-bg-secondary"; badge.textContent = image.name; // Badge click event to call userInfoRequest with owner ID badge.addEventListener("click", (event) => { event.stopPropagation(); // Prevent other click handlers from triggering if (image.owner) { userInfoRequest(image.owner); // Call userInfoRequest with the owner ID } else { console.warn("Owner ID is missing for this skin."); } }); // Create the dropdown container const dropdownContainer = document.createElement("ul"); dropdownContainer.className = "dropdown-menu custom-dropdown-menu dropdown-menu-end"; dropdownContainer.style.cssText = ` display: none; position: absolute; top: 100%; left: 50%; transform: translate(-50%, -200%); z-index: 1000; flex-direction: column; justify-content: center; align-items: center; `; // Create dropdown item for "Vip Skin" const vipDropdownItem = document.createElement("li"); const vipDropdownLink = document.createElement("a"); vipDropdownLink.className = "dropdown-item"; vipDropdownLink.href = "#"; // Create dropdown item for "Custom Skin" const customSkinDropdownItem = document.createElement("li"); const customSkinDropdownLink = document.createElement("a"); customSkinDropdownLink.className = "dropdown-item"; customSkinDropdownLink.href = "#"; // Add an image badge next to the dropdown link const vipBadgeImage = document.createElement("img"); vipBadgeImage.src = "./assets/badges/badge4.png"; vipBadgeImage.alt = "Badge"; vipBadgeImage.className = "badge-image"; vipBadgeImage.style.cssText = ` width: 20px; height: 20px; margin-right: 8px; vertical-align: middle; `; // Add an image badge next to the dropdown link const customBadgeImage = document.createElement("img"); customBadgeImage.src = "./assets/img/SkinsCustom.png"; customBadgeImage.alt = "Custom Skin"; customBadgeImage.className = "badge-image"; customBadgeImage.style.cssText = ` width: 20px; height: 20px; margin-right: 8px; vertical-align: middle; `; vipDropdownLink.appendChild(vipBadgeImage); vipDropdownLink.appendChild(document.createTextNode("Vip Skin")); customSkinDropdownLink.appendChild(customBadgeImage); customSkinDropdownLink.appendChild(document.createTextNode("Custom Skin")); // Dropdown link click handler vipDropdownLink.addEventListener("click", (event) => { event.preventDefault(); const vipSkinInput = document.getElementById("addVipSkin"); if (vipSkinInput) { vipSkinInput.value = image.url; // Set the skin URL } else { console.warn("Input element with id='addVipSkin' not found."); } const vipButton = document.querySelector("#gallery-body > div.vip-div > ul > li:nth-child(1) > button"); if (vipButton) { vipButton.click(); } }); customSkinDropdownLink.addEventListener("click", (event) => { event.preventDefault(); // Prevent default link behavior changeSkin(image.url); // Dynamically pass the URL of the skin }); vipDropdownItem.appendChild(vipDropdownLink); dropdownContainer.appendChild(vipDropdownItem); customSkinDropdownItem.appendChild(customSkinDropdownLink); dropdownContainer.appendChild(customSkinDropdownItem); // Dropdown toggle logic skinDiv.addEventListener("click", (event) => { event.stopPropagation(); // Prevent click event propagation if (openDropdown && openDropdown !== dropdownContainer) { openDropdown.style.display = "none"; // Close other dropdownsF } dropdownContainer.style.display = dropdownContainer.style.display === "block" ? "none" : "block"; openDropdown = dropdownContainer.style.display === "block" ? dropdownContainer : null; }); // Append elements to the skin container skinDiv.appendChild(img); skinDiv.appendChild(badge); skinDiv.appendChild(dropdownContainer); // Append skin container to the main container container.appendChild(skinDiv); // Apply lazy-loading animation with delay setTimeout(() => { skinDiv.classList.add("loaded"); }, index * 100); // 100ms delay per skin }); // Global click listener to close dropdowns when clicking outside document.addEventListener("click", () => { if (openDropdown) { openDropdown.style.display = "none"; openDropdown = null; // Reset the tracker } }); } // Example usage const skinsContainer = document.getElementById("skinsContainer"); fetchImages() .then((images) => renderImagesLazy(images, skinsContainer)) .catch((error) => { skinsContainer.innerHTML = '<p style="color: red;">Failed to load skins.</p>'; }); // Main Function async function main() { const sideButtons = document.querySelector('#main-menu .side-buttons') || await waitForElement('#main-menu .side-buttons'); // Create the "Public Skins" button const button = document.createElement('button'); button.className = 'btn side-btn'; button.id = 'PublicSkins'; button.style.cssText = ` background-color: rgb(139 92 246 / 0%); color: white; border-radius: 0px; padding: 16px 40px; font-size: 1.2rem; background-image: url(https://i.postimg.cc/wBhr5Ftc/SlF1SEF.png); `; // Ensure the sideButtons container is set up sideButtons.style.display = 'flex'; sideButtons.style.flexDirection = 'column'; sideButtons.style.alignItems = 'center'; sideButtons.prepend(button); // Create the drawer const drawer = document.createElement('div'); drawer.className = 'offcanvas offcanvas-bottom'; drawer.tabIndex = -1; drawer.id = 'publicSkinsDrawer'; drawer.setAttribute('aria-labelledby', 'publicSkinsDrawerLabel'); drawer.style.height = '100%'; drawer.style.backgroundColor = '#1d2024'; drawer.innerHTML = ` <nav class="navbar navbar-expand-lg"> <div class="container-fluid"> <div class="search-container"> <button class="button1" id="closebtn"> <span class="X"></span><span class="Y"></span></button> <input id="customInputField" class="input" placeholder="Search skins..."> <div class="dropdown profile-container"> <img src="https://i.imgur.com/V4RclNb.png" alt="Profile" class="profile-image dropdown-toggle" id="profileDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <ul class="dropdown-menu custom-dropdown-menu dropdown-menu-end" aria-labelledby="profileDropdown"> <li><a class="dropdown-item" href="#" id="discord-login-btn" data-action="login">Login</a></li> <li><a class="dropdown-item" href="#" id="discord-logout-btn" data-action="logout" style="display: none;">Logout</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="#" id="refreshSkins">Refresh Skins</a></li> </ul> </div> </div> <a href="#" id="shine1" class="btn-shine">Public Skins</a> </div> </nav> <div class="multi-button"> <button id="publicSkinsTab">PUBLIC SKINS</button> <button id="mySkinsTab">MY SKINS</button> <button id="userPPP">hello</button> <button id="favoritesTab">❤️</button> </div> <div class="offcanvas-body"> <a href="#" id="shine2" class="btn-shine">Public Skins</a> <div id="skinsContainer" style="display: none;"></div> <div id="mySkinsContainer" style="display: none;"></div> <div id="favoritesContainer" style="display: none;"></div> </div> `; // Add the drawer to the body document.body.appendChild(drawer); // Configure the Public Skins button to open the drawer button.setAttribute('data-bs-toggle', 'offcanvas'); button.setAttribute('data-bs-target', '#publicSkinsDrawer'); // Ensure Bootstrap initializes the drawer correctly //const drawerInstance = new bootstrap.Offcanvas(drawer); // Check if the close button and drawer elements exist before proceeding const closeButton = document.getElementById("closebtn"); // Only attach the event listener if both elements exist if (closeButton && drawer) { closeButton.addEventListener("click", function () { const bsDrawer = bootstrap.Offcanvas.getInstance(drawer); if (bsDrawer) { bsDrawer.hide(); console.log('Drawer closed.'); } else { console.log('Bootstrap Offcanvas instance not found for the drawer.'); } }); } else { if (!closeButton) { console.log('Close button with ID "closebtn" not found.'); } if (!drawer) { console.log('Drawer element with ID "publicSkinsDrawer" not found.'); } } // Discord OAuth URL const DISCORD_OAUTH_URL = "https://discord.com/api/oauth2/authorize?client_id=1266230711690596455&redirect_uri=https://snay.vercel.app/api/discord&response_type=code&scope=identify"; function updateButtons() { const storedUsername = localStorage.getItem("discordUsername"); const storedToken = localStorage.getItem("discordToken"); const username = new URLSearchParams(window.location.search).get("username") || storedUsername; const token = new URLSearchParams(window.location.search).get("token") || storedToken; const loginButton = document.getElementById("discord-login-btn"); const logoutButton = document.getElementById("discord-logout-btn"); const profileImage = document.getElementById("profileDropdown"); // Profile image element const userprofile = document.getElementById("userPPP"); if (username && token) { loginButton.innerText = `Logged in as ${username}`; loginButton.disabled = true; userprofile.innerText = `${username}`; loginButton.style.display = "none"; logoutButton.style.display = "block"; userprofile.style.display = "block"; if (!storedUsername || !storedToken) { // Store username and token if not already stored localStorage.setItem("discordUsername", username); localStorage.setItem("discordToken", token); } // Fetch Discord user information fetch('https://discord.com/api/users/@me', { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, }) .then(response => { if (!response.ok) { throw new Error(`Failed to fetch user info: ${response.status}`); } return response.json(); }) .then(userData => { console.log("Fetched Discord user data:", userData); // Update profile picture const avatarURL = userData.avatar ? `https://cdn.discordapp.com/avatars/${userData.id}/${userData.avatar}.png` : `https://cdn.discordapp.com/embed/avatars/${userData.discriminator % 5}.png`; // Default avatar profileImage.src = avatarURL; }) .catch(error => { console.error("Error fetching Discord user info:", error); profileImage.src = "https://i.imgur.com/V4RclNb.png"; // Fallback profile image }); // Remove query parameters from URL if (new URLSearchParams(window.location.search).has("username")) { history.replaceState(null, null, window.location.pathname); } } else { loginButton.style.display = "block"; loginButton.innerText = "Login with Discord"; loginButton.disabled = false; logoutButton.style.display = "none"; userprofile.style.display = "none"; // Reset to default profile picture if (profileImage) { profileImage.src = "https://i.imgur.com/V4RclNb.png"; } } } // Create Login Button Behavior function createLoginButton() { const loginButton = document.getElementById("discord-login-btn"); loginButton.addEventListener("click", (event) => { event.preventDefault(); window.location.href = DISCORD_OAUTH_URL; }); } // Create Logout Button Behavior function createLogoutButton() { const logoutButton = document.getElementById("discord-logout-btn"); logoutButton.addEventListener("click", (event) => { event.preventDefault(); localStorage.removeItem("discordUsername"); localStorage.removeItem("discordToken"); updateButtons(); }); } // Initialize Buttons if (document.readyState === "complete" || document.readyState === "interactive") { createLoginButton(); createLogoutButton(); updateButtons(); } else { document.addEventListener("DOMContentLoaded", () => { createLoginButton(); createLogoutButton(); updateButtons(); }); } button.setAttribute('data-bs-toggle', 'offcanvas'); button.setAttribute('data-bs-target', '#publicSkinsDrawer'); const skinsContainer = document.getElementById('skinsContainer'); const customInputField = document.getElementById('customInputField'); // Tooltip Initialization const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); setupTabSwitching(); // Refresh Skins Functionality document.getElementById('refreshSkins').addEventListener('click', async () => { window.showNotification("error", "Error", "Something went wrong.", 3000); skinsContainer.innerHTML = ` <div class="dot-spinner"> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> <div class="dot-spinner__dot"></div> </div>`; try { const images = await fetchImages(); renderImagesLazy(images, skinsContainer); console.log("Skins refreshed successfully!"); } catch (error) { console.log("Failed to refresh skins."); } }); try { const images = await fetchImages(); renderImagesLazy(images, skinsContainer); // Add search functionality customInputField.addEventListener('input', () => { const searchTerm = customInputField.value.toLowerCase(); const filteredImages = images.filter(image => image.name.toLowerCase().includes(searchTerm)); renderImagesLazy(filteredImages, skinsContainer); }); } catch (error) { skinsContainer.innerHTML = '<p style="color: white;">Failed to load skins. Please try again later.</p>'; } } main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址