Repository navigation
Expand file tree
/
Copy pathchat.js
More file actions
83 lines (83 loc) · 25.5 KB
/
Copy pathchat.js
File metadata and controls
83 lines (83 loc) · 25.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
let twData=null,twDirty=false,twBusy=false;const twTime=t=>new Date(t*1000).toLocaleTimeString(LOC(),{hour:"2-digit",minute:"2-digit"});function twStatusHtml(d){const s=d.status||{};if(s.ok==null)return d.enabled?"<span>Noch nichts gesendet</span>":"";const t=twTime(s.time);if(s.ok)return`<span>Letzte Meldung um ${t} Uhr:</span> <span translate="no">${esc(s.text)}</span>`;return`<span>Fehler um ${t} Uhr:</span> <span>${esc(s.text)}</span>`+(s.retry_at?` <span>Neuer Versuch um ${twTime(s.retry_at)} Uhr.</span>`:"");}
function twButtons(){const ok=!!(twData&&((twData.token_set&&twData.channel&&twData.login)||(twData.account&&twData.channel)||(twData.bot&&twData.channel)));$("tw_save").disabled=twBusy;$("tw_test").disabled=twBusy||twDirty||!ok;$("tw_test").title=twDirty?"Zuerst speichern":"";}
function twFill(d){twData=d;$("tw_sum").textContent=d.enabled?"· an":"· a
8000
us";setHtml($("tw_status"),twStatusHtml(d));$("tw_status").style.color=d.status&&d.status.ok===false?"var(--crit)":"";if(!twDirty){$("tw_on").checked=!!d.enabled;$("tw_channel").value=d.channel||"";$("tw_login").value=d.login||"";$("tw_token").value="";$("tw_token").placeholder=d.token_set?"gespeichert":"";$("tw_thr").value=d.threshold;$("tw_msg").value=d.message||"";$("tw_live").checked=d.only_live!==false;}
twButtons();}
async function twLoad(){let d;try{d=await srtlaCall("GET","/api/twitch");}catch(e){return;}
twFill(d);twbLoad();}
function twForm(){return{enabled:$("tw_on").checked,channel:$("tw_channel").value,login:$("tw_login").value,token:$("tw_token").value,threshold:parseInt($("tw_thr").value,10),message:$("tw_msg").value,only_live:$("tw_live").checked};}
async function twSave(){const err=$("tw_err");err.textContent="";err.style.color="";twBusy=true;twButtons();try{const d=await srtlaCall("POST","/api/twitch",twForm());twDirty=false;twFill(d);err.textContent="Gespeichert.";err.style.color="var(--ok)";}
catch(x){err.textContent=x.message;}
twBusy=false;twButtons();}
async function twTest(){const err=$("tw_err");err.textContent="Wird gesendet …";err.style.color="";twBusy=true;twButtons();try{const r=await srtlaCall("POST","/api/twitch/test",{});err.textContent=r.message;err.style.color=r.ok?"var(--ok)":"";}
catch(x){err.textContent=x.message;err.style.color="";}
twBusy=false;twButtons();twLoad();}
$("tw_save").addEventListener("click",twSave);$("tw_test").addEventListener("click",twTest);["input","change"].forEach(ev=>$("tw_sec").addEventListener(ev,()=>{twDirty=true;twButtons();}));$("tw_sec").addEventListener("toggle",()=>{if($("tw_sec").open)twLoad();});twLoad();setInterval(()=>{if(due("tw","c_cams",60000))twLoad();},10000);let saData=null,saDirty=false,saBusy=false;function saStatusHtml(d){const parts=[];if(d.enabled&&!d.ready)parts.push("<span>Twitch ist noch nicht eingerichtet: Anmeldung im Bereich „Chat“ oder Bot-Konto bei der Akku-Warnung.</span>");const v=d.values||{};if(d.enabled&&d.ready&&(d.alerting||[]).length)parts.push("<span>Gerade über der Grenze: "+d.alerting.map(k=>({temp:"Temperatur",cpu:"CPU-Last",ram:"RAM",fps:"Kamera unter 20 fps"})[k]).join(", ")+"</span>");const s=d.last;if(s)parts.push(s.ok?`<span>Letzte Meldung um ${twTime(s.time)} Uhr:</span> <span translate="no">${esc(s.text)}</span>`:`<span>Fehler um ${twTime(s.time)} Uhr:</span> <span>${esc(s.text)}</span>`);return parts.join(" ");}
function saButtons(){const ok=!!(saData&&saData.ready);$("sa_save").disabled=saBusy;$("sa_test").disabled=saBusy||saDirty||!ok;$("sa_test").title=saDirty?"Zuerst speichern":"";}
function saFill(d){saData=d;$("sa_sum").textContent=d.enabled?"· an":"· aus";setHtml($("sa_status"),saStatusHtml(d));if(!saDirty){$("sa_on").checked=!!d.enabled;[["t","temp"],["c","cpu"],["r","ram"]].forEach(([i,k])=>{$("sa_"+i+"_on").checked=!!d[k].on;$("sa_"+i).value=d[k].limit;});$("sa_cd").value=d.cooldown;$("sa_rec").checked=!!d.recover;$("sa_live").checked=d.only_live!==false;$("sa_cmdt").checked=!!d.cmd_temp;$("sa_cmds").checked=!!d.cmd_status;$("sa_cmdv").checked=!!d.cmd_vip;$("sa_cmdp").checked=!!d.cmd_pixel;$("sa_cmdph").checked=!!d.pixel_head_mod;$("sa_f_on").checked=d.fps_on!==false;$("sa_msg_a").value=d.msg_alert||"";$("sa_msg_o").value=d.msg_ok||"";$("sa_msg_f").value=d.msg_fps||"";$("sa_msg_t").value=d.msg_temp||"";$("sa_msg_so").value=d.msg_st_ok||"";$("sa_msg_sw").value=d.msg_st_warn||"";$("sa_msg_sc").value=d.msg_st_crit||"";}
saButtons();}
async function saLoad(){let d;try{d=await srtlaCall("GET","/api/sysalerts");}catch(e){return;}
saFill(d);}
function saForm(){const n=id=>parseInt($(id).value,10);return{enabled:$("sa_on").checked,cooldown:n("sa_cd"),recover:$("sa_rec").checked,only_live:$("sa_live").checked,cmd_temp:$("sa_cmdt").checked,cmd_status:$("sa_cmds").checked,cmd_vip:$("sa_cmdv").checked,cmd_pixel:$("sa_cmdp").checked,pixel_head_mod:$("sa_cmdph").checked,fps_on:$("sa_f_on").checked,temp:{on:$("sa_t_on").checked,limit:n("sa_t")},cpu:{on:$("sa_c_on").checked,limit:n("sa_c")},ram:{on:$("sa_r_on").checked,limit:n("sa_r")},msg_alert:$("sa_msg_a").value,msg_ok:$("sa_msg_o").value,msg_fps:$("sa_msg_f").value,msg_temp:$("sa_msg_t").value,msg_st_ok:$("sa_msg_so").value,msg_st_warn:$("sa_msg_sw").value,msg_st_crit:$("sa_msg_sc").value};}
async function saSave(){const err=$("sa_err");err.textContent="";err.style.color="";saBusy=true;saButtons();try{const d=await srtlaCall("POST","/api/sysalerts",saForm());saDirty=false;saFill(d);err.textContent="Gespeichert.";err.style.color="var(--ok)";}
catch(x){err.textContent=x.message;}
saBusy=false;saButtons();}
async function saTest(){const err=$("sa_err");err.textContent="Wird gesendet …";err.style.color="";saBusy=true;saButtons();try{const r=await srtlaCall("POST","/api/twitch/test",{});err.textContent=r.message;err.style.color=r.ok?"var(--ok)":"";}
catch(x){err.textContent=x.message;err.style.color="";}
saBusy=false;saButtons();saLoad();}
$("sa_save").addEventListener("click",saSave);$("sa_test").addEventListener("click",saTest);["input","change"].forEach(ev=>$("sa_sec").addEventListener(ev,()=>{saDirty=true;saButtons();}));$("sa_sec").addEventListener("toggle",()=>{if($("sa_sec").open)saLoad();});saLoad();setInterval(()=>{if(due("sa","c_cams",60000))saLoad();},10000);let twAccPoll=null,twAcc=null,twWasWait=false,twShowDone=false,twDoneT=0;function twAccRender(a){if(!a)return;const t=$("tw_acc_t"),go=$("tw_acc_go"),code=$("tw_acc_code"),on=a.state==="angemeldet";twAcc=a;const wait=a.state==="wartet",other=a.role==="none"&&!!a.streamer;$("tw_chrow").hidden=!a.need_channel;["tw_ch_in","tw_ch2_in"].forEach(id=>{if($(id)!==document.activeElement)$(id).value=a.streamer||"";});$("tw_ch_t").textContent=a.need_channel?"Zuerst eintragen, dann anmelden.":"";go.textContent=other?"Zuschauer":"Mit Twitch anmelden";$("tw_acc_asmod").textContent="Moderator";$("tw_acc_owner").textContent="Streamer";$("tw_acc_owner").hidden=!(other&&!wait&&!on);$("tw_acc_asmod").hidden=!(other&&!wait&&!on);$("tw_accrow").classList.toggle("twwho",other&&!wait&&!on);$("tw_acc_mod").hidden=!(on&&!a.mod);$("tw_acc_modoff").hidden=!(on&&a.mod);$("tw_acc_ev").hidden=!(on&&!a.events);const justDone=on&&twWasWait;twWasWait=wait;if(justDone){twShowDone=true;clearTimeout(twDoneT);twDoneT=setTimeout(()=>{[$("tw_accrow"),code].forEach(e=>e.classList.add("twfade"));twDoneT=setTimeout(()=>{twShowDone=false;[$("tw_accrow"),code].forEach(e=>e.classList.remove("twfade"));twAccRender(twAcc);},700);},3500);}
if(!on){twShowDone=false;clearTimeout(twDoneT);[$("tw_accrow"),code].forEach(e=>e.classList.remove("twfade"));}
code.hidden=!(wait||(on&&twShowDone));go.hidden=wait||on||!!a.need_channel;$("tw_accrow").hidden=on&&!twShowDone;$("tw_accpanel").hidden=!on;$("tw_acc_t2").textContent=on?(a.role==="mod"?`Angemeldet als ${a.login} · Moderator`:a.role==="user"?`Angemeldet als ${a.login} · Zuschauer`:a.mod?`Angemeldet als ${a.login} · Moderation an`:`Angemeldet als ${a.login}`):"";$("tw_acc_ev_on").hidden=!(on&&a.events);if(on)t.textContent="Angemeldet als "+a.login;else if(wait)t.textContent="Warte auf die Bestätigung bei Twitch …";else if(a.state==="abgelaufen")t.textContent="Anmeldung abgelaufen";else if(a.state==="fehler")t.textContent=a.error||"Anmeldung fehlgeschlagen";else if(other)t.textContent=a.box_login?`Streamer: ${a.box_login}. Anmelden als:`:`Streamer (${a.streamer}) noch nicht angemeldet. Anmelden als:`;else t.textContent="Nicht angemeldet";if(wait){$("tw_code").textContent=a.code||"";const l=$("tw_link");l.href=a.uri||"#";l.textContent=(a.uri||"").replace(/^https?:\/\//,"").replace(/\?.*$/,"")||"twitch.tv/activate";const q=a.uri?QR.svg(a.uri):"";if($("tw_qr").dataset.u!==a.uri){$("tw_qr").innerHTML=q;$("tw_qr").dataset.u=a.uri||"";}}
$("chat_form").hidden=a.state!=="angemeldet";twWho();twAdvVis();if(wait&&!twAccPoll)twAccPoll=setInterval(()=>{if(!document.hidden)twAccLoad();},2000);if(!wait&&twAccPoll){clearInterval(twAccPoll);twAccPoll=null;if(typeof twLoad==="function")twLoad();}}
async function twAccLoad(){try{twAccRender(await srtlaCall("GET","/api/twitch/login"));}catch(e){}}
$("tw_acc_go").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",twAcc&&twAcc.role==="none"?{action:"start",as:"user"}:{action:"start",all:true}));}catch(x){$("tw_acc_t").textContent=x.message;}});$("tw_acc_asmod").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",{action:"start",as:"mod"}));}catch(x){$("tw_acc_t").textContent=x.message;}});async function twChanSave(id){const v=$(id).value.trim();try{await srtlaCall("POST","/api/twitch",{channel:v});await twAccLoad();if(typeof twLoad==="function")twLoad();}catch(x){$("chat_err").textContent=x.message;}}
[["tw_ch_in","tw_ch_save"],["tw_ch2_in","tw_ch2_save"]].forEach(([i,b])=>{$(b).addEventListener("click",()=>twChanSave(i));$(i).addEventListener("keydown",e=>{if(e.key==="Enter"){e.preventDefault();twChanSave(i);}});});$("tw_acc_owner").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",{action:"start",all:true}));}catch(x){$("tw_acc_t").textContent=x.message;}});$("tw_acc_mod").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",twAcc&&twAcc.mod_scope?{action:"mod",on:true}:twAcc&&(twAcc.role==="mod"||twAcc.role==="user")?{action:"start",as:"mod"}:{action:"start",all:true}));}
catch(x){$("chat_err").textContent=x.message;}});$("tw_acc_ev").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",{action:"start",events:true}));}catch(x){$("chat_err").textContent=x.message;}});$("tw_acc_modoff").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",{action:"mod",on:false}));}catch(x){$("chat_err").textContent=x.message;}});$("tw_acc_cancel").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/login",{action:"cancel"}));}catch(x){}});$("tw_acc_out").addEventListener("click",async()=>{try{twAccRender(await srtlaCall("POST","/api/twitch/logout",{}));}catch(x){}});let twbPoll=null,twbData=null;function twWho(){const e=$("tw_acc2");if(e)e.textContent=twAcc&&twAcc.state==="angemeldet"?"Konto: "+twAcc.login:"";}
function twAdvVis(){const main=!!(twAcc&&twAcc.state==="angemeldet"),bot=!!(twbData&&twbData.state==="angemeldet");$("tw_adv_box").hidden=main;$("tw_login_l").hidden=bot;$("tw_token_l").hidden=bot;$("tw_manual_hint").hidden=bot;}
function twbRender(a){if(!a)return;twbData=a;const on=a.state==="angemeldet",wait=a.state==="wartet";$("twb_go").hidden=on||wait;$("twb_cancel").hidden=!wait;$("twb_out").hidden=!on;$("twb_code").hidden=!wait;$("twb_t").textContent=on?"Bot: "+a.login:wait?"Warte auf die Bestätigung bei Twitch …":a.state==="abgelaufen"?"Anmeldung des Bots abgelaufen":a.state==="fehler"?(a.error||"Anmeldung fehlgeschlagen"):"Kein Bot (das Hauptkonto schreibt)";if(wait){$("twb_big").textContent=a.code||"";const l=$("twb_link");l.href=a.uri||"#";l.textContent=(a.uri||"").replace(/^https?:\/\//,"").replace(/\?.*$/,"")||"twitch.tv/activate";}
twWho();twAdvVis();if(typeof twButtons==="function")twButtons();if(wait&&!twbPoll)twbPoll=setInterval(()=>{if(!document.hidden)twbLoad();},2000);if(!wait&&twbPoll){clearInterval(twbPoll);twbPoll=null;if(typeof twLoad==="function")twLoad();}}
async function twbLoad(){try{twbRender(await srtlaCall("GET","/api/twitch/bot"));}catch(e){}}
async function twbDo(action){try{twbRender(await srtlaCall("POST","/api/twitch/bot",{action}));}catch(x){$("tw_err").textContent=x.message;}}
$("twb_go").addEventListener("click",()=>twbDo("start"));$("twb_cancel").addEventListener("click",()=>twbDo("cancel"));$("twb_out").addEventListener("click",()=>twbDo("logout"));twbLoad();twAccLoad();let chatOkTimer=0;$("chat_form").addEventListener("submit",async e=>{e.preventDefault();const i=$("chat_in"),err=$("chat_err"),t=i.value.trim();if(!t)return;$("chat_send").disabled=true;err.textContent="";err.classList.remove("okmsg");try{const r=await srtlaCall("POST","/api/chat/send",{text:t});i.value="";if(t[0]==="/"&&r&&r.message){err.textContent=r.message;err.classList.add("okmsg");clearTimeout(chatOkTimer);chatOkTimer=setTimeout(()=>{if(err.classList.contains("okmsg")){err.textContent="";err.classList.remove("okmsg");}},8000);}}catch(x){err.textContent=x.message;}
$("chat_send").disabled=false;i.focus();});(function(){const box=$("chat_box"),msg=$("chat_msg"),card=$("c_chat");let since=0,busy=false,shown=0,altN=0;const OPT_KEY="pb_chatopt";let opt={ts:true,badge:true,emote:true,line:true,sig:false,size:"m"};try{Object.assign(opt,JSON.parse(localStorage.getItem(OPT_KEY)||"{}")||{});}catch(e){}
function paintOpt(){box.classList.toggle("o-nots",!opt.ts);box.classList.toggle("o-nobadge",!opt.badge);box.classList.toggle("o-noemote",!opt.emote);box.classList.toggle("o-noline",!opt.line);box.classList.remove("o-s","o-l");if(opt.size==="s"||opt.size==="l")box.classList.add("o-"+opt.size);document.querySelectorAll("#chat_optbox [data-opt]").forEach(i=>{if(i.type==="checkbox")i.checked=!!opt[i.dataset.opt];else i.value=opt[i.dataset.opt];});}
$("chat_opt").addEventListener("click",()=>{const b=$("chat_optbox");b.hidden=!b.hidden;$("chat_opt").setAttribute("aria-expanded",String(!b.hidden));});$("chat_optbox").addEventListener("change",e=>{const i=e.target.closest("[data-opt]");if(!i)return;opt[i.dataset.opt]=i.type==="checkbox"?i.checked:i.value;if(i.dataset.opt==="sig"&&i.checked){try{actx=actx||new(window.AudioContext||window.webkitAudioContext)();actx.resume();}catch(x){}}
try{localStorage.setItem(OPT_KEY,JSON.stringify(opt));}catch(x){}paintOpt();});paintOpt();const BADGE_TXT={broadcaster:"\u25CF",moderator:"\u2694\uFE0E",vip:"\u25C6",subscriber:"\u2605",founder:"\u2605",staff:"\u2731",partner:"\u2714\uFE0E"};const BADGE_TIP={broadcaster:"Streamer",moderator:"Moderator",vip:"VIP",subscriber:"Abonnent",founder:"Abonnent (Gründer)",staff:"Twitch-Mitarbeiter",partner:"Partner"};let modOn=false,me="";const MAXLINES=200,EMOTE="https://static-cdn.jtvnw.net/emoticons/v2/%s/default/dark/1.0";const LINK_RE=/(?:https?:\/\/|www\.)[^\s<>"']+|(?:[a-z0-9-]+\.)+(?:com|net|org|de|tv|gg|io|be|me|co|app|ly|to|fm|ch|at|eu|info|live|dev|xyz|uk|us|ru|fr|nl|es|it|pl)(?![\w-])(?:\/[^\s<>"']*)?/gi;function linkTo(raw){let u;try{u=new URL(/^https?:\/\//i.test(raw)?raw:"https://"+raw);}catch(e){return null;}
if(u.protocol!=="https:"&&u.protocol!=="http:")return null;if(u.username||u.password)return null;const a=document.createElement("a");a.className="lk";a.href=u.href;a.target="_blank";for(const v of["noopener","noreferrer","nofollow"])a.relList.add(v);a.title=u.href;let t=u.hostname.replace(/^www\./,"")+(u.pathname==="/"?"":u.pathname)+u.search;if(t.length>40)t=t.slice(0,39)+"…";a.textContent=t;return a;}
function addText(mt,str){let last=0;LINK_RE.lastIndex=0;let x;while((x=LINK_RE.exec(str))){let raw=x[0];const trail=raw.match(/[.,;:!?)\]}'"]+$/);if(trail)raw=raw.slice(0,raw.length-trail[0].length);if(x.index>0&&!/^(?:https?:\/\/|www\.)/i.test(raw)&&/[\w@.\/-]/.test(str[x.index-1]))continue;const a=raw?linkTo(raw):null;if(!a)continue;if(x.index>last)mt.appendChild(document.createTextNode(str.slice(last,x.index)));mt.appendChild(a);last=x.index+raw.length;LINK_RE.lastIndex=last;}
if(last<str.length)mt.appendChild(document.createTextNode(str.slice(last)));}
function fillText(mt,m){const cp=Array.from(m.text||""),sl=(a,b)=>cp.slice(a,b).join("");let pos=0;for(const e of(m.emotes||[])){if(e[1]<pos||e[2]>=cp.length)continue;if(e[1]>pos)addText(mt,sl(pos,e[1]));const im=document.createElement("img");im.className="em";im.alt=sl(e[1],e[2]+1);im.loading="lazy";im.src=/^https:\/\/(cdn\.betterttv\.net|cdn\.7tv\.app|cdn\.frankerfacez\.com)\//.test(e[0])?e[0]:EMOTE.replace("%s",encodeURIComponent(e[0]));mt.appendChild(im);const et=document.createElement("span");et.className="emt";et.textContent=sl(e[1],e[2]+1);mt.appendChild(et);im.>
if(pos<cp.length)addText(mt,sl(pos));}
function line(m){const d=document.createElement("div");d.className="cm "+(m.type||"msg");const tm=document.createElement("span");tm.className="ct";const dt=new Date((m.t||0)*1000);tm.textContent=String(dt.getHours()).padStart(2,"0")+":"+String(dt.getMinutes()).padStart(2,"0");tm.title=dt.toLocaleString();d.appendChild(tm);const body=document.createElement("div");body.className="cb";d.appendChild(body);if(m.mid)d.dataset.mid=m.mid;if(m.uid)d.dataset.uid=m.uid;if(m.login)d.dataset.login=m.login;if((m.badges||[]).includes("broadcaster"))d.dataset.owner="1";for(const bn of(m.badges||[])){const bg=document.createElement("span");bg.className="bdg "+bn;bg.textContent=BADGE_TXT[bn]||bn;bg.title=BADGE_TIP[bn]||bn;body.appendChild(bg);}
if(m.name){const n=document.createElement("span");n.className="cn";n.textContent=m.name;n.translate=false;if(/^#[0-9a-fA-F]{6}$/.test(m.color||""))n.style.setProperty("--nc",m.color);body.appendChild(n);}
const mt=document.createElement("span");mt.translate=false;body.appendChild(mt);fillText(mt,m);d._mt=mt;d._txt=m.text||"";if(m.bits&&!m.ev){const b=document.createElement("span");b.className="bt";b.textContent=m.bits+" Bits";body.appendChild(b);}
if(m.ev){const k=m.ev.k,n=m.ev.n||0;const head={sub:n>0?`Sub · ${n} Monate`:"Sub",gift:n>1?`Geschenk-Abos · ${n}`:"Geschenk-Abo",raid:n>0?`Raid · ${n} Zuschauer`:"Raid",cheer:`Cheer · ${n} Bits`,announce:"Ankündigung",follow:"Follow",points:n>0?`Kanalpunkte · ${n}`:"Kanalpunkte"}[k];if(head){d.classList.add("ev","ev-"+k);const h=document.createElement("span");h.className="evh";h.appendChild(document.createTextNode({sub:"⭐ ",gift:"🎁 ",raid:"🚀 ",cheer:"💎 ",announce:"📢 ",follow:"💜 ",points:"🎯 "}[k]));h.appendChild(document.createTextNode(head));body.insertBefore(h,body.firstChild);}}
if(modOn)addMm(d);return d;}
function addMm(d){if(d.querySelector(".mm")||!d.dataset.mid||!d.dataset.uid||d.dataset.login===me||d.dataset.owner)return;const mm=document.createElement("button");mm.type="button";mm.className="mm";mm.textContent="⋯";mm.title="Moderation";mm.setAttribute("aria-label","Moderation");d.appendChild(mm);}
let actx=null;function evSignal(){if(!opt.sig)return;try{if(navigator.vibrate)navigator.vibrate([120,70,120]);}catch(e){}
try{actx=actx||new(window.AudioContext||window.webkitAudioContext)();if(actx.state==="suspended")actx.resume();[0,.18].forEach((dt,i)=>{const o=actx.createOscillator(),g=actx.createGain();o.type="sine";o.frequency.value=i?1175:880;o.connect(g);g.connect(actx.destination);const t=actx.currentTime+dt;g.gain.setValueAtTime(.0001,t);g.gain.exponentialRampToValueAtTime(.18,t+.02);g.gain.exponentialRampToValueAtTime(.0001,t+.15);o.start(t);o.stop(t+.16);});}catch(e){}}
$("chat_evchip").addEventListener("click",()=>{box.scrollTop=box.scrollHeight;$("chat_evchip").hidden=true;});box.addEventListener("scroll",()=>{if(box.scrollHeight-box.scrollTop-box.clientHeight<60)$("chat_evchip").hidden=true;});function notice(text){const d=document.createElement("div");d.className="cm notice";const t=document.createElement("span");t.className="ct";const n=new Date();t.textContent=String(n.getHours()).padStart(2,"0")+":"+String(n.getMinutes()).padStart(2,"0");const b=document.createElement("div");b.className="cb";b.textContent=text;d.appendChild(t);d.appendChild(b);box.appendChild(d);shown++;}
function metaEvent(m){if(m.type==="emotes"){for(const x of box.querySelectorAll(".cm[data-mid]"))if(x.dataset.mid===m.mid&&x._mt){x._mt.textContent="";fillText(x._mt,{text:x._txt,emotes:m.emotes});}return;}
if(m.type==="del"){for(const x of box.querySelectorAll(".cm[data-mid]"))if(x.dataset.mid===m.mid)x.classList.add("gone");return;}
if(m.type==="clear"){if(!m.uid){notice("Der Chat wurde geleert.");return;}
let who=m.uid;for(const x of box.querySelectorAll(".cm[data-uid]")){if(x.dataset.uid===m.uid){x.classList.add("gone");who=x.dataset.login||who;}}
notice(who+(m.seconds?": Timeout "+(m.seconds>=3600?Math.round(m.seconds/3600)+" Std":(m.seconds>=60?Math.round(m.seconds/60)+" Min":m.seconds+" s")):": gebannt"));}}
let undoTimer=null;function toast(text,undo){$("chat_toast").hidden=false;$("chat_toast_t").textContent=text;const u=$("chat_undo");u.hidden=!undo;u._undo=undo||null;clearTimeout(undoTimer);undoTimer=setTimeout(()=>{$("chat_toast").hidden=true;},8000);}
async function modDo(body,label){try{const r=await srtlaCall("POST","/api/chat/mod",body);toast(label||r.message,(body.action==="timeout"||body.action==="ban")&&r.user_id&&body.seconds!==1?{action:"unban",user_id:r.user_id}:null);}
catch(x){toast(x.message,null);}}
$("chat_undo").addEventListener("click",async()=>{const u=$("chat_undo")._undo;if(!u)return;$("chat_undo").hidden=true;await modDo(u,"Bann aufgehoben");});box.addEventListener("click",e=>{const mm=e.target.closest(".mm");if(!mm)return;const row=mm.parentNode,old=row.querySelector(".mrow");if(old){old.remove();return;}
const mr=document.createElement("div");mr.className="mrow";const uid=row.dataset.uid,mid=row.dataset.mid,who=row.dataset.login||"";for(const[txt,body,lab]of[["Löschen",{action:"delete",message_id:mid},"Gelöscht"],["Alle löschen",{action:"timeout",user_id:uid,seconds:1},`Alle Nachrichten von ${who} gelöscht`],["Timeout 10 Min",{action:"timeout",user_id:uid,seconds:600},"Timeout 10 Min: "+who],["Timeout 1 Std",{action:"timeout",user_id:uid,seconds:3600},"Timeout 1 Std: "+who],["Bannen",{action:"ban",user_id:uid},"Gebannt: "+who]]){const b=document.createElement("button");b.type="button";b.className="sec";b.textContent=txt;b.addEventListener("click",()=>{mr.remove();modDo(body,lab);});mr.appendChild(b);}
row.appendChild(mr);});let stick=true,chan="",lastOk=0;box.addEventListener("scroll",()=>{if(box.clientHeight>0)stick=box.scrollHeight-box.scrollTop-box.clientHeight<60;});if(typeof ResizeObserver!=="undefined")new ResizeObserver(()=>{if(box.clientHeight>0&&stick)box.scrollTop=box.scrollHeight;}).observe(box);function pollFetch(url){const ac=typeof AbortController!=="undefined"?new AbortController():null,to=setTimeout(()=>{if(ac)ac.abort();},8000);return fetch(url,Object.assign({headers:{"x-pb-owner":pbOwnerKey()}},ac?{signal:ac.signal}:{})).finally(()=>clearTimeout(to));}
async function poll(){if(busy||!card.open||card.hasAttribute("data-pbhide")||document.hidden)return;busy=true;try{const since0=since;const r=await pollFetch("/api/chat?since="+since);if(r.status===401){if(!pbUpdating)location.reload();return;}
const d=await r.json();if(!r.ok||!d||!Array.isArray(d.items)||!Number.isFinite(d.last))throw new Error("bad");lastOk=Date.now();try{if(d.account)twAccRender(d.account);}catch(e){}
const changed=!!(d.channel&&chan&&d.channel!==chan);if(d.last<since0||changed){box.textContent="";shown=0;stick=true;$("chat_evchip").hidden=true;}
chan=d.channel||chan;const near=box.clientHeight>0?box.scrollHeight-box.scrollTop-box.clientHeight<60:stick;const wasMod=modOn;modOn=!!(d.account&&d.account.mod);me=(d.account&&d.account.login)||"";if(modOn&&!wasMod)box.querySelectorAll(".cm").forEach(addMm);if(!modOn&&wasMod)box.querySelectorAll(".mm,.mrow").forEach(e=>e.remove());const first=since0===0;let evNew=false;for(const m of d.items){try{if(m.meta){metaEvent(m);continue;}
const row=line(m);if(altN++&1)row.classList.add("alt");box.appendChild(row);shown++;if(m.ev&&!first&&Date.now()/1000-(m.t||0)<120){row.classList.add("fresh");evNew=true;}}catch(e){}}
since=d.last;const keepTop=!near?box.scrollHeight-box.scrollTop:0;while(shown>MAXLINES){box.removeChild(box.firstChild);shown--;}
if(near){if(!box.querySelector(".mrow"))box.scrollTop=box.scrollHeight;}
else if(keepTop)box.scrollTop=box.scrollHeight-keepTop;if(evNew){if(!near)$("chat_evchip").hidden=false;evSignal();}
const text={"kein-kanal":"Kein Kanal in der Twitch-Karte eingetragen.","verbinde":"Verbinde …","fehler":"Keine Verbindung zu Twitch."}[d.state]||"";msg.textContent=(d.state==="ok"&&!shown)?"Noch keine Nachrichten.":text;msg.hidden=!msg.textContent;msg.classList.toggle("quiet",d.state==="ok"&&!shown);card.classList.toggle("chatok",d.state==="ok");}catch(e){if(Date.now()-lastOk>10000){msg.textContent="Keine Verbindung zur Box.";msg.classList.remove("quiet");card.classList.remove("chatok");msg.hidden=false;}}
finally{busy=false;}}
card.addEventListener("toggle",()=>{if(card.open)poll();});setInterval(poll,2000);poll();})();let pbOwnerMem="";function pbOwnerKey(){try{const k=localStorage.getItem("pb_owner_key");if(k&&/^[A-Za-z0-9_-]{32,128}$/.test(k))return k;}catch(e){}
if(pbOwnerMem)return pbOwnerMem;const b=new Uint8Array(32);crypto.getRandomValues(b);let t="";b.forEach(x=>t+=String.fromCharCode(x));const k=btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");pbOwnerMem=k;try{localStorage.setItem("pb_owner_key",k);}catch(e){}
return k;}
(function(){const c=$("c_chat");let t=0;const show=()=>{c.classList.add("showctl");clearTimeout(t);t=setTimeout(()=>c.classList.remove("showctl"),10000);};c.addEventListener("pointermove",e=>{if(e.pointerType==="mouse")show();});c.addEventListener("click",show);})();