Refactor: Reset Turnstile CAPTCHA on all comment attempts

This commit is contained in:
2025-10-03 14:10:51 -07:00
parent effc5efdb9
commit 472f1efe36

View File

@@ -103,7 +103,7 @@
</main>
</div>
<script>
f=()=>({view:'list',user:null,posts:[],post:null,comments:[],txt:'',reply:null,md:null,commenting:false,commentSuccess:false,profile:{user:null,comments:[],posts:[]},loading:false,init(){this.md=window.markdownit({highlight:(s,l)=>{if(l&&hljs.getLanguage(l))try{return hljs.highlight(s,{language:l,ignoreIllegals:!0}).value}catch(e){}return''}});fetch('https://speech.capital/api/user',{credentials:'include'}).then(r=>r.json()).then(d=>{if(d.user){this.user=d.user;const s=document.getElementById('signup-link'),a=document.getElementById('auth-links'),u=document.getElementById('username-link');s.style.display='none';u.textContent=d.user.username;u.href=`https://speech.capital/${d.user.username}`;a.style.display='flex'}});const host=window.location.hostname,parts=host.split('.'),path=location.pathname;if(parts.length<=2){const p=path.replace(/\/$/,''),uMatch=p.match(/^\/([a-zA-Z0-9_-]+)$/);if(uMatch&&!['submit','login','signup','admin'].includes(uMatch[1])){this.view='user';this.loadUser(uMatch[1])}else{this.view='home'}}else{document.getElementById('subdomain').textContent=parts[0]+'.';const pMatch=path.match(/^\/(\d+)$/);if(pMatch){this.view='post';this.loadPost(pMatch[1])}else{this.view='list';this.loadList()}}},isMod(){return this.user&&['mod','admin','owner'].includes(this.user.role)},isImageLink(l){return l&&/\.(jpg|jpeg|png|gif|webp)$/i.test(l)},async moderate(type,item){if(!confirm(`Are you sure you want to delete this ${type}?`))return;const isPost=type==='post',body={action:isPost?'delete_post':'delete_comment',[isPost?'post_id':'comment_id']:item.id};try{const r=await fetch('https://speech.capital/api/moderate',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok)throw new Error('Failed to delete');if(isPost){item.title='[Deleted]';item.content='[Deleted]';item.link=null}else{item.content='[Deleted]'}}catch(e){alert(e.message)}},async banUser(user_id,username){const days=prompt(`How many days to ban ${username}?`);if(!days||isNaN(parseInt(days)))return;const body={action:'ban_user',user_id,days:parseInt(days)};try{const r=await fetch('https://speech.capital/api/moderate',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok){const d=await r.json();throw new Error(d.error||'Failed to ban user')}alert(`${username} banned for ${days} days.`)}catch(e){alert(e.message)}},async loadList(){const parts=window.location.hostname.split('.'),sub=parts.length>2?parts[0]:'free',sort=new URLSearchParams(location.search).get('sort')||'hot';const r=await fetch(`https://speech.capital/api/posts?sub=${sub}&sort=${sort}`,{credentials:'include'});this.posts=((await r.json()).posts||[]).map(p=>({...p,showImage:!1}))},async loadPost(id){const r=await fetch(`https://speech.capital/api/posts/${id}`,{credentials:'include'});const d=await r.json();this.post=d.post;this.comments=d.comments||[]},async loadUser(u){this.loading=!0;try{const r=await fetch(`/api/users/${u}`);if(!r.ok)throw'';this.profile=await r.json()}catch(e){this.profile={user:null,comments:[],posts:[]}}finally{this.loading=!1}},formatDate(d){return d?new Date(d.replace(' ','T')+'Z').toLocaleDateString():''},renderMarkdown(t){return this.md.render(t||'')},async vote(obj,dir,isComment=false){try{const body=isComment?{comment_id:obj.id,direction:dir}:{post_id:obj.id,direction:dir};const r=await fetch('https://speech.capital/api/vote',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok)throw new Error();const{score,voted}=await r.json();obj.score=score;obj.voted=voted}catch(e){}},async comment(parent_id,ev){if(!this.txt.trim()||this.commenting)return;this.commenting=!0;const form=ev.target,token=form.querySelector('[name="cf-turnstile-response"]')?.value;if(!token){alert('Please complete the CAPTCHA.');this.commenting=!1;return}try{const r=await fetch('https://speech.capital/api/comments',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({post_id:this.post.id,parent_id,content:this.txt,'cf-turnstile-response':token})});if(!r.ok){const d=await r.json();throw new Error(d.error?.message||'Failed to comment')}this.txt='';this.reply=null;this.commentSuccess=!0;setTimeout(()=>this.commentSuccess=!1,3000);this.loadPost(this.post.id)}catch(e){alert(e.message);turnstile.reset(form.querySelector('.cf-turnstile'))}finally{this.commenting=!1}},domain(p){if(p.link){try{return new URL(p.link).hostname}catch{}}return p.link?'link':'self'},ago(d){if(!d)return'';const s=Math.floor((new Date()-new Date(d.replace(' ','T')+'Z'))/1000);if(s<60)return s+'s ago';if(s<3600)return Math.floor(s/60)+'m ago';if(s<86400)return Math.floor(s/3600)+'h ago';return Math.floor(s/86400)+'d ago'}})
f=()=>({view:'list',user:null,posts:[],post:null,comments:[],txt:'',reply:null,md:null,commenting:false,commentSuccess:false,profile:{user:null,comments:[],posts:[]},loading:false,init(){this.md=window.markdownit({highlight:(s,l)=>{if(l&&hljs.getLanguage(l))try{return hljs.highlight(s,{language:l,ignoreIllegals:!0}).value}catch(e){}return''}});fetch('https://speech.capital/api/user',{credentials:'include'}).then(r=>r.json()).then(d=>{if(d.user){this.user=d.user;const s=document.getElementById('signup-link'),a=document.getElementById('auth-links'),u=document.getElementById('username-link');s.style.display='none';u.textContent=d.user.username;u.href=`https://speech.capital/${d.user.username}`;a.style.display='flex'}});const host=window.location.hostname,parts=host.split('.'),path=location.pathname;if(parts.length<=2){const p=path.replace(/\/$/,''),uMatch=p.match(/^\/([a-zA-Z0-9_-]+)$/);if(uMatch&&!['submit','login','signup','admin'].includes(uMatch[1])){this.view='user';this.loadUser(uMatch[1])}else{this.view='home'}}else{document.getElementById('subdomain').textContent=parts[0]+'.';const pMatch=path.match(/^\/(\d+)$/);if(pMatch){this.view='post';this.loadPost(pMatch[1])}else{this.view='list';this.loadList()}}},isMod(){return this.user&&['mod','admin','owner'].includes(this.user.role)},isImageLink(l){return l&&/\.(jpg|jpeg|png|gif|webp)$/i.test(l)},async moderate(type,item){if(!confirm(`Are you sure you want to delete this ${type}?`))return;const isPost=type==='post',body={action:isPost?'delete_post':'delete_comment',[isPost?'post_id':'comment_id']:item.id};try{const r=await fetch('https://speech.capital/api/moderate',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok)throw new Error('Failed to delete');if(isPost){item.title='[Deleted]';item.content='[Deleted]';item.link=null}else{item.content='[Deleted]'}}catch(e){alert(e.message)}},async banUser(user_id,username){const days=prompt(`How many days to ban ${username}?`);if(!days||isNaN(parseInt(days)))return;const body={action:'ban_user',user_id,days:parseInt(days)};try{const r=await fetch('https://speech.capital/api/moderate',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok){const d=await r.json();throw new Error(d.error||'Failed to ban user')}alert(`${username} banned for ${days} days.`)}catch(e){alert(e.message)}},async loadList(){const parts=window.location.hostname.split('.'),sub=parts.length>2?parts[0]:'free',sort=new URLSearchParams(location.search).get('sort')||'hot';const r=await fetch(`https://speech.capital/api/posts?sub=${sub}&sort=${sort}`,{credentials:'include'});this.posts=((await r.json()).posts||[]).map(p=>({...p,showImage:!1}))},async loadPost(id){const r=await fetch(`https://speech.capital/api/posts/${id}`,{credentials:'include'});const d=await r.json();this.post=d.post;this.comments=d.comments||[]},async loadUser(u){this.loading=!0;try{const r=await fetch(`/api/users/${u}`);if(!r.ok)throw'';this.profile=await r.json()}catch(e){this.profile={user:null,comments:[],posts:[]}}finally{this.loading=!1}},formatDate(d){return d?new Date(d.replace(' ','T')+'Z').toLocaleDateString():''},renderMarkdown(t){return this.md.render(t||'')},async vote(obj,dir,isComment=false){try{const body=isComment?{comment_id:obj.id,direction:dir}:{post_id:obj.id,direction:dir};const r=await fetch('https://speech.capital/api/vote',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok)throw new Error();const{score,voted}=await r.json();obj.score=score;obj.voted=voted}catch(e){}},async comment(parent_id,ev){if(!this.txt.trim()||this.commenting)return;this.commenting=!0;const form=ev.target,token=form.querySelector('[name="cf-turnstile-response"]')?.value;if(!token){alert('Please complete the CAPTCHA.');this.commenting=!1;return}try{const r=await fetch('https://speech.capital/api/comments',{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify({post_id:this.post.id,parent_id,content:this.txt,'cf-turnstile-response':token})});if(!r.ok){const d=await r.json();throw new Error(d.error?.message||'Failed to comment')}this.txt='';this.reply=null;this.commentSuccess=!0;setTimeout(()=>this.commentSuccess=!1,3000);this.loadPost(this.post.id)}catch(e){alert(e.message)}finally{this.commenting=!1;turnstile.reset(form.querySelector('.cf-turnstile'))}},domain(p){if(p.link){try{return new URL(p.link).hostname}catch{}}return p.link?'link':'self'},ago(d){if(!d)return'';const s=Math.floor((new Date()-new Date(d.replace(' ','T')+'Z'))/1000);if(s<60)return s+'s ago';if(s<3600)return Math.floor(s/60)+'m ago';if(s<86400)return Math.floor(s/3600)+'h ago';return Math.floor(s/86400)+'d ago'}})
</script>
</body>
</html>