first commit

This commit is contained in:
2026-08-14 11:44:00 +02:00
commit 4fa600a3a3
990 changed files with 153262 additions and 0 deletions
+936
View File
@@ -0,0 +1,936 @@
const encodeForm =
document.getElementById("encode-form");
const decodeForm =
document.getElementById("decode-form");
const encodeImage =
document.getElementById("encode-image");
const decodeImage =
document.getElementById("decode-image");
const encodeDropzone =
document.getElementById("encode-dropzone");
const decodeDropzone =
document.getElementById("decode-dropzone");
const encodeImageInfo =
document.getElementById("encode-image-info");
const decodeImageInfo =
document.getElementById("decode-image-info");
const messageSection =
document.getElementById("message-section");
const fileSection =
document.getElementById("file-section");
const messageInput =
document.getElementById("message");
const messageSize =
document.getElementById("message-size");
const secretFile =
document.getElementById("secret-file");
const encodePassword =
document.getElementById("encode-password");
const decodePassword =
document.getElementById("decode-password");
const encodeButton =
document.getElementById("encode-button");
const decodeButton =
document.getElementById("decode-button");
const encodeStatus =
document.getElementById("encode-status");
const decodeStatus =
document.getElementById("decode-status");
const decodedMessage =
document.getElementById("decoded-message");
const messageOutput =
document.getElementById("message-output");
const copyMessage =
document.getElementById("copy-message");
function getEncodeMode() {
const selected =
document.querySelector(
'input[name="encode-mode"]:checked'
);
return selected
? selected.value
: "message";
}
function setStatus(
element,
message,
type = ""
) {
element.textContent =
message;
element.className =
`status ${type}`;
}
function setLoading(
button,
loading
) {
if (loading) {
button.disabled =
true;
button.dataset.originalText =
button.textContent;
button.textContent =
"Working...";
} else {
button.disabled =
false;
button.textContent =
button.dataset.originalText ||
button.textContent;
}
}
function formatBytes(bytes) {
if (bytes < 1024) {
return `${bytes} B`;
}
if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;
}
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}
function setSelectedFile(
input,
file
) {
/*
* DataTransfer lets us put a file selected
* through drag-and-drop into the file input.
*/
const dataTransfer =
new DataTransfer();
dataTransfer.items.add(file);
input.files =
dataTransfer.files;
input.dispatchEvent(
new Event(
"change",
{
bubbles: true
}
)
);
}
function setupDropzone(
dropzone,
input
) {
dropzone.addEventListener(
"click",
() => {
input.click();
}
);
dropzone.addEventListener(
"keydown",
(event) => {
if (
event.key === "Enter" ||
event.key === " "
) {
event.preventDefault();
input.click();
}
}
);
[
"dragenter",
"dragover"
].forEach((eventName) => {
dropzone.addEventListener(
eventName,
(event) => {
event.preventDefault();
dropzone.classList.add(
"dragging"
);
}
);
});
[
"dragleave",
"drop"
].forEach((eventName) => {
dropzone.addEventListener(
eventName,
(event) => {
event.preventDefault();
dropzone.classList.remove(
"dragging"
);
}
);
});
dropzone.addEventListener(
"drop",
(event) => {
const file =
event.dataTransfer.files[0];
if (!file) {
return;
}
if (
!file.type.includes("png") &&
!file.name
.toLowerCase()
.endsWith(".png")
) {
alert(
"Please select a PNG image."
);
return;
}
setSelectedFile(
input,
file
);
}
);
input.addEventListener(
"change",
() => {
const file =
input.files[0];
if (!file) {
return;
}
dropzone.classList.add(
"has-file"
);
const strong =
dropzone.querySelector(
"strong"
);
const span =
dropzone.querySelector(
"span"
);
strong.textContent =
file.name;
span.textContent =
formatBytes(file.size);
}
);
}
setupDropzone(
encodeDropzone,
encodeImage
);
setupDropzone(
decodeDropzone,
decodeImage
);
async function getImageInfo(file) {
const form =
new FormData();
form.append(
"image",
file
);
const response =
await fetch(
"/api/info",
{
method: "POST",
body: form
}
);
const data =
await response.json();
if (!response.ok) {
throw new Error(
data.error ||
"Unable to inspect image"
);
}
return data;
}
async function updateImageInfo(
file,
element,
statusElement
) {
element.classList.add(
"hidden"
);
if (!file) {
return;
}
try {
const info =
await getImageInfo(file);
element.innerHTML =
`
<strong>
${info.width} × ${info.height}
</strong>
<span>
${info.format.toUpperCase()}
· ${info.capacityKB} KB capacity
</span>
`;
element.classList.remove(
"hidden"
);
} catch (error) {
setStatus(
statusElement,
error.message,
"error"
);
}
}
encodeImage.addEventListener(
"change",
async () => {
await updateImageInfo(
encodeImage.files[0],
encodeImageInfo,
encodeStatus
);
}
);
messageInput.addEventListener(
"input",
() => {
const count =
messageInput.value.length;
messageSize.textContent =
`${count.toLocaleString()} character${
count === 1
? ""
: "s"
}`;
}
);
document
.querySelectorAll(
'input[name="encode-mode"]'
)
.forEach((radio) => {
radio.addEventListener(
"change",
() => {
const mode =
getEncodeMode();
if (
mode === "message"
) {
messageSection
.classList
.remove("hidden");
fileSection
.classList
.add("hidden");
} else {
messageSection
.classList
.add("hidden");
fileSection
.classList
.remove("hidden");
}
}
);
});
encodeForm.addEventListener(
"submit",
async (event) => {
event.preventDefault();
const image =
encodeImage.files[0];
const password =
encodePassword.value;
const mode =
getEncodeMode();
if (!image) {
setStatus(
encodeStatus,
"Please select a PNG image.",
"error"
);
return;
}
const form =
new FormData();
form.append(
"image",
image
);
/*
* Password is optional.
*/
if (password) {
form.append(
"password",
password
);
}
if (
mode === "message"
) {
if (
!messageInput.value
) {
setStatus(
encodeStatus,
"Please enter a message.",
"error"
);
return;
}
form.append(
"message",
messageInput.value
);
} else {
const file =
secretFile.files[0];
if (!file) {
setStatus(
encodeStatus,
"Please select a file.",
"error"
);
return;
}
form.append(
"file",
file
);
}
setLoading(
encodeButton,
true
);
setStatus(
encodeStatus,
"Encoding..."
);
try {
const response =
await fetch(
"/api/encode",
{
method: "POST",
body: form
}
);
if (!response.ok) {
const data =
await response.json();
throw new Error(
data.error ||
"Encoding failed"
);
}
const blob =
await response.blob();
const url =
URL.createObjectURL(
blob
);
const link =
document.createElement(
"a"
);
link.href =
url;
link.download =
"encoded.png";
document.body.appendChild(
link
);
link.click();
link.remove();
URL.revokeObjectURL(
url
);
setStatus(
encodeStatus,
"Encoded successfully. Download started.",
"success"
);
} catch (error) {
setStatus(
encodeStatus,
error.message,
"error"
);
} finally {
setLoading(
encodeButton,
false
);
}
}
);
decodeForm.addEventListener(
"submit",
async (event) => {
event.preventDefault();
const image =
decodeImage.files[0];
const password =
decodePassword.value;
decodedMessage
.classList
.add("hidden");
if (!image) {
setStatus(
decodeStatus,
"Please select an encoded PNG.",
"error"
);
return;
}
const form =
new FormData();
form.append(
"image",
image
);
/*
* Password is optional.
*/
if (password) {
form.append(
"password",
password
);
}
setLoading(
decodeButton,
true
);
setStatus(
decodeStatus,
"Decoding..."
);
try {
const response =
await fetch(
"/api/decode",
{
method: "POST",
body: form
}
);
if (!response.ok) {
const data =
await response.json();
throw new Error(
data.error ||
"Decoding failed"
);
}
const contentType =
response.headers.get(
"content-type"
);
/*
* Message payload.
*/
if (
contentType &&
contentType.startsWith(
"application/json"
)
) {
const result =
await response.json();
if (
result.type !==
"message"
) {
throw new Error(
"Unknown decoded payload"
);
}
messageOutput.textContent =
result.message;
decodedMessage
.classList
.remove("hidden");
setStatus(
decodeStatus,
"Message decoded successfully.",
"success"
);
return;
}
/*
* File payload.
*/
const blob =
await response.blob();
const url =
URL.createObjectURL(
blob
);
let filename =
"extracted-file";
const disposition =
response.headers.get(
"content-disposition"
);
if (disposition) {
const match =
disposition.match(
/filename="([^"]+)"/
);
if (match) {
filename =
match[1];
}
}
const link =
document.createElement(
"a"
);
link.href =
url;
link.download =
filename;
document.body.appendChild(
link
);
link.click();
link.remove();
URL.revokeObjectURL(
url
);
setStatus(
decodeStatus,
`File extracted: ${filename}`,
"success"
);
} catch (error) {
setStatus(
decodeStatus,
error.message,
"error"
);
} finally {
setLoading(
decodeButton,
false
);
}
}
);
copyMessage.addEventListener(
"click",
async () => {
try {
await navigator.clipboard.writeText(
messageOutput.textContent
);
const original =
copyMessage.textContent;
copyMessage.textContent =
"Copied!";
setTimeout(
() => {
copyMessage.textContent =
original;
},
1500
);
} catch {
setStatus(
decodeStatus,
"Unable to copy message.",
"error"
);
}
}
);