let username, reply_id = 0, highest_id, currentChannel = sessionStorage.getItem('lastchannel') || 'everyone' const wss_server = 'wss://ipost.rocks', wss_port = '443', wss_URI = wss_server + ':' + wss_port, decURIComp = decodeURIComponent function createElement(s) { return document.createElement(s) } function getById(i) { return document.getElementById(i) } let socket = new WebSocket(wss_URI) socket.addEventListener('message', async function (event) { console.log('new websocket message arrived') if (wss_server === event.origin) { let data = event.data let ds = JSON.parse(data) let message = ds.message let item = ds.data let username = decURIComp(item.post_user_name) if ( message === 'new_post' && decURIComp(item.post_receiver_name) === currentChannel ) { await createPost( username, decURIComp(item.post_text), item.post_time, item.post_special_text, item.post_id || highest_id + 1, item.post_from_bot, item.post_reply_id, true, item.user_avatar, item.files[0], item.files[1], item.files[2], item.files[3], item.files[4] ) console.log('created new post') if (user['username'] !== username) mainNoti(username) let highest_known_posts = await ( await fetch( `/api/getPostsLowerThan?id=${highest_id + 28}&channel=${currentChannel}` ) ).json() for (let i = 0; i < highest_known_posts.length; i++) { if (getById(highest_known_posts[i].post_id) === undefined) { main() return } } highest_id++ } else { console.warn('unknown message') } } else { console.warn('unknown ws origin') } }) socket.addEventListener('open', () => { switchChannel(currentChannel) }) var cd = true //inversed "cooldown" let last_called_postMsg = Date.now() /* Tell the api to create a new post with the given information previously called "postMessage" */ function postMsg() { if (Date.now() - last_called_postMsg < 100) { createModal('slow down there') debugger return } last_called_postMsg = Date.now() let msg = getById('post-text').value let len = msg.length if (len === 0) { alert('you have to enter a message!') return } if (len > 1000) { alert(`Your message cant contain more than 1000 characters! (${len})`) return } if (encodeURIComponent(msg).length > 3000) { alert('Your message is too long! (Too many special characters)') return } if (cd && posting_id !== undefined) { cd = false setTimeout(function () { cd = true }, 400) let formdata = new FormData() formdata.append('message', msg) formdata.append('reply_id', reply_id) formdata.append('receiver', currentChannel) formdata.append('pid', posting_id) for (let i in files) { formdata.append('file_' + i, files[i]) } files = [] getById('filesDiv').innerHTML = '' fetch('/api/post', { method: 'POST', body: formdata, }) posting_id = undefined update_pid() getById('post-text').value = '' unreply() } else { alert('Please wait a tiny bit before posting again') } } async function update_pid() { let r = await (await fetch('/api/pid')).json() console.log('new pid info: ', r) if (r.error) { //an error occurred if (r.error === 'you cannot access the api without being logged in') { //account error, go to login page location.replace('/') return } throw new Error(r.error) } posting_id = r.pid console.log('Updated pid', posting_id) } function spacerTextNode() { return document.createTextNode(' | ') } async function reply_link_clicked(reply_channel, reply_id) { console.log('clicked link') if (reply_channel !== currentChannel) { console.log('reply is in another channel') switchChannel(reply_channel) console.log('switched channel') await main() console.log('loaded new messages') let replied_msg = getById(reply_id) if (replied_msg) { console.log('found element') replied_msg.scrollIntoView() } } else { let replied_msg = getById(reply_id) if (replied_msg) { console.log('found element') replied_msg.scrollIntoView() } } } const image_types = { png: true, jpg: true, jpeg: true, webp: true, jfif: true, } function iconLink(name) { if (!name) { //if(typeof name === 'undefined' || typeof name === "null"){ return undefined } console.log( name, name.lastIndexOf('\.'), name.substring(name.lastIndexOf('\.') + 1) ) let extension = name.substring(name.lastIndexOf('\.') + 1) if (extension in image_types) { return '/user_uploads/previews/' + name } return '/api/getFileIcon/' + extension } async function createPost( username, text, time, specialtext, postid, isbot, reply_id, add_on_top, avatar_src, file0, file1, file2, file3, file4 ) { if (!specialtext) specialtext = '' const newDiv = createElement('div') const newP = createElement('p') const newA = createElement('a') const newSpan2 = createElement('span') const newSpan3 = createElement('span') const avatar = createElement('img') const boticon = createElement('img') const replyDiv = createElement('div') const replyA = createElement('a') const replyAvatar = createElement('img') const replySpan = createElement('span') const replyBr = createElement('br') boticon.src = '/images/bot.png' boticon.height = 25 boticon.width = 25 boticon.classList.add('boticon') const newUsername = document.createTextNode(username) const newTime = document.createTextNode(new Date(time).toLocaleTimeString()) const newSpecialText = document.createTextNode(specialtext) newDiv.classList.add('post') newSpan3.classList.add('specialtext') avatar.width = 25 avatar.height = 25 avatar.alt = 'user avatar' avatar.classList.add('avatar') if (avatar_src) avatar.src = '/avatars/' + avatar_src else { avatar.src = '/images/default_avatar.png' } newA.appendChild(avatar) newA.appendChild(newUsername) newA.href = `/users/${username}` newSpan2.appendChild(newTime) newSpan3.appendChild(newSpecialText) newP.appendChild(newA) newP.appendChild(spacerTextNode()) newP.appendChild(newSpan2) if (specialtext !== '') newP.appendChild(spacerTextNode()) newP.appendChild(newSpan3) if (isbot === 1) { newP.appendChild(spacerTextNode()) newP.appendChild(boticon) } newP.appendChild(spacerTextNode()) // |\>.Reply to this Post` if (reply_id !== 0) { try { const reply_obj = await ( await fetch(`/api/getPost?id=${reply_id}`) ).json() const reply_username = decURIComp(reply_obj.post_user_name) const reply_username_text = document.createTextNode(reply_username) const reply_text = decURIComp(reply_obj.post_text) const reply_channel = reply_obj.post_receiver_name replyAvatar.width = 10 replyAvatar.height = 10 replyAvatar.classList.add('avatar') if (reply_obj.User_Avatar) replyAvatar.src = '/avatars/' + reply_obj.User_Avatar else { replyAvatar.src = '/images/default_avatar.png' } replyA.appendChild(replyAvatar) replyA.appendChild(reply_username_text) replyA.appendChild(spacerTextNode()) replyA.innerHTML += filterReply( reply_text.replace('\n', ' ').substring(0, 20) ) replyA.appendChild(replyBr) replyA.classList.add('no-link-style') replyDiv.appendChild(replyA) newDiv.appendChild(replyDiv) replyDiv.outerHTML = replyDiv.outerHTML.replace( /\>/im, ` onclick="reply_link_clicked('${reply_channel}',${reply_id})" \>` ) } catch (ignored) { console.log(ignored) } } newDiv.appendChild(newP) newDiv.innerHTML += filterPost(text) newDiv.id = postid /* FILES */ const filesP = createElement('p') const file0_img = createElement('img') const file1_img = createElement('img') const file2_img = createElement('img') const file3_img = createElement('img') const file4_img = createElement('img') file0_img.src = iconLink(file0) file1_img.src = iconLink(file1) file2_img.src = iconLink(file2) file3_img.src = iconLink(file3) file4_img.src = iconLink(file4) file0_img.width = 50 file1_img.width = 50 file2_img.width = 50 file3_img.width = 50 file4_img.width = 50 file0_img.alt = 'user uploaded file' file1_img.alt = 'user uploaded file' file2_img.alt = 'user uploaded file' file3_img.alt = 'user uploaded file' file4_img.alt = 'user uploaded file' if (file0) { filesP.appendChild(file0_img) file0_img.onclick = function (event) { console.warn('TODO: Create Modal') } } if (file1) { filesP.appendChild(file1_img) file1_img.onclick = function (event) { console.warn('TODO: Create Modal') } } if (file2) { filesP.appendChild(file2_img) file2_img.onclick = function (event) { console.warn('TODO: Create Modal') } } if (file3) { filesP.appendChild(file3_img) file3_img.onclick = function (event) { console.warn('TODO: Create Modal') } } if (file4) { filesP.appendChild(file4_img) file4_img.onclick = function (event) { console.warn('TODO: Create Modal') } } newDiv.appendChild(filesP) /* Adding the post to the posts list */ let posts_div = getById('posts') if (add_on_top) { posts_div.insertBefore(newDiv, posts_div.children[0]) } else { posts_div.appendChild(newDiv) } } async function main() { if (!user) { user = await (await fetch('/api/getuser')).json() username = user.username if (!username) { user = undefined getById('noaccount').style = '' getById('loading').style = 'display:none;' console.log('no account') return } } username = user.username getById('username-self').innerText = username let all_posts = await ( await fetch(`/api/getPosts?channel=${currentChannel}`) ).json() if (!all_posts) return getById('posts').innerHTML = '' getById('loading').style = 'display:none;' getById('scriptonly').style = '' highest_id = all_posts[0].post_id let post_promises = [] for (i in all_posts) { let item = all_posts[i] let created = createPost( decURIComp(item.post_user_name), decURIComp(item.post_text), item.post_time, item.post_special_text, item.post_id, item.post_from_bot, item.post_reply_id, false, item.User_Avatar, item.file_0, item.file_1, item.file_2, item.file_3, item.file_4 ) post_promises.push(created) } await Promise.all(post_promises) Array.from(getById('posts').childNodes) .sort((a, b) => { if (Number(a.id) > Number(b.id)) return -1 if (Number(a.id) < Number(b.id)) return 1 return 0 }) .forEach((e) => { getById('posts').appendChild(e) }) let links = document.getElementsByClassName('insertedlink') for (let i = 0; i < links.length; i++) { links[i].innerText = links[i].innerText.split('\/\/')[1].split('\/')[0] } let mentions = document.getElementsByClassName('mention') for (let i = 0; i < mentions.length; i++) { if ( mentions[i] !== undefined && mentions[i].innerText === '@' + username ) { mentions[i].classList.add('user-mention') mentions[i].classList.remove('mention') i-- } } } async function reply(postid) { let post = await (await fetch('/api/getPost?id=' + postid)).json() let username = post.post_user_name let posttext = post.post_text getById('reply').style = '' getById('reply_username').innerText = decURIComp(username) getById('reply_text').innerHTML = filterPost(decURIComp(posttext)) reply_id = postid } function unreply() { getById('reply').style = 'display:none;' reply_id = 0 } var cansendNoti = false function askNotiPerms() { return Notification.requestPermission() } async function firstAsk() { if ( Notification.permission === 'denied' || Notification.permission === 'default' ) { await askNotiPerms() } } async function mainNoti(user) { if ( Notification.permission === 'denied' || Notification.permission === 'default' ) { await askNotiPerms() } else { if (cansendNoti) { let notification = new Notification('IPost', { body: 'new message posted from ' + user, tag: 'new_post', }) notification = await notification notification.addEventListener('click', function () { notification.close() }) console.log(notification) } } } document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'visible') { cansendNoti = false } else { cansendNoti = true } }) if (window.location.href.includes('message=')) { getById('post-text').innerText = `${decURIComp(window.location.href.split('message=')[1])} ` } function switchChannel(channelname) { sessionStorage.setItem('lastchannel', channelname) currentChannel = channelname socket.send(JSON.stringify({ id: 'switchChannel', data: channelname })) } function loadChannels() { // let tab = getById('channelTab') tab.innerHTML = '' for (let i = 0; i < channels.length; i++) { let channelname = decURIComp(channels[i]) if (channelname === '') continue let channelp = createElement('p') channelp.classList.add('channel') let textnode = document.createTextNode(channelname) channelp.appendChild(textnode) channelp.addEventListener('click', async function () { switchChannel(channelname) main() let settings = await (await fetch('/api/settings')).json() // skipqc console.log(settings) // skipqc if (settings !== 'null') { if (settings.ACCR === false) { unreply() } } }) tab.appendChild(channelp) } } var files = [] function addFile(file) { if (file.size > 1_000_000) { alert('that file is too large, max size: 1MiB') console.log('file is too big: ', file.name, file.type, file.size) return } if (files.length >= 5) { console.log('too many files already: ', files) return } files[files.length] = file const fileimg = createElement('img') console.log( file.name, file.name.lastIndexOf('\.'), file.name.substring(file.name.lastIndexOf('\.') + 1) ) fileimg.src = '/api/getFileIcon/' + file.name.substring(file.name.lastIndexOf('\.') + 1) getById('filesDiv').appendChild(fileimg) //filesDiv console.log('File added: ', file.name, file.type, file.size) } function dropHandler(ev) { console.log('file dropped') ev.preventDefault() if (ev.dataTransfer.items) { // Use DataTransferItemList interface to access the file(s) ;[...ev.dataTransfer.items].forEach((item, i) => { // If dropped items aren't files, reject them if (item.kind === 'file') { const file = item.getAsFile() addFile(file) } }) } else { // Use DataTransfer interface to access the file(s) ;[...ev.dataTransfer.files].forEach((file, i) => { addFile(file) }) } } function init() { setInterval(update_pid, 30000) if (posting_id === '') update_pid() main() firstAsk() loadChannels() } init()