Files
ha.mr/main.js
T
Matthew Yang (杨佳明) 2f7e26b02b Fix other domains (#7)
* Make main compressor capable of other domains

* Fix missing bracket

* Domain needs to contain port as well

* Add decode functionality for other domains

---------

Co-authored-by: p2r3 <41925384+p2r3@users.noreply.github.com>
2026-08-16 13:06:17 +03:00

223 lines
6.9 KiB
JavaScript

import { compress, decompress } from "./compress.js";
import {
outputAlphabetASCII,
outputAlphabetQR,
outputAlphabetEmoji
} from "./alphabets.js";
let domain = window.location.hostname;
if (domain !== "ha.mr" && domain !== "www.ha.mr") {
console.log(`This page is intended to be used on the ha.mr domain. You are currently on ${domain}.`);
}
const webPort = window.location.port;
if (webPort && webPort !== "80" && webPort !== "443") {
domain += `:${webPort}`;
}
var settings = {
emoji: false,
qr: false
};
const settingsElements = {
emoji: "#settings-emoji",
qr: "#settings-qr"
};
for (const setting in settingsElements) {
const element = document.querySelector(settingsElements[setting]);
settings[setting] = element.checked;
element.addEventListener("change", (event) => {
settings[setting] = element.checked;
updateOutput();
});
}
function countSymbols (string, alphabet) {
let count = 0;
while (string) {
const symbol = alphabet.find(c => string.endsWith(c));
string = string.slice(0, symbol ? -symbol.length : -1);
count ++;
}
return count;
}
const inputLinkElement = document.querySelector("#input-link");
const outputLinkElement = document.querySelector("#output-link");
const outputRatioElement = document.querySelector("#output-ratio");
const queryWarningElement = document.querySelector("#query-warning");
const qrCodeImage = document.querySelector("#qrcode");
const qrCodeCorrectionLevelContainer = document.querySelector("#qr-correct-level-container");
const qrCodeCorrectionLevelElement = document.querySelector("#qr-correct-level");
let qrCorrectionManuallySet = false;
qrCodeCorrectionLevelElement.addEventListener("change", () => {
qrCorrectionManuallySet = true;
updateOutput();
});
function getOptimalErrorCorrectionLevel (text) {
const levels = ["M", "Q", "H"];
const baseVersion = QRCode.create(text, {
errorCorrectionLevel: levels[0]
}).version;
let optimalLevel = levels[0];
for (const level of levels.slice(1)) {
try {
const candidate = QRCode.create(text, {
errorCorrectionLevel: level
});
if (candidate.version > baseVersion) {
break;
}
optimalLevel = level;
} catch {
break;
}
}
return optimalLevel;
}
function updateOutput () {
const input = inputLinkElement.value.trim();
try {
const alphabet = settings.emoji ? outputAlphabetEmoji : outputAlphabetASCII;
const output = compress(input, alphabet);
let inputNormalized = input;
const inputLower = input.toLowerCase();
if (inputLower.startsWith("https://")) {
inputNormalized = input.slice(8);
} else if (inputLower.startsWith("http://")) {
inputNormalized = input.slice(7);
}
let excessiveParams = false;
if (URL.canParse("http://" + inputNormalized)) {
const url = new URL("http://" + inputNormalized);
if (url.searchParams.size > 1) {
excessiveParams = true;
}
}
if (excessiveParams) {
queryWarningElement.style.display = "inline";
} else {
queryWarningElement.style.display = "none";
}
const ratio = (1 - (countSymbols(output, alphabet) + 6) / inputNormalized.length) * 100;
if (ratio < -300) {
outputRatioElement.textContent = `Output is much larger than the input`;
outputRatioElement.style.color = "rgb(255, 50, 50)";
} else if (ratio < 0) {
outputRatioElement.textContent = `Output is ${Math.floor(-ratio)}% larger than the input`;
outputRatioElement.style.color = "rgb(255, 50, 50)";
} else if (ratio > 0) {
outputRatioElement.textContent = `Output is ${Math.ceil(ratio)}% smaller than the input`;
outputRatioElement.style.color = "rgb(15, 190, 15)";
} else {
outputRatioElement.textContent = "Output is the same length as the input";
outputRatioElement.style.color = "gray";
}
outputLinkElement.textContent = `http://${domain}#${output}`;
outputLinkElement.href = `http://${domain}#${output}`;
outputLinkElement.style.color = "";
if (settings.qr) {
const correctionLevels = ["L", "M", "Q", "H"];
qrCodeImage.style.display = "inline";
qrCodeCorrectionLevelContainer.style.display = "inline";
const qrCodeDomain = domain.toUpperCase();
const qrCodeLink = `HTTP://${qrCodeDomain}/${compress(input, outputAlphabetQR)}`;
if (!qrCorrectionManuallySet) {
const optimalLevel = getOptimalErrorCorrectionLevel(qrCodeLink);
qrCodeCorrectionLevelElement.value = correctionLevels.indexOf(optimalLevel);
}
const errorCorrection =
correctionLevels[qrCodeCorrectionLevelElement.value];
QRCode.toDataURL(qrCodeLink, {
errorCorrectionLevel: errorCorrection,
scale: 8
}, (err, url) => {
if (err) {
qrCodeImage.style.display = "none";
qrCodeCorrectionLevelContainer.style.display = "none";
return;
}
qrCodeImage.src = url;
qrCodeImage.title = qrCodeLink;
});
} else {
qrCodeImage.style.display = "none";
qrCodeCorrectionLevelContainer.style.display = "none";
}
} catch (e) {
if (!input.trim()) {
outputLinkElement.textContent = "Enter a link above to compress";
} else {
outputLinkElement.textContent = "Invalid link";
outputLinkElement.style.color = "rgb(255, 50, 50)";
console.error(e);
}
qrCodeImage.style.display = "none";
qrCodeCorrectionLevelContainer.style.display = "none";
outputRatioElement.style.color = "rgba(255, 255, 255, 0)";
outputLinkElement.removeAttribute("href");
queryWarningElement.style.display = "none";
}
}
inputLinkElement.addEventListener("input", () => {
qrCorrectionManuallySet = false;
updateOutput();
});
(() => {
let payload = null;
let alphabet = outputAlphabetASCII;
// Get hash value of current address bar
if (window.location.hash) {
// Decode hash value in case it's non-ASCII
payload = decodeURIComponent(window.location.hash.slice(1));
// Remove all whitespace - we never use whitespace when encoding hash values
payload = payload.replaceAll(" ", "");
// Check if input is pure ASCII - potentially unreliable?
const useEmoji = Array.from(payload).some(c => !outputAlphabetASCII.includes(c));
alphabet = useEmoji ? outputAlphabetEmoji : outputAlphabetASCII;
} else {
// If no hash value, we're likely reading a QR code
// For that, use the path instead
payload = decodeURIComponent(window.location.pathname.slice(1));
alphabet = outputAlphabetQR;
}
if (payload && payload.trim()) {
try {
const target = decompress(payload, alphabet);
window.location.href = target;
return;
} catch (e) {
console.warn(`Redirect failed. Could not decode input.`);
console.error(e);
}
}
updateOutput();
document.querySelector("#loader").style.opacity = 0;
document.querySelector("#content").style.opacity = 1;
document.querySelector("#content").style.pointerEvents = "auto";
})();