(!adminPW) {
const pw = prompt('初回:管理者パスワードを設定してください(空でスキップ可)');
if (pw && pw.trim()) {
localStorage.setItem('chat_admin_pw', pw.trim());
adminPW = pw.trim();
alert('管理者パスワードを設定しました');
}
}
init();
// ──────────────────────────────────────────────
function broadcast(obj) {
channel.postMessage(obj);
}
function send() {
let text = input.value.trim();
if (!text) return;
if (text.startsWith('/')) {
handleCommand(text);
} else if (!isBanned && myRole !== 'viewer') {
broadcast({t:'msg', n:myName, txt:text, time:new Date().toLocaleTimeString('ja-JP',{hour12:false})});
}
input.value = '';
}
function handleCommand(cmd) {
const parts = cmd.slice(1).trim().split(/\s+/);
const op = parts[0].toLowerCase();
const target = parts[1];
if (!target && !['delete'].includes(op)) {
addSystem('使い方例: /ban ユーザー名 /mod ユーザー名 /viewer ユーザー名 /delete');
return;
}
const isAdmin = myRole==='admin';
const isMod = myRole==='admin' || myRole==='mod';
switch(op) {
case 'ban':
if (!isMod) return addSystem('権限がありません');
broadcast({t:'ban', target});
break;
case 'viewer':
if (!isMod) return addSystem('権限がありません');
broadcast({t:'viewer', target});
break;
case 'mod':
if (!isAdmin) return addSystem('管理者しか実行できません');
broadcast({t:'mod', target});
break;
case 'delete':
if (!isMod) return addSystem('権限がありません');
broadcast({t:'clear'});
break;
default:
addSystem('不明なコマンドです');
}
}
// ──────────────────────────────────────────────
channel.onmessage = e => {
const d = e.data;
switch(d.t) {
case 'msg':
if (banned.has(d.n)) return;
addMsg(d.n, d.txt, d.time, d.n === myName);
break;
case 'join':
case 'hb':
online.set(d.n, {r:d.r || 'user'});
updateUsers();
if (d.t==='join' && d.n !== myName) {
addSystem(`${d.n} が参加しました`);
}
break;
case 'ban':
if (!['admin','mod'].includes(myRole)) return;
banned.add(d.target);
localStorage.setItem('chat_banned', JSON.stringify([...banned]));
addSystem(`${d.target} をBANしました`);
if (d.target === myName) {
isBanned = true;
input.disabled = sendBtn.disabled = true;
addSystem('あなたはBANされました');
}
updateUsers();
break;
case 'viewer':
if (!['admin','mod'].includes(myRole)) return;
viewers.add(d.target);
localStorage.setItem('chat_viewers', JSON.stringify([...viewers]));
addSystem(`${d.target} を閲覧専用にしました`);
updateUsers();
break;
case 'mod':
if (myRole !== 'admin') return;
mods.add(d.target);
localStorage.setItem('chat_mods', JSON.stringify([...mods]));
addSystem(`${d.target} を仮管理者にしました`);
updateUsers();
break;
case 'clear':
if (!['admin','mod'].includes(myRole)) return;
messages.innerHTML = '';
addSystem('=== 管理者により全メッセージが削除されました ===');
break;
}
};
// ──────────────────────────────────────────────
function addMsg(name, text, time, mine) {
const div = document.createElement('div');
div.className = `msg ${mine ? 'mine' : 'other'} ${banned.has(name) ? 'banned' : ''}`;
div.innerHTML = `${name} (${time})
${text.replace(/\n/g,'
')}`;
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}
function addSystem(text) {
const div = document.createElement('div');
div.className = 'msg system';
div.textContent = text;
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}
function updateUsers() {
userlist.innerHTML = '';
let cnt = 0;
online.forEach((info, name) => {
cnt++;
const div = document.createElement('div');
div.className = 'user';
if (info.r === 'admin') div.classList.add('admin');
if (info.r === 'mod') div.classList.add('mod');
if (viewers.has(name)) div.classList.add('viewer');
if (banned.has(name)) div.classList.add('banned');
div.textContent = name;
userlist.appendChild(div);
});
status.textContent = `オンライン: ${cnt} 人`;
}
function logout() {
if (confirm('ログアウトしますか?(名前がリセットされます)')) {
localStorage.removeItem('chat_username');
location.reload();
}
}
// ページ離脱時に退出通知(ベストエフォート)
window.addEventListener('beforeunload', () => {
broadcast({t:'leave', n:myName});
});