Updated 24 files and added 1046 files (automated)

mane
Mia Raindrops 1 month ago
parent e229e325e2
commit 1ff31fb1c0
Signed by: Mia Raindrops
GPG Key ID: EFBDC68435A574B7

@ -12,5 +12,21 @@
</value>
</option>
</inspection_tool>
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="emoji-picker" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

@ -4,6 +4,19 @@ const zlib = require('zlib');
const child_process = require("child_process");
const crypto = require("crypto");
console.log("Note: use './build.js stable' to release a stable version.");
console.log(" use './build.js launcher' to build the launchers.");
if (process.argv[2] === "stable" && fs.readFileSync("./client/index.html").toString().replaceAll(" ", "").includes("window.betaVersion=true;")) {
console.error("Error: stable version is marked as beta through the 'window.betaVersion' property.");
process.exit(2);
}
if (process.argv[2] !== "stable" && fs.readFileSync("./client/index.html").toString().replaceAll(" ", "").includes("window.betaVersion=false;")) {
console.error("Error: beta version is marked as stable through the 'window.betaVersion' property.");
process.exit(2);
}
let ignore = [
"bans.json",
"verify.json",
@ -107,11 +120,24 @@ for (let item of list) {
}
fs.writeFileSync("build/shared.lctpk", zlib.brotliCompressSync(JSON.stringify(pack)));
child_process.execSync("scp build/client.lctpk zephyrheights:/pool/web/cdn/localchat", { stdio: "inherit" });
child_process.execSync("scp build/server.lctpk zephyrheights:/pool/web/cdn/localchat", { stdio: "inherit" });
child_process.execSync("scp build/shared.lctpk zephyrheights:/pool/web/cdn/localchat", { stdio: "inherit" });
child_process.execSync("npx pkg -C GZip -o ../../build/server/mac/lcts -t node18-darwin-arm64 index.js", { cwd: "./launcher/server", stdio: "inherit" });
child_process.execSync("npx pkg -C GZip -o ../../build/server/linux/lcts -t node18-linuxstatic-x64 index.js", { cwd: "./launcher/server", stdio: "inherit" });
child_process.execSync("npx pkg -C GZip -o ../../build/server/win32/lcts.exe -t node18-win32-x64 index.js", { cwd: "./launcher/server", stdio: "inherit" });
child_process.execSync("npx electron-packager . Localchat --ignore build --ignore scripts --overwrite --icon icon.icns --platform=darwin --arch=arm64 --out=../../build/client", { cwd: "./launcher/client", stdio: "inherit" })
child_process.execSync("npx electron-packager . Localchat --ignore build --ignore scripts --overwrite --icon icon.icns --platform=win32 --arch=x64 --out=../../build/client", { cwd: "./launcher/client", stdio: "inherit" })
if (process.argv[2] === "stable") {
console.log("Uploading to stable channel");
child_process.execSync("scp build/client.lctpk bridlewood:/opt/localchat/update/client.stable.lctpk", { stdio: "inherit" });
child_process.execSync("scp build/server.lctpk bridlewood:/opt/localchat/update/server.stable.lctpk", { stdio: "inherit" });
child_process.execSync("scp build/shared.lctpk bridlewood:/opt/localchat/update/shared.stable.lctpk", { stdio: "inherit" });
} else {
console.log("Uploading to beta channel");
child_process.execSync("scp build/client.lctpk bridlewood:/opt/localchat/update/client.beta.lctpk", { stdio: "inherit" });
child_process.execSync("scp build/server.lctpk bridlewood:/opt/localchat/update/server.beta.lctpk", { stdio: "inherit" });
child_process.execSync("scp build/shared.lctpk bridlewood:/opt/localchat/update/shared.beta.lctpk", { stdio: "inherit" });
}
if (process.argv[2] === "launcher") {
child_process.execSync("npx pkg -C GZip -o ../../build/server/mac/lcts -t node18-darwin-arm64 index.js", { cwd: "./launcher/server", stdio: "inherit" });
child_process.execSync("npx pkg -C GZip -o ../../build/server/linux/lcts -t node18-linuxstatic-x64 index.js", { cwd: "./launcher/server", stdio: "inherit" });
child_process.execSync("npx pkg -C GZip -o ../../build/server/win32/lcts.exe -t node18-win32-x64 index.js", { cwd: "./launcher/server", stdio: "inherit" });
child_process.execSync("npx electron-packager . Localchat --ignore build --ignore scripts --overwrite --icon icon.icns --platform=darwin --arch=arm64 --out=../../build/client", { cwd: "./launcher/client", stdio: "inherit" })
child_process.execSync("npx electron-packager . Localchat --ignore build --ignore scripts --overwrite --icon icon.icns --platform=win32 --arch=x64 --out=../../build/client", { cwd: "./launcher/client", stdio: "inherit" })
child_process.execSync("npx electron-packager . Localchat --ignore build --ignore scripts --overwrite --icon icon.png --platform=linux --arch=x64 --out=../../build/client", { cwd: "./launcher/client", stdio: "inherit" })
}

@ -1,6 +1,6 @@
commands = {
"verify": (_1, _2) => {
localSystemMessage("Use this fingerprint to verify the server's identity: " + require('crypto').createHash("sha256").update(window.serverVerifyKey).digest("base64"));
localSystemMessage(l("commands/verify") + "<br><pre style='margin: 0 !important;'>" + require('crypto').createHash("sha256").update(window.serverVerifyKey).digest("base64").match(/.{1,35}/g).join("\n") + "</pre>", true);
},
"nick": (argument, _) => {
if (argument.trim() === "") argument = require('os').userInfo().username;
@ -8,7 +8,7 @@ commands = {
let oldUserName = userName;
userName = argument;
localStorage.setItem("username", userName);
systemMessage(`${oldUserName} changed their name to "${argument}"`, `Your name is now "${argument}"`);
systemMessage(sl("commands/nick/other", [["%2", oldUserName], ["%1", argument]]), l("commands/nick/self").replace("%1", argument));
},
"background": (argument, _) => {
if (argument.trim() === "") argument = null;
@ -17,15 +17,15 @@ commands = {
try {
require('fs').readFileSync(argument);
} catch (e) {
localSystemMessage("Unable to change the background image: " + e.message);
localSystemMessage(l("commands/background/error").replace("%1", e.message));
return;
}
localStorage.setItem("background", argument);
localSystemMessage("Successfully changed the background image to " + require('path').resolve(argument));
localSystemMessage(l("commands/background/changed").replace("%1", require('path').resolve(argument)));
} else {
localStorage.removeItem("background");
localSystemMessage("Successfully removed the background image");
localSystemMessage(l("commands/background/removed"));
}
updateBackground();
@ -36,24 +36,24 @@ commands = {
if (parts.length === 0) {
colors = [ "ffffff", "ffffff" ];
localStorage.setItem("colors", JSON.stringify(colors));
systemMessage(`${userName} changed their profile colors to the default ones`, `Your profile colors are now the default ones`);
systemMessage(sl("commands/color/reset/other", [["%1", userName]]), l("commands/color/reset/self"));
return;
} else if (parts.length === 1) {
parts[1] = parts[0];
}
if (!parts[0].match(/(^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$)/gm)) {
localSystemMessage("The first color is not a valid hexadecimal color code");
localSystemMessage(l("commands/color/invalid/first"));
return;
}
if (!parts[1].match(/(^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$)/gm)) {
localSystemMessage("The second color is not a valid hexadecimal color code");
localSystemMessage(l("commands/color/invalid/second"));
return;
}
colors = [ parts[0], parts[1] ];
systemMessage(`${userName} changed their profile colors`, `Your profile colors are now #${parts[0]} and #${parts[1]}`);
systemMessage(sl("commands/color/changed/other", [["%1", userName]]), l("commands/color/changed/self").replace("%1", parts[0].trim()).replace("%2", parts[1].trim()));
},
"list": (argument, _) => {
send({
@ -69,7 +69,7 @@ commands = {
},
"ban": (argument, _) => {
if (argument.trim() === "") {
localSystemMessage("Please specify the IP address of a client you want to block");
localSystemMessage(l("commands/ban"));
return;
}
@ -81,7 +81,7 @@ commands = {
},
"unban": (argument, _) => {
if (argument.trim() === "") {
localSystemMessage("Please specify the IP address of a client you want to unblock");
localSystemMessage(l("commands/unban"));
return;
}
@ -99,7 +99,7 @@ commands = {
},
"op": (argument, _) => {
if (argument.trim() === "") {
localSystemMessage("Please specify the IP address of a client you want to make an operator");
localSystemMessage(l("commands/op"));
return;
}
@ -111,7 +111,7 @@ commands = {
},
"deop": (argument, _) => {
if (argument.trim() === "") {
localSystemMessage("Please specify the IP address of a client you want to not make an operator");
localSystemMessage(l("commands/deop"));
return;
}
@ -125,7 +125,7 @@ commands = {
let parts = argument.split(" ");
if (parts.length < 2) {
localSystemMessage("Please specify the IP address of the client the message to send");
localSystemMessage(l("commands/msg"));
return;
}
@ -139,7 +139,7 @@ commands = {
},
"r": (argument, _) => {
if (argument.trim() === "") {
localSystemMessage("Please specify the message to send");
localSystemMessage(l("commands/r"));
return;
}
@ -152,30 +152,42 @@ commands = {
},
"ping": (_1, _2) => {
if (window.connected) {
localSystemMessage("Your ping is: " + window.ping + " ms");
localSystemMessage(l("commands/ping").replace("%1", window.ping));
} else {
localSystemMessage("You are not currently connected to a server");
localSystemMessage(l("commands/_server2"));
}
},
"url": (_1, _2) => {
if (window.connected) {
localSystemMessage("The server you are connected to is: " + window.serverURL);
localSystemMessage(l("commands/url") + " " + window.serverURL);
} else {
localSystemMessage("You are not currently connected to a server");
localSystemMessage(l("commands/_server2"));
}
},
"version": (_1, _2) => {
if (window.connected) {
localSystemMessage("Localchat version " + stripHTML(window.version) + "<br>Node.js version " + process.versions.node + "<br>Chrome version " + process.versions.chrome + "<br>Electron version " + process.versions.electron + "<br>Server version " + stripHTML(window.serverVersion), true);
localSystemMessage(l("commands/version/localchat") + " " + stripHTML(window.version) + "<br>" + l("commands/version/node") + " " + process.versions.node + "<br>" + l("commands/version/chrome") + " " + process.versions.chrome + "<br>" + l("commands/version/electron") + " " + process.versions.electron + "<br>" + l("commands/version/server") + " " + stripHTML(window.serverVersion), true);
} else {
localSystemMessage("Localchat version " + stripHTML(window.version) + "<br>Node.js version " + process.versions.node + "<br>Chrome version " + process.versions.chrome + "<br>Electron version " + process.versions.electron, true);
localSystemMessage(l("commands/version/localchat") + " " + stripHTML(window.version) + "<br>" + l("commands/version/node") + " " + process.versions.node + "<br>" + l("commands/version/chrome") + " " + process.versions.chrome + "<br>" + l("commands/version/electron") + " " + process.versions.electron, true);
}
},
"changelog": (_1, _2) => {
localSystemMessage(window.changeLog.trim().replace("%1", window.version).replaceAll("\n", "<br>"), true);
},
"channel": (_1, _2) => {
if (require('fs').existsSync(window.applicationData + "/LocalchatBetaChannel")) {
require('fs').unlinkSync(window.applicationData + "/LocalchatBetaChannel");
localSystemMessage(l("commands/channel/stable"));
} else {
require('fs').writeFileSync(window.applicationData + "/LocalchatBetaChannel", "");
localSystemMessage(l("commands/channel/beta"));
}
},
"_": (argument, command) => {
localSystemMessage("Sorry, /" + command + " is not a valid slash command, use /help to get a list");
localSystemMessage(l("commands/_").replace("%1", command));
},
"help": (_1, _2) => {
localSystemMessage("Available commands: " + Object.keys(commands).filter(i => i !== "_").sort((a, b) => a.localeCompare(b)).join(", "));
localSystemMessage(l("commands/help") + " " + Object.keys(commands).filter(i => i !== "_").sort((a, b) => a.localeCompare(b)).join(", "));
},
"reload": (_1, _2) => {
location.reload();
@ -194,7 +206,7 @@ function runCommand(message) {
if (window.connected) {
commands[command](argument, command);
} else {
localSystemMessage("Please connect to the server before using slash commands");
localSystemMessage(l("commands/_server"));
}
} else {
commands["_"](argument, command);

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

@ -0,0 +1,62 @@
function mergeRecursive(obj1, obj2) {
for (const p in obj2) {
try {
if (obj2[p].constructor === Object) {
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
} catch (e) {
obj1[p] = obj2[p];
}
}
return obj1;
}
function loadLang() {
let language = window.betaVersion ? (localStorage.getItem("language") ?? "en-US") : "en-US";
window.langName = language;
delete window.lang;
window.lang = mergeRecursive(require('../shared/lang/en-US.json'), require('../shared/lang/' + language.replaceAll("/", "-") + ".json"));
for (let item of Array.from(document.querySelectorAll("[data-i18n-text]"))) {
item.innerText = l(item.getAttribute("data-i18n-text"));
}
for (let item of Array.from(document.querySelectorAll("[data-i18n-title]"))) {
item.title = l(item.getAttribute("data-i18n-title"));
}
}
window.addEventListener("load", () => {
loadLang();
});
function l(path) {
let line = 'window.lang["' + path.split("/").map(i => i.replaceAll('"', "'")).join('"]["') + '"]';
console.log(line);
try {
if (eval(line)) {
return eval(line);
} else {
return path;
}
} catch (e) {
return path;
}
}
function sl(path, substitutes) {
let obj = {};
obj['message'] = path;
obj['sub'] = substitutes;
return JSON.stringify(obj);
}
function changeLanguageOOBE() {
localStorage.setItem("language", document.getElementById("oobe-dropdown-language").value);
location.reload();
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path fill="#ede0de" d="M172 936q-41.777 0-59.388-39Q95 858 124 826l248-280V276h-52q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T320 216h320q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T640 276h-52v270l248 280q29 32 11.388 71-17.611 39-59.388 39H172Zm-12-60h640L528 568V276h-96v292L160 876Zm318-300Z"/></svg>

After

Width:  |  Height:  |  Size: 437 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path fill="#e0e3e2" d="M172 936q-41.777 0-59.388-39Q95 858 124 826l248-280V276h-52q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T320 216h320q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T640 276h-52v270l248 280q29 32 11.388 71-17.611 39-59.388 39H172Zm-12-60h640L528 568V276h-96v292L160 876Zm318-300Z"/></svg>

After

Width:  |  Height:  |  Size: 437 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m346 996-76-130-151-31 17-147-96-112 96-111-17-147 151-31 76-131 134 62 134-62 77 131 150 31-17 147 96 111-96 112 17 147-150 31-77 130-134-62-134 62Zm27-79 107-45 110 45 67-100 117-30-12-119 81-92-81-94 12-119-117-28-69-100-108 45-110-45-67 100-117 28 12 119-81 94 81 92-12 121 117 28 70 100Zm107-341Zm-43 133 227-225-45-41-182 180-95-99-46 45 141 140Z" fill="#e7bdb7"/></svg>

After

Width:  |  Height:  |  Size: 471 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m346 996-76-130-151-31 17-147-96-112 96-111-17-147 151-31 76-131 134 62 134-62 77 131 150 31-17 147 96 111-96 112 17 147-150 31-77 130-134-62-134 62Zm27-79 107-45 110 45 67-100 117-30-12-119 81-92-81-94 12-119-117-28-69-100-108 45-110-45-67 100-117 28 12 119-81 94 81 92-12 121 117 28 70 100Zm107-341Zm-43 133 227-225-45-41-182 180-95-99-46 45 141 140Z" fill="#b0cccc"/></svg>

After

Width:  |  Height:  |  Size: 471 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M63 519 0 456q93-95 218-147.5T480 256q24 0 54 1.5t62 5.5l-42 86q-18-2-36-2.5t-38-.5q-119 0-225.5 46T63 519Zm123 118-63-63q69-67 165.5-102T508 445l-39 81q-77-2-154.5 29T186 637Zm258 255q-27-11-39.5-41t1.5-59l237-490q3-7 10.5-10t15.5-1q8 2 13 9t3 15L547 850q-8 32-41 42.5t-62-.5Zm330-255q-16-17-44.5-37T681 571l21-86q35 12 73 38.5t62 50.5l-63 63Zm123-118q-39-38-85-69t-88-50l22-90q65 27 116 63.5t98 82.5l-63 63Z" fill="#e7bdb7"/></svg>

After

Width:  |  Height:  |  Size: 528 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m421 758 283-283-46-45-237 237-120-120-45 45 165 166Zm59 218q-82 0-155-31.5t-127.5-86Q143 804 111.5 731T80 576q0-83 31.5-156t86-127Q252 239 325 207.5T480 176q83 0 156 31.5T763 293q54 54 85.5 127T880 576q0 82-31.5 155T763 858.5q-54 54.5-127 86T480 976Zm0-60q142 0 241-99.5T820 576q0-142-99-241t-241-99q-141 0-240.5 99T140 576q0 141 99.5 240.5T480 916Zm0-340Z" fill="#e7bdb7"/></svg>

After

Width:  |  Height:  |  Size: 476 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m232 699-63-63q70-70 146.5-105T480 496q88 0 164.5 35T791 636l-63 63q-61-61-123-87t-125-26q-63 0-125 26t-123 87ZM63 530 0 467q93-95 216.5-153T480 256q140 0 263.5 58T960 467l-63 63q-88-84-192.5-134T480 346q-120 0-224.5 50T63 530Zm417 417 148-149q-29-29-66.5-45.5T480 736q-44 0-81.5 16.5T332 798l148 149Z" fill="#e7bdb7"/></svg>

After

Width:  |  Height:  |  Size: 420 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path fill="#ede0de" d="M320 814 80 574l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z"/></svg>

After

Width:  |  Height:  |  Size: 222 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path fill="#e7bdb7" d="M363 866q-16 0-29-9t-18-23l-97-228H48v-60h213l102 241 187-467q5-14 18-23t29-9q16 0 29 9t18 23l97 226h171v60H699L597 367 410 834q-5 14-18 23t-29 9Z"/></svg>

After

Width:  |  Height:  |  Size: 265 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m40 936 440-760 440 760H40Zm104-60h672L480 296 144 876Zm340.175-57q12.825 0 21.325-8.675 8.5-8.676 8.5-21.5 0-12.825-8.675-21.325-8.676-8.5-21.5-8.5-12.825 0-21.325 8.675-8.5 8.676-8.5 21.5 0 12.825 8.675 21.325 8.676 8.5 21.5 8.5ZM454 708h60V484h-60v224Zm26-122Z" fill="#e7bdb7"/></svg>

After

Width:  |  Height:  |  Size: 382 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M220 896q-24 0-42-18t-18-42V693h60v143h520V693h60v143q0 24-18 42t-42 18H220Zm260-153L287 550l43-43 120 120V256h60v371l120-120 43 43-193 193Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 259 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M626 523q22.5 0 38.25-15.75T680 469q0-22.5-15.75-38.25T626 415q-22.5 0-38.25 15.75T572 469q0 22.5 15.75 38.25T626 523Zm-292 0q22.5 0 38.25-15.75T388 469q0-22.5-15.75-38.25T334 415q-22.5 0-38.25 15.75T280 469q0 22.5 15.75 38.25T334 523Zm146 272q66 0 121.5-35.5T682 663H278q26 61 81 96.5T480 795Zm0 181q-83 0-156-31.5T197 859q-54-54-85.5-127T80 576q0-83 31.5-156T197 293q54-54 127-85.5T480 176q83 0 156 31.5T763 293q54 54 85.5 127T880 576q0 83-31.5 156T763 859q-54 54-127 85.5T480 976Zm0-400Zm0 340q142.375 0 241.188-98.812Q820 718.375 820 576t-98.812-241.188Q622.375 236 480 236t-241.188 98.812Q140 433.625 140 576t98.812 241.188Q337.625 916 480 916Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 768 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z" fill="#fff3aa"/></svg>

After

Width:  |  Height:  |  Size: 768 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M290 896V356H80V256h520v100H390v540H290Zm360 0V556H520V456h360v100H750v340H650Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 198 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M180 936q-24 0-42-18t-18-42V276q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600V276H180v600Zm56-97h489L578 583 446 754l-93-127-117 152Zm-56 97V276v600Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 296 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m140 256 74 152h130l-74-152h89l74 152h130l-74-152h89l74 152h130l-74-152h112q24 0 42 18t18 42v520q0 24-18 42t-42 18H140q-24 0-42-18t-18-42V316q0-24 18-42t42-18Zm0 212v368h680V468H140Zm0 0v368-368Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 314 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M393 936q-63 0-106.5-43.5T243 786q0-63 43.5-106.5T393 636q28 0 50.5 8t39.5 22V216h234v135H543v435q0 63-43.5 106.5T393 936Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 241 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M277 777h275v-60H277v60Zm0-171h406v-60H277v60Zm0-171h406v-60H277v60Zm-97 501q-24 0-42-18t-18-42V276q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600V276H180v600Zm0-600v600-600Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 320 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M220 976q-24 0-42-18t-18-42V236q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554V236H220v680h520V422H551ZM220 236v186-186 680-680Z" fill="#e0e3e2"/></svg>

After

Width:  |  Height:  |  Size: 264 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path fill="#ede0de" d="M480 976q-84 0-157-31.5T196 859q-54-54-85-127.5T80 574q0-84 31-156.5T196 291q54-54 127-84.5T480 176q84 0 157 30.5T764 291q54 54 85 126.5T880 574q0 84-31 157.5T764 859q-54 54-127 85.5T480 976Zm0-58q35-36 58.5-82.5T577 725H384q14 60 37.5 108t58.5 85Zm-85-12q-25-38-43-82t-30-99H172q38 71 88 111.5T395 906Zm171-1q72-23 129.5-69T788 725H639q-13 54-30.5 98T566 905ZM152 665h159q-3-27-3.5-48.5T307 574q0-25 1-44.5t4-43.5H152q-7 24-9.5 43t-2.5 45q0 26 2.5 46.5T152 665Zm221 0h215q4-31 5-50.5t1-40.5q0-20-1-38.5t-5-49.5H373q-4 31-5 49.5t-1 38.5q0 21 1 40.5t5 50.5Zm275 0h160q7-24 9.5-44.5T820 574q0-26-2.5-45t-9.5-43H649q3 35 4 53.5t1 34.5q0 22-1.5 41.5T648 665Zm-10-239h150q-33-69-90.5-115T565 246q25 37 42.5 80T638 426Zm-254 0h194q-11-53-37-102.5T480 236q-32 27-54 71t-42 119Zm-212 0h151q11-54 28-96.5t43-82.5q-75 19-131 64t-91 115Z"/></svg>

After

Width:  |  Height:  |  Size: 945 B

@ -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 += '&nbsp;';
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/complete.svg">
<img src="icons/changelog.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("&nbsp;", " ");
</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">&nbsp;</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));