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 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); 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}

`; document.getElementById('btn_poke_cancel').onclick = () => closeLauncherAlert('poke_completed'); document.getElementById('btn_poke_confirm').onclick = handlePoke; }; const refreshCount = () => fetch(`${apiUrl}?pokeCount=1`) .then(res => res.json()) .then(data => { const n = Number(data.count) || 0; document.getElementById('poke_title').innerHTML = `현재까지 ${n}회 찔렸습니다!`; }) .catch(() => { document.getElementById('poke_title').innerText = '현재 횟수를 불러올 수 없음'; }); const handlePoke = () => { const sender = document.getElementById('poke_sender').value.trim() || '익명'; const message = document.getElementById('poke_message').value.trim() || ''; const statusField = document.getElementById('poke_status'); const confirmBtn = document.getElementById('btn_poke_confirm'); confirmBtn.disabled = true; statusField.innerText = '전송 중...'; fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ action: 'poke', sender, message }) }) .then(res => res.ok ? res.json() : Promise.reject()) .then(() => refreshCount()) .then(() => { statusField.innerText = '성공!'; setTimeout(() => { confirmBtn.disabled = false; statusField.innerText = ''; }, 300); }) .catch(() => { statusField.innerText = '오류 발생'; confirmBtn.disabled = false; }); }; renderMain(); refreshCount(); } function createFormHeader(text, onBack) { const header = document.createElement('div'); header.className = 'launcher-form-header'; const backLink = document.createElement('a'); backLink.innerHTML = ' 목록'; backLink.className = 'launcher-back'; backLink.addEventListener('click', onBack); const title = document.createElement('span'); title.className = 'headerTitle'; title.textContent = text; header.appendChild(backLink); header.appendChild(title); return header; } function createPasswordInput(id, placeholder) { const input = document.createElement('input'); input.type = 'password'; input.placeholder = placeholder; input.id = id; input.autocomplete = 'current-password'; return input; } function createButton(text) { const button = document.createElement('button'); button.type = 'button'; button.textContent = text; return button; } function launcherAlert(name) { if (typeof showAlert === 'function') { showAlert(name); } } function closeLauncherAlert(name) { if (typeof closeAlert === 'function') { closeAlert(name); } } document.addEventListener('DOMContentLoaded', () => { renderIcons(); const findShortcut = document.getElementById('findMyShortcut'); if (findShortcut) { findShortcut.style.userSelect = 'none'; findShortcut.style.webkitUserSelect = 'none'; findShortcut.style.msUserSelect = 'none'; findShortcut.style.MozUserSelect = 'none'; findShortcut.style.webkitTouchCallout = 'none'; findShortcut.addEventListener('dragstart', e => e.preventDefault()); } }); if (location.pathname.split('/').filter(Boolean).length === 1) { document.title = '블로그 - 채건닷컴'; }