const shortcut = document.getElementById('findMyShortcut'); let failIndex = 0; const apiUrl = '/custom/launcher/api.php'; const iconItems = [ { id: 'findmydevice', img: '/custom/launcher/img/find-my.svg', label: '나의 찾기', onclick: () => requireAccess(() => location.href = '/findmydevice') }, { id: 'wakeonlan', img: '/custom/launcher/img/wakeonlan-svgrepo-com.svg', label: 'PC 켜기', onclick: () => requireAccess(wakeComputer) }, { id: 'poke', img: '/custom/launcher/img/R1280x0.jpg', label: '콕 찌르기', onclick: showPokeForm }, { id: 'car', img: '/custom/launcher/img/car.png', label: '니모', onclick: () => requireAccess(() => location.href = 'https://seo.chaegeon.com/car/monitor.php') } ]; function renderIcons() { shortcut.className = 'find-container'; shortcut.innerHTML = ''; iconItems.forEach(item => { const div = document.createElement('div'); div.className = 'iconContainer'; div.id = item.id; if (item.onclick) div.addEventListener('click', item.onclick); const img = document.createElement('img'); img.src = item.img; img.alt = item.label; const label = document.createElement('div'); label.className = 'iconLabel'; label.textContent = item.label; div.appendChild(img); div.appendChild(label); shortcut.appendChild(div); }); const manage = document.createElement('button'); manage.type = 'button'; manage.className = 'launcher-manage'; manage.textContent = '관리'; manage.addEventListener('click', () => showPasswordChangeForm()); shortcut.appendChild(manage); } async function requireAccess(nextAction) { try { const session = await fetch(`${apiUrl}?session=1`, { credentials: 'same-origin' }).then(res => res.json()); if (session.authenticated) { nextAction && nextAction(); return; } } catch (_) { } showPasswordForm(nextAction); } function showPasswordForm(onSuccess) { shortcut.className = 'password-container'; shortcut.innerHTML = ''; const title = createFormHeader('비밀번호', renderIcons); const input = createPasswordInput('blog_secret', '비밀번호'); const button = createButton('확인'); const changeButton = createTextButton('암호 변경', () => showPasswordChangeForm()); const msg = document.createElement('div'); msg.id = 'failMsg'; const inputRow = document.createElement('div'); inputRow.className = 'launcher-input-row'; inputRow.appendChild(input); inputRow.appendChild(button); const column = document.createElement('div'); column.className = 'launcher-form-column'; column.appendChild(title); column.appendChild(inputRow); column.appendChild(changeButton); shortcut.appendChild(column); const submit = async () => { const password = input.value; try { const messageRes = await fetch(`${apiUrl}?failMessages=1`); const messagesJson = await messageRes.json(); const failMessages = messagesJson.failMessages || []; const res = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ action: 'login', password }) }); const data = await res.json(); if (res.ok && data.result === 'ok') { if (typeof onSuccess === 'function') { onSuccess(); } else { renderIcons(); } return; } column.appendChild(msg); msg.textContent = failMessages[failIndex] || '비밀번호가 틀렸습니다.'; failIndex = (failIndex + 1) % Math.max(1, failMessages.length); input.value = ''; input.focus(); } catch (_) { column.appendChild(msg); msg.textContent = '요청을 처리하지 못했습니다.'; } }; button.addEventListener('click', submit); input.addEventListener('keydown', e => { if (e.key === 'Enter') submit(); }); input.focus(); } function showPasswordChangeForm() { shortcut.className = 'password-container'; shortcut.innerHTML = ''; const title = createFormHeader('암호 변경', renderIcons); const currentInput = createPasswordInput('current_password', '현재 암호'); const newInput = createPasswordInput('new_password', '새 암호'); const confirmInput = createPasswordInput('confirm_password', '새 암호 확인'); const button = createButton('변경'); const msg = document.createElement('div'); msg.id = 'failMsg'; const column = document.createElement('div'); column.className = 'launcher-form-column'; column.appendChild(title); column.appendChild(currentInput); column.appendChild(newInput); column.appendChild(confirmInput); column.appendChild(button); column.appendChild(msg); shortcut.appendChild(column); const submit = async () => { const currentPassword = currentInput.value; const newPassword = newInput.value; const confirmPassword = confirmInput.value; if (newPassword !== confirmPassword) { msg.textContent = '새 암호가 일치하지 않습니다.'; return; } try { const res = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ action: 'changePassword', currentPassword, newPassword }) }); const data = await res.json(); if (res.ok && data.result === 'ok') { msg.style.color = '#34c759'; msg.textContent = '변경되었습니다.'; setTimeout(renderIcons, 700); return; } msg.style.color = 'red'; msg.textContent = data.message === 'password_too_short' ? '새 암호는 4자 이상이어야 합니다.' : '현재 암호가 틀립니다.'; } catch (_) { msg.style.color = 'red'; msg.textContent = '요청을 처리하지 못했습니다.'; } }; button.addEventListener('click', submit); [currentInput, newInput, confirmInput].forEach(input => { input.addEventListener('keydown', e => { if (e.key === 'Enter') submit(); }); }); currentInput.focus(); } async function wakeComputer() { try { const data = await fetch('/custom/wakeonlan/api.php?status=1', { credentials: 'same-origin' }) .then(res => res.ok ? res.json() : Promise.reject()); if (!data || data.result !== 'ok') { launcherAlert('error'); return; } if (data.state === 'on') { launcherAlert('wakeonlan_already'); return; } await fetch('/custom/wakeonlan/api.php', { method: 'POST', credentials: 'same-origin' }) .then(res => res.ok ? res.json() : Promise.reject()); launcherAlert('wakeonlan_request_sent'); let attempts = 0; const interval = setInterval(() => { attempts++; fetch('/custom/wakeonlan/api.php?status=1', { credentials: 'same-origin' }) .then(res => res.ok ? res.json() : Promise.reject()) .then(status => { if (status.state === 'on') { clearInterval(interval); launcherAlert('wakeonlan_success'); } else if (attempts >= 12) { clearInterval(interval); } }) .catch(() => clearInterval(interval)); }, 5000); } catch (_) { launcherAlert('error'); } } function showPokeForm() { launcherAlert('poke_completed'); const alertBox = document.querySelector('#poke_completed'); const content = alertBox?.querySelector('.alert-content'); if (!content) return; content.style.minHeight = 'auto'; content.style.padding = '15px 20px 10px 20px'; content.style.justifyContent = 'flex-start'; const renderMain = (countText = '조회 중...') => { content.innerHTML = `
${countText}