@ -2,15 +2,100 @@
< html > <!-- 006a6c -->
< head >
< script >
window.version = "2.0-dev.m2.2023-04-26";
//window.version = "2.3";
window.version = "2.4-beta.2023-05-01";
window.betaVersion = true;
window.changeLog = `
Thanks for using Localchat. You are currently running Localchat version %1, below are all the changes in that version compared to the previous version.
Localchat Client 2.4 Beta:
- Added experimental localization support
- Added experimental Linux support
Here is what changed in the previous versions:
* Localchat Client 2.3:
- Added the /channel command
- The version number now appears on the OOBE
- Beta versions now have a different OOBE
- A badge is now shown when you are running a beta version
- Fixed trying to send a folder showing an error
- Fixed the /color output being non-conforming in some cases
* Localchat Client 2.2:
- Added a beta channel option, to receive experimental updates
* Localchat Client 2.1:
- Added the changelog, with a /changelog command
- Added an emoji picker
- Added drag-and-drop support for file uploads
- Audio players now show in black
- Redesigned file uploads
- Fixed the administrator badge not appearing for file uploads
- Fixed some links being broken
`.replace(/^[ \t]+/mg, replaceLeadingSpaces);
function replaceLeadingSpaces() {
let leadingSpaces = arguments[0].length;
let str = '';
while (leadingSpaces > 0) {
str += ' ';
leadingSpaces--;
}
return str;
}
window.addEventListener("load", () => {
if (window.betaVersion) {
document.getElementById("oobe").classList.add("oobe-beta");
document.getElementById("oobe-disclaimer-beta").style.display = "";
document.getElementById("beta-badge").style.display = "";
Array.from(document.getElementsByClassName("oobe-beta-icon")).forEach((el) => {
el.src = el.src.split(".svg")[0] + "-beta.svg" + el.src.split(".svg")[1];
});
}
})
< / script >
< meta charset = "UTF-8" >
< title > Localchat< / title >
< script src = "../shared/crypt.js" > < / script >
< script src = "./i18n.js" > < / script >
< script src = "commands.js" > < / script >
< script type = "text/javascript" src = "lib/purify.min.js" > < / script >
< script src = "lib/marked.min.js" > < / script >
< script type = "module" >
import './node_modules/emoji-picker-element/index.js';
< / script >
< style >
audio {
filter: invert(1) hue-rotate(180deg);
width: 100%;
}
.message-file .message-text {
background-color: #3f4949;
color: #bec8c8;
padding: 10px;
border-radius: 10px;
}
emoji-picker {
position: fixed;
bottom: 48px;
left: 24px;
color-scheme: dark;
--background: #191c1c;
--border-color: rgba(255, 255, 255, .1);
--indicator-color: #b0cccc;
--input-border-color: #3f4949;
--input-font-color: #e0e3e2;
--input-placeholder-color: rgba(225, 227, 226, 0.5);
--outline-color: transparent;
--category-font-color: #e0e3e2;
--button-active-background: #bec8c8;
--button-hover-background: #3f4949;
}
body, html {
background-color: #191c1c;
color: #e0e3e2;
@ -53,7 +138,7 @@
z-index: 999;
display: grid;
color: #e0e3e2;
grid-template-columns: 48px 1fr;
grid-template-columns: max-content 1fr;
}
#input-text {
@ -236,6 +321,10 @@
z-index: 999999;
}
#oobe.oobe-beta {
animation-name: none;
}
@keyframes hero {
0% {
background-position: 0 50%;
@ -337,6 +426,13 @@
background-position: right;
}
.oobe-beta .oobe-hero {
background-image: url("./hero-beta.png");
background-color: transparent !important;
background-size: cover;
background-position: right;
}
.oobe-dropdown, .oobe-action {
width: calc(100% + 40px);
-webkit-appearance: none;
@ -394,19 +490,6 @@
align-items: end;
}
#input-text-placeholder {
font-family: inherit;
font-size: 14px;
opacity: .5;
position: fixed;
pointer-events: none;
width: 100%;
height: 48px;
left: 47px;
display: flex;
align-items: center;
}
p {
margin-top: 0;
}
@ -414,6 +497,52 @@
.user-message, .system-message {
display: flex;
}
.input-icon {
width: 24px;
height: 32px;
padding: 0 4px;
margin: 8px 0;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
}
#oobe-version {
text-align: right;
position: fixed;
top: 8px;
right: 8px;
opacity: .75;
font-size: 14px;
}
.oobe-beta .oobe-button-back, .oobe-beta .oobe-dropdown, .oobe-beta .oobe-action {
color: #ede0de;
}
.oobe-beta .oobe-button-next {
background-color: #e7bdb7;
color: #442926;
}
.oobe-beta .oobe-button-back:hover {
background-color: #534341;
}
.oobe-beta .oobe-page, .oobe-beta .oobe-dropdown:active, .oobe-beta .oobe-action:active {
background-color: #201a19;
}
.oobe-beta {
color: #ede0de;
}
.oobe-beta .oobe-textbox {
background-color: #534341 !important;
color: #d8c2be !important;
}
< / style >
< script >
function oobePage(page) {
@ -442,6 +571,31 @@
localStorage.setItem("oobe-" + version, "1");
location.reload();
}
function pickEmoji() {
document.getElementById("emoji-picker").style.display = "block";
document.getElementById("emoji-unpicker").style.display = "block";
document.getElementById("emoji-picker").focus();
document.getElementById("emoji-picker").shadowRoot.getElementById("search").focus()
}
function unpickEmoji() {
document.getElementById("emoji-picker").style.display = "none";
document.getElementById("emoji-unpicker").style.display = "none";
}
window.addEventListener('load', () => {
document.getElementById("emoji-picker").addEventListener('emoji-click', (event) => {
if (document.getElementById("input-text").contentEditable & & !document.getElementById("input-text").disabled) {
document.getElementById("input-text").insertAdjacentText("beforeend", event.detail.unicode);
unpickEmoji();
document.getElementById("input-text").focus();
}
});
document.getElementById("emoji-picker").shadowRoot.querySelector(".picker").style.borderTopLeftRadius = "10px";
document.getElementById("emoji-picker").shadowRoot.querySelector(".picker").style.borderTopRightRadius = "10px";
});
< / script >
< / head >
< body >
@ -450,190 +604,301 @@
< div > < / div >
< div class = "oobe-main" >
< h1 > Welcome to your local messaging app< / h1 >
< h1 data-i18n-text = "oobe/title" > < / h1 >
< div style = "margin-top: 50px;" >
< select class = "oobe-dropdown oobe-item-first" id = "oobe-dropdown-language" >
< option value = "en-US" > English (United States)< / option >
< / select >
< img src = "icons/language.svg" class = "oobe-dropdown-icon" >
< div id = "oobe-i18n-beta" >
< select class = "oobe-dropdown oobe-item-first" id = "oobe-dropdown-language" onchange = "changeLanguageOOBE();" >
< option value = "en-US" > English (United States)< / option >
< option value = "fr-FR" > français (France) [BETA]< / option >
< / select >
< img src = "icons/language.svg" class = "oobe-dropdown-icon oobe-beta-icon" >
< a class = "oobe-action" onclick = "require('electron').ipcRenderer.send('devmode');" data-i18n-text = "oobe/developer" > < / a >
< img src = "icons/developer.svg" class = "oobe-dropdown-icon oobe-beta-icon" >
< / div >
< div id = "oobe-i18n-stable" >
< a class = "oobe-action oobe-item-first" onclick = "require('electron').ipcRenderer.send('devmode');" data-i18n-text = "oobe/developer" > < / a >
< img src = "icons/developer.svg" class = "oobe-dropdown-icon oobe-beta-icon" >
< / div >
< a class = "oobe-action" id = "oobe-action-beta" onclick = "beta();" data-i18n-text = "oobe/beta" > < / a >
< a class = "oobe-action" id = "oobe-action-stable" style = "display: none;" onclick = "beta();" data-i18n-text = "oobe/stable" > < / a >
< img src = "icons/beta.svg" class = "oobe-dropdown-icon oobe-beta-icon" >
< script >
if (window.betaVersion) {
document.getElementById("oobe-i18n-beta").style.display = "";
document.getElementById("oobe-i18n-stable").style.display = "none";
} else {
document.getElementById("oobe-i18n-beta").style.display = "none";
document.getElementById("oobe-i18n-stable").style.display = "";
}
window.applicationData = localStorage.getItem("application-data");
require('electron').ipcRenderer.on('path', (_, data) => {
window.applicationData = data;
localStorage.setItem("application-data", window.applicationData);
if (fs.existsSync(window.applicationData + "/LocalchatBetaChannel")) {
document.getElementById("oobe-action-beta").style.display = "none";
document.getElementById("oobe-action-stable").style.display = "";
} else {
document.getElementById("oobe-action-beta").style.display = "";
document.getElementById("oobe-action-stable").style.display = "none";
}
< a class = "oobe-action" id = "oobe-action-devmode" onclick = "require('electron').ipcRenderer.send('devmode');" > Enable developer options< / a >
< img src = "icons/developer.svg" class = "oobe-dropdown-icon" >
if (window.betaVersion) {
document.getElementById("oobe-version").innerText = "v" + window.version + "\n" + os.userInfo().username + "@" + os.hostname() + "\n" + window.applicationData;
}
})
if (fs.existsSync(window.applicationData + "/LocalchatBetaChannel")) {
document.getElementById("oobe-action-beta").style.display = "none";
document.getElementById("oobe-action-stable").style.display = "";
}
function beta() {
if (fs.existsSync(window.applicationData + "/LocalchatBetaChannel")) {
fs.unlinkSync(window.applicationData + "/LocalchatBetaChannel");
} else {
fs.writeFileSync(window.applicationData + "/LocalchatBetaChannel", "");
}
require('electron').ipcRenderer.send('restart');
}
< / script >
< / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" > < / div >
< div class = "oobe-button-next-outer" >
< a class = "oobe-button-next" onclick = "oobePage(2);" > Start< / a >
< a class = "oobe-button-next" onclick = "oobePage(2);" data-i18n-text = "oobe/navigation/start" > < / a >
< / div >
< / div >
< div id = "oobe-version" > -< / div >
< script >
if (window.betaVersion) {
document.getElementById("oobe-version").innerText = "v" + window.version + "\n" + require('os').userInfo().username + "@" + require('os').hostname() + "\n" + window.applicationData;
} else {
document.getElementById("oobe-version").innerText = "";
}
< / script >
< / div >
< div id = "oobe-2" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/connect.svg" >
< img src = "icons/connect.svg" class = "oobe-beta-icon" >
< / div >
< div class = "oobe-main" >
< h1 > Let's get you connected< / h1 >
< h1 data-i18n-text = "oobe/connect/title" > < / h1 >
< div style = "margin-top: 50px;" >
Your administrator should have provided a Localchat Server Configuration (.lctsc) file. Make sure you have this file before clicking on Next.
< / div >
< div style = "margin-top: 50px;" data-i18n-text = "oobe/connect/description" > < / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" >
< a class = "oobe-button-back" onclick = "location.reload();" > Restart< / a >
< a class = "oobe-button-back" onclick = "location.reload();" data-i18n-text = "oobe/navigation/restart" > < / a >
< / div >
< div class = "oobe-button-next-outer" >
< a class = "oobe-button-next" onclick = "oobeUpload();" > Next< / a >
< a class = "oobe-button-next" onclick = "oobeUpload();" data-i18n-text = "oobe/navigation/next" > < / a >
< / div >
< / div >
< / div >
< div id = "oobe-3" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/check.svg" >
< img src = "icons/check.svg" class = "oobe-beta-icon" >
< / div >
< div class = "oobe-main" >
< h1 > Checking configuration...< / h1 >
< h1 data-i18n-text = "oobe/connect/check" > < / h1 >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" >
< a class = "oobe-button-back" onclick = "location.reload();" > Restart< / a >
< a class = "oobe-button-back" onclick = "location.reload();" data-i18n-text = "oobe/navigation/restart" > < / a >
< / div >
< / div >
< / div >
< div id = "oobe-4" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/error.svg" >
< img src = "icons/error.svg" class = "oobe-beta-icon" >
< / div >
< div class = "oobe-main" >
< h1 > Something went wrong< / h1 >
< h1 data-i18n-text = "oobe/error/title" > < / h1 >
< div style = "margin-top: 50px;" >
We were unable to check your configuration. Make sure you have provided the right file and that the server is powered on and working properly.
< / div >
< div style = "margin-top: 50px;" data-i18n-text = "oobe/error/description" > < / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" >
< a class = "oobe-button-back" onclick = "location.reload();" > Restart< / a >
< a class = "oobe-button-back" onclick = "location.reload();" data-i18n-text = "oobe/navigation/restart" > < / a >
< / div >
< / div >
< / div >
< div id = "oobe-5" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/disclaimers.svg" >
< img src = "icons/disclaimers.svg" class = "oobe-beta-icon" >
< / div >
< div class = "oobe-main" >
< h1 > Usage disclaimers< / h1 >
< h1 data-i18n-text = "oobe/disclaimers/title" > < / h1 >
< div style = "margin-top: 50px;" >
< div style= "background-color: #3f4949; color: #bec8c8; overflow-y: auto; height: 200px; border-radius: 10px; padding: 20px;" >
Thanks for using Localchat, we hope you are making great use of this software and that you will enjoy your experience.< br > < br > You may encounter bugs or other problems preventing you from using Localchat properly. You are encouraged to report these bugs to the developers so they can fix them as quickly as possible.< br > < br > Localchat is using end-to-end encryption, meaning your internet service provider, your school or organization, or any VPN or proxy server you are connected to will not be able to see your messages. This, however, does not prevent a malicious software (e.g. "keylogger") or a physical device (e.g. a video capture device) from intercepting your communications.< br > < br > Your use of Localchat should remain legal, and should respect the rules of your local server, if applicable. The Localchat developers don't limit the type of content that can be exchanged, but your local server administrator might.< br > < br > Enjoy Localchat.
< div class= "oobe-textbox" style= "background-color: #3f4949; color: #bec8c8; overflow-y: auto; height: 200px; border-radius: 10px; padding: 20px;" >
< span id = "oobe-disclaimer-beta" style = "display:none;" > PLEASE READ THIS BEFORE CONTINUING< br > < br > You are using a beta version of Localchat. Any update can render Localchat unusable and Equestria.dev should not be held responsible for any damage or issue while using a beta version of Localchat.< br > If you wish to switch back to a stable release, click on "Restart" then on "Switch to the stable channel". Continue at your own risk.< br > < br > -------------------< br > < br > < / span > Thanks for using Localchat, we hope you are making great use of this software and that you will enjoy your experience.< br > < br > You may encounter bugs or other problems preventing you from using Localchat properly. You are encouraged to report these bugs to the developers so they can fix them as quickly as possible.< br > < br > Localchat is using end-to-end encryption, meaning your internet service provider, your school or organization, or any VPN or proxy server you are connected to will not be able to see your messages. This, however, does not prevent a malicious software (e.g. "keylogger") or a physical device (e.g. a video capture device) from intercepting your communications.< br > < br > Your use of Localchat should remain legal, and should respect the rules of your local server, if applicable. The Localchat developers don't limit the type of content that can be exchanged, but your local server administrator might.< br > < br > Enjoy Localchat.
< / div >
< / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" >
< a class = "oobe-button-back" onclick = "location.reload();" > Restart< / a >
< a class = "oobe-button-back" onclick = "location.reload();" data-i18n-text = "oobe/navigation/restart" > < / a >
< / div >
< div class = "oobe-button-next-outer" >
< a class = "oobe-button-next" onclick = "oobePage(6);" > Next< / a >
< a class = "oobe-button-next" onclick = "oobePage(6);" data-i18n-text = "oobe/navigation/next" > < / a >
< / div >
< / div >
< / div >
< div id = "oobe-6" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/diagnostics.svg" >
< img src = "icons/diagnostics.svg" class = "oobe-beta-icon" >
< / div >
< div class = "oobe-main" >
< h1 > Allow diagnostic data?< / h1 >
< h1 data-i18n-text = "oobe/diagnostics/title" > < / h1 >
< div style = "margin-top: 50px;" >
Equestria.dev can collect diagnostic data while you use Localchat to resolve issues you are encountering and improve your experience.
< / div >
< div style = "margin-top: 50px;" data-i18n-text = "oobe/diagnostics/description" > < / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" >
< a class = "oobe-button-back" onclick = "diagnostics(false);" > Skip < / a >
< a class = "oobe-button-back" onclick = "diagnostics(false);" data-i18n-text = "oobe/diagnostics/no" > < / a >
< / div >
< div class = "oobe-button-next-outer" >
< a class = "oobe-button-next" onclick = "diagnostics(true);" > I agree < / a >
< a class = "oobe-button-next" onclick = "diagnostics(true);" data-i18n-text = "oobe/diagnostics/yes" > < / a >
< / div >
< / div >
< / div >
< div id = "oobe-7" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/c omplete.svg ">
< img src = "icons/c hangelog.svg" class = "oobe-beta-icon ">
< / div >
< div class = "oobe-main" >
< h1 > All set!< / h1 >
< h1 data-i18n-text = "oobe/changelog/title" > < / h1 >
< div style = "margin-top: 50px;" >
Thanks for using Localchat. You have now completed the configuration process and you are ready to use Localchat. Click on Done to start
< div class = "oobe-textbox" style = "background-color: #3f4949; color: #bec8c8; overflow-y: auto; height: 200px; border-radius: 10px; padding: 20px;" id = "changelog" > < / div >
< script >
document.getElementById("changelog").innerText = window.changeLog.trim().replace("%1", window.version).replaceAll(" ", " ");
< / script >
< / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" >
< a class = "oobe-button-back" onclick = "location.reload();" data-i18n-text = "oobe/navigation/restart" > < / a >
< / div >
< div class = "oobe-button-next-outer" >
< a class = "oobe-button-next" onclick = "oobePage(8);" data-i18n-text = "oobe/navigation/next" > Next< / a >
< / div >
< / div >
< / div >
< div id = "oobe-8" class = "oobe-page" >
< div class = "oobe-icon" >
< img src = "icons/complete.svg" class = "oobe-beta-icon" >
< / div >
< div class = "oobe-main" >
< h1 data-i18n-text = "oobe/done/title" > < / h1 >
< div style = "margin-top: 50px;" data-i18n-text = "oobe/done/description" > < / div >
< / div >
< div class = "oobe-buttons" >
< div class = "oobe-button-back-outer" > < / div >
< div class = "oobe-button-next-outer" >
< a class = "oobe-button-next" onclick = "completeOOBE();" > Done< / a >
< a class = "oobe-button-next" onclick = "completeOOBE();" data-i18n-text = "oobe/done/complete" > < / a >
< / div >
< / div >
< / div >
< / div >
< div id = "title-bar" >
< div id = "title-bar-user" >
< b > Localchat< / b >
< div id = "app" style = "width: 100%; height: 100%; z-index: -2; position: fixed;" >
< div id = "title-bar" >
< div id = "title-bar-user" >
< b > Localchat< / b >
< / div >
< div id = "title-bar-status" >
< span id = "beta-badge" style = "display: none;margin-right: 10px;background: #324b4c;color: #cce8e8;padding: 5px 10px;border-radius: 999px;" data-i18n-text = "beta" > < / span >
< img src = "icons/signal-none.svg" id = "title-bar-signal" >
< / div >
< / div >
< div id = "title-bar-status" >
< img src = "icons/signal-none.svg" id = "title-bar-signal" >
< script >
if (require('os').platform() === "darwin") {
document.getElementById("title-bar").classList.add("platform-mac");
} else if (require('os').platform() === "win32") {
document.getElementById("title-bar").classList.add("platform-windows");
}
< / script >
< div id = "chat-log" > < / div >
< div id = "input" >
< div id = "emoji-unpicker" style = "display: none; z-index: 99999; position: fixed; inset: 0;" onclick = "unpickEmoji();" > < / div >
< emoji-picker id = "emoji-picker" style = "display: none; z-index: 999999;" > < / emoji-picker >
< div id = "icons" style = "margin-left: 10px; margin-right: 10px;" >
< a data-i18n-title = "input/upload" class = "input-icon" onclick = "if (window.connected) uploadFile();" > < img src = "icons/upload.svg" > < / a >
< a data-i18n-title = "input/emoji" class = "input-icon" onclick = "pickEmoji();" > < img src = "icons/emojis.svg" > < / a >
< / div >
< div contenteditable = "true" style = "font-family: inherit; font-size: 14px;" id = "input-text" > < / div >
< / div >
< div id = "typing" > < b id = "typing-single" > X< / b > < span id = "typing-two-outer" > < span data-i18n-text = "input/typing/and" > < / span > < b id = "typing-two" > X< / b > < / span > < span id = "typing-many" data-i18n-text = "input/typing/many" > < / span > < span id = "typing-singular" data-i18n-text = "input/typing/singular" > < / span > < span id = "typing-plural" data-i18n-text = "input/typing/plural" > < / span > < span data-i18n-text = "input/typing/end" > < / span > < / div >
< / div >
< script >
if (require('os').platform() === "darwin") {
document.getElementById("title-bar").classList.add("platform-mac");
} else {
document.getElementById("title-bar").classList.add("platform-windows");
}
< / script >
< div id = "chat-log" > < / div >
< div id = "input" >
< a title = "Upload a file" class = "input-icon" onclick = "nonExistent(); return; if (window.connected) uploadFile();" > < img src = "icons/upload.svg" > < / a >
< div contenteditable = "true" style = "font-family: inherit; font-size: 14px;" id = "input-text" > < / div >
< div style = "font-family: inherit; font-size: 14px; opacity: .5;" id = "input-text-placeholder" > Send a message< / div >
< / div >
< div id = "typing" > < b id = "typing-single" > X< / b > < span id = "typing-two-outer" > and < b id = "typing-two" > X< / b > < / span > < span id = "typing-many" > Multiple people< / span > < span id = "typing-singular" > is< / span > < span id = "typing-plural" > are< / span > typing…< / div >
< script >
const { ipcRenderer } = require('electron');
const os = require("os");
document.getElementById("chat-log").ondrop = document.getElementById("input").ondrop = document.getElementById("title-bar").ondrop = (event) => {
if (!window.connected) return;
let foundFile = null;
if (event.dataTransfer.items) {
if ([...event.dataTransfer.items].filter(i => i.kind === "file").length > 0) {
foundFile = [...event.dataTransfer.items].filter(i => i.kind === "file")[0].getAsFile();
}
} else {
if ([...event.dataTransfer.files].filter(i => i.kind === "file").length > 0) {
foundFile = [...event.dataTransfer.files].filter(i => i.kind === "file")[0];
}
}
if (foundFile) processFileUpload(foundFile);
}
document.getElementById("chat-log").ondragover = document.getElementById("input").ondragover = document.getElementById("title-bar").ondragover = document.getElementById("app").ondragover = (event) => {
event.preventDefault();
}
function diagnostics(status) {
localStorage.setItem("diagnostics-data", status ? "true" : "false");
oobePage(7);
}
window.onerror = (_1, _2, _3, _4, error) => {
localSystemMessage("An error occurred: " + error.name + ": " + error.message + "; please report this on https://bugs.equestria.dev/issues/LCHT");
localSystemMessage(l("error").replace("%1", error.name).replace("%2", error.message) );
console.error(error);
}
@ -655,7 +920,7 @@
try {
require('fs').readFileSync(background);
} catch (e) {
localSystemMessage("Unable to load the background image: " + e.message + "; run /background to change or reset it" );
localSystemMessage(l("background_error").replace("%1", e.message) );
return;
}
@ -738,10 +1003,10 @@
< span class = "message-text" > ${DOMPurify.sanitize(marked.parse(message['text'].replaceAll("\r\n", "\n").replaceAll("\n", "< br > "))).replaceAll("< p > ", "").replaceAll("< / p > ", "")}< / span >
< / div >
` : (message['type'] === "file" ? `
< div class = "message user-message " id = "message-${message['_id']}" >
< div class = "message user-message message-file " id = "message-${message['_id']}" >
< span class = "message-date" title = "${new Date(message['date']).toString()}" > ${new Date(message['date']).toTimeString().split(":").splice(0, 2).join(":")}< / span >
< span class = "message-author" title = "${stripHTML(message['_source'])}" > ${stripHTML(message['author'])}< / span >
< span class = "message-text" > < span style = "opacity: .75;" > ${message['file']['name']} (${formatSize(message['file']['size'])}) · < a style = "color: white; cursor: pointer;" href = "data:${message['file']['type']};base64,${message['file']['content']}" download = "${stripHTML(message['file']['name'])}" > Download< / a > < / span > ${message['file']['type'].startsWith("image/") ? `< br > < img class = "message-file-preview" src = "data:${message['file']['type']};base64,${message['file']['content']}" > ` : (message['file']['type'].startsWith("audio/") ? `< br > < audio controls class = "message-file-preview" src = "data:${message['file']['type']};base64,${message['file']['content']}" > < / audio > ` : (message['file']['type'].startsWith("video/") ? `< br > < video class = "message-file-preview" src = "data:${message['file']['type']};base64,${message['file']['content']}" controls > < / video > ` : ``))}< / span >
< span class = "message-author" title = "${stripHTML(message['_source'])}" > ${stripHTML(message['author'])}${message['admin'] ? `< svg xmlns = "http://www.w3.org/2000/svg" height = "48" viewBox = "0 96 960 960" width = "48" class = "message-badge" fill = "#${stripHTML(message['colors'][1])}" > < path d = "M480 975q-140-35-230-162.5T160 533V295l320-120 320 120v238q0 152-90 279.5T480 975Zm0-62q115-38 187.5-143.5T740 533V337l-260-98-260 98v196q0 131 72.5 236.5T480 913Zm0-337Z" / > < / svg > ` : ``} < / span >
< div class = "message-text" style = "overflow: hidden;" > < div style = "display: grid; grid-template-columns: max-content 1fr${(!message['file']['type'].startsWith(" video / " ) & & ! message [ ' file ' ] [ ' type ' ] . startsWith ( " audio / " ) ) ? ` max-content ` : " " } ; grid-gap: 10px ; " > < div style = "display: flex; align-items: center; justify-content: center;" > < img src = "icons/file-${getIconFromType(message['file']['type'])}.svg" style = "width: 32px; height: 32px;" > < / div > < div style = "overflow: hidden;" > < div style = "text-overflow: ellipsis;white-space: nowrap;overflow: hidden;" title = "${stripHTML(message['file']['name'].replaceAll('" ' , " ' ' " ) ) } " > ${stripHTML(message['file']['name'])}< / div > < span style = "opacity: .75;" > ${formatSize(message['file']['size'])}< / span > < / div > < div > ${(!message['file']['type'].startsWith("video/") & & !message['file']['type'].startsWith("audio/")) ? ` < a style = "color: white; cursor: pointer;" href = "data:${message['file']['type']};base64,${message['file']['content']}" download = "${stripHTML(message['file']['name'])}" class = "input-icon" > < img src = "icons/download.svg" > < / a > ` : ``}< / div > < / div > ${message['file']['type'].startsWith("image/") ? `< br > < img class = "message-file-preview" src = "data:${message['file']['type']};base64,${message['file']['content']}" > ` : (message['file']['type'].startsWith("audio/") ? `< br > < audio controls class = "message-file-preview" src = "data:${message['file']['type']};base64,${message['file']['content']}" > < / audio > ` : (message['file']['type'].startsWith("video/") ? `< br > < video class = "message-file-preview" src = "data:${message['file']['type']};base64,${message['file']['content']}" controls > < / video > ` : ``))}< / div >
< / div >
` : ``)))))}`);
@ -752,6 +1017,30 @@
if (willScroll) el.scrollTop = el.scrollHeight;
}
function getIconFromType(type) {
let group = type.split("/")[0];
switch (group) {
case "text":
return "text";
case "audio":
return "music";
case "video":
return "movie";
case "font":
return "font";
case "image":
return "image";
default:
return "unknown";
}
}
function checkConfiguration() {
return new Promise((res, rej) => {
try {
@ -820,7 +1109,7 @@
localStorage.setItem("server-configuration", JSON.stringify(config));
} catch (e) {
console.error(e);
alert("The server configuration you have selected is invalid, please select another one." );
alert(l("invalid_config") );
await updateServer();
}
}
@ -853,6 +1142,7 @@
wsPingInterval = setInterval(() => {
ws.send(JSON.stringify({
type: "ping",
lang: window.langName,
date: new Date().getTime()
}));
}, 1000);
@ -860,11 +1150,11 @@
ws.onclose = (e) => {
if (window.connected & & window.reconnect) {
localSystemMessage("You left the chat" );
localSystemMessage(l("status/leave") );
}
if (!window.banned & & !window.connected & & window.reconnect & & !showedServerErrorMessage) {
localSystemMessage("Unable to connect to the Localchat server at " + window.serverURL + ", make sure the server is turned on" );
localSystemMessage(l("status/error").replace("%1", window.serverURL) );
}
window.connected = false;
@ -874,7 +1164,7 @@
if (!showedServerErrorMessage) {
showedServerErrorMessage = true;
localSystemMessage("If you wish to connect to another server, < a href = '#' onclick = 'updateServer();' > click here to import another .lctsc file< / a > " , true);
localSystemMessage(l("status/import").replace("%1", " < a href = '#' onclick = 'updateServer();' > ").replace("%2", "< / a > ") , true);
}
if (window.reconnect) {
@ -886,9 +1176,11 @@
setInterval(() => {
Array.from(document.getElementsByTagName("a")).filter(i => i.href).map((i) => {
i.onclick = (event) => {
event.preventDefault();
require('electron').shell.openExternal(i.href);
if (!i.href.startsWith("data:") & & !i.href.startsWith("#") & & !i.href.startsWith(location.href)) {
i.onclick = (event) => {
event.preventDefault();
require('electron').shell.openExternal(i.href);
}
}
});
});
@ -908,13 +1200,13 @@
if (data.token === window.token) {
window.serverKey = data.key;
window.connected = true;
systemMessage(userName + " (" + data.address + ") joined the chat", "You joined the chat" );
systemMessage(sl("status/join2", [["%1", userName], ["%2", data.address]]), l("status/join") );
} else {
localSystemMessage("Unable to verify the server's identity" );
localSystemMessage(l("status/unverify") );
ws.close();
}
} else if (data.type === "banned") {
localSystemMessage("You have been blocked from this server" );
localSystemMessage(l("status/banned") );
window.reconnect = false;
window.banned = true;
ws.close();
@ -933,7 +1225,7 @@
decrypted.file.content = deciphered;
if (crypto.createHash("md5").update(Buffer.from(deciphered, "base64")).digest("hex") !== decrypted.file.md5) {
localSystemMessage("Warning: this file is possibly corrupted, make sure to check it before opening it" );
localSystemMessage(l("upload/corrupt") );
}
}
@ -954,7 +1246,7 @@
if (decrypted.type === "dm" & & !window.directOverview) {
window.directOverview = true;
localSystemMessage("This is a private message from the server owner. Use /r to reply privately." )
localSystemMessage(l("dm_notice") )
}
if (decrypted.type === "typing") {
@ -1015,105 +1307,100 @@
if (value > 1024**2) {
if (value > 1024**3) {
if (value > 1024**4) {
return (value / 1024**4).toFixed(2) + " TiB" ;
return (value / 1024**4).toFixed(2) + " Ti" + l("byte") ;
} else {
return (value / 1024**3).toFixed(2) + " GiB" ;
return (value / 1024**3).toFixed(2) + " Gi" + l("byte") ;
}
} else {
return (value / 1024**2).toFixed(2) + " MiB" ;
return (value / 1024**2).toFixed(2) + " Mi" + l("byte") ;
}
} else {
return (value / 1024).toFixed(2) + " KiB" ;
return (value / 1024).toFixed(2) + " ki" + l("byte") ;
}
} else {
return value + " B" ;
return value + " " + l("byte") ;
}
}
async function uploadFile() {
let handles = await window.showOpenFilePicker({
multiple: false
});
function processFileUpload(file) {
if (file.type.startsWith("inode/") || file.type.trim() === "") {
localSystemMessage(l("upload/folder"));
return;
}
if (handles.length >= 1) {
let file = await handles[0].getFile();
if (file.size > 6*1024**2) {
localSystemMessage(l("upload/size").replace("%1", formatSize(6*1024**2)));
} else {
let reader = new FileReader();
reader.readAsArrayBuffer(file);
if (file.size > 5*1024**2) {
localSystemMessage("Unable to send this file because it exceeds the maximum allowed size (5 MiB)");
} else {
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = () => {
let content = Buffer.from(reader.result).toString("base64");
let key = crypto.randomBytes(32);
let iv = crypto.randomBytes(16);
let cipher = crypto.createCipheriv("aes-256-ctr", key, iv);
let ciphered = cipher.update(content, "utf8", "base64");
ciphered += cipher.final("base64");
let encrypted = iv.toString("base64") + ':' + ciphered;
let token = generateToken();
let fileObj = {
type: "file",
author: userName,
colors,
date: new Date().getTime(),
file: {
token,
size: file.size,
name: file.name,
type: file.type,
key: key.toString("base64"),
md5: crypto.createHash("md5").update(content).digest("hex"),
content: null
}
};
reader.onload = () => {
let content = Buffer.from(reader.result).toString("base64");
let key = crypto.randomBytes(32);
let iv = crypto.randomBytes(16);
let cryptObj = {
type: "encrypted",
message: encrypt(JSON.stringify(fileObj), window.serverKey)
}
cryptObj[token] = encrypted;
let cipher = crypto.createCipheriv("aes-256-ctr", key, iv);
let ciphered = cipher.update(content, "utf8", "base64");
ciphered += cipher.final("base64");
let encrypted = iv.toString("base64") + ':' + ciphered;
ws.send(JSON.stringify(cryptObj));