let myName = localStorage.getItem('chat_username') || '';
let myRole = 'user'; // user | admin | mod | viewer
let isBanned = false;
let adminPW = localStorage.getItem('chat_admin_pw') || null;
let banned = new Set(JSON.parse(localStorage.getItem('chat_banned') || '[]'));
let viewers = new Set(JSON.parse(localStorage.getItem('chat_viewers') || '[]'));
let mods = new Set(JSON.parse(localStorage.getItem('chat_mods') || '[]'));
let online = new Map(); // name → {role, banned?, viewer?}
// ──────────────────────────────────────────────
function init() {
if (!myName) {
myName = prompt('ユーザー名を入力してください', 'Guest' + Math.floor(Math.random()*1000));
if (!myName || myName.trim()==='') myName = 'Guest';
localStorage.setItem('chat_username', myName);
}
mynameEl.textContent = myName;
// 管理者チェック
if (adminPW && confirm('管理者としてログインしますか?')) {
const pw = prompt('管理者パスワードを入力');
if (pw === adminPW) {
myRole = 'admin';
addSystem('管理者モードでログインしました');
}
}
// 自分の状態確認
if (banned.has(myName)) {
isBanned = true;
addSystem('あなたはBANされています(閲覧のみ)');
} else if (viewers.has(myName)) {
myRole = 'viewer';
addSystem('閲覧専用モードです');
} else if (mods.has(myName)) {
myRole = 'mod';
}
input.disabled = isBanned || myRole==='viewer';
sendBtn.disabled = isBanned || myRole==='viewer';
// 参加通知
broadcast({t:'join', n:myName, r:myRole});
input.focus();
input.onkeydown = e => { if (e.key==='Enter') { e.preventDefault(); send(); }};
sendBtn.onclick = send;
// 生存確認(30秒毎)
setInterval(() => {
if (!isBanned) broadcast({t:'hb', n:myName, r:myRole});
}, 30000);
setInterval(updateUsers, 8000);
}
if (!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});
});