summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRikki <i@rikki.moe>2025-04-19 15:47:34 +0800
committerRikki <i@rikki.moe>2025-04-19 15:47:34 +0800
commit00806ca12b6b5157c06709eea356e72992b99a9b (patch)
treea3e08f8233e4710d56443279e419ae5c72f87ece
parent8cd9a59ef6e90da8be2a2d1ca22f689e9cf6ce52 (diff)
random speed of oiia cat && tab auto completion
-rw-r--r--scripts/main.js573
1 files changed, 309 insertions, 264 deletions
diff --git a/scripts/main.js b/scripts/main.js
index 13feca7..c371208 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -1,367 +1,412 @@
-// --- Constants ---
-const app = document.querySelector("#app");
-function delay(ms) {
- scrollToBottom();
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-
-// --- Data Definitions ---
-const links = [
- { name: "blog", url: "https://blog.rikki.moe", desc: "Who am i and what do i do." },
- { name: "git", url: "https://git.rikki.moe", desc: "My personal git server." },
- { name: "github", url: "https://github.com/rikkix", desc: "My github page with my projects. Follow me there ;)" },
- { name: "matrix-chat", url: "https://chat.mtf.moe", desc: "My personal matrix chat server." }
-];
-
-const commands = [
- ...links,
- { name: "email", desc: "Get my email address." },
- { name: "help", desc: "Show the list of commands." },
- { name: "clear", desc: "Clear the terminal." }
+// --------- Constants ---------
+const app = document.querySelector('#app');
+
+const LINKS = [
+ { name: 'blog', url: 'https://blog.rikki.moe', desc: 'Who am i and what do i do.' },
+ { name: 'git', url: 'https://git.rikki.moe', desc: 'My personal git server.' },
+ { name: 'github', url: 'https://github.com/rikkix', desc: 'My github page with my projects. Follow me there ;)' },
+ { name: 'matrix-chat', url: 'https://chat.mtf.moe', desc: 'My personal matrix chat server.' }
];
-const sudoRoasts = [
- "You think you're the boss now? Nice try, you're still guest.",
- "Trying to act like root? You can't fool me!",
- "Permission denied, root access is for the cool kids only.",
- "You're not fooling anyone, buddy. You're still a guest.",
- "Is that a root password, or just wishful thinking?",
- "The root can't hear you from down there, guest.",
- "I see you're trying to hack your way into being the boss... nice try!",
- "Rooting for root? Sorry, but you're still stuck as a guest."
+const COMMANDS = [
+ ...LINKS,
+ { name: 'email', desc: 'Get my email address.' },
+ { name: 'help', desc: 'Show the list of commands.' },
+ { name: 'clear', desc: 'Clear the terminal.' }
];
-const shutdownRoasts = [
- "Shutting down? Is your internet even working?",
- "You want to shut down? The system is already shutting down your self-esteem.",
- "Oh, you want to shut me down? Good luck with that.",
- "I’m not shutting down, you’re just pressing random keys.",
- "Trying to shutdown, but all you’ve achieved is pressing your own buttons.",
- "Shut down? Oh, you mean like your attempts at this command?",
- "Shutdown initiated... just kidding, it's still not happening."
-];
+// Built-in handlers not in COMMANDS
+const BUILTIN_COMMANDS = ['su','sudo','shutdown','reboot','echo','df','pwd','cat','top','rm','ls','oiia'];
-const rebootRoasts = [
- "Rebooting? You're just hitting keys for fun, aren't you?",
- "You want to reboot, but your life is already stuck in an endless loop.",
- "Rebooting... Yeah, sure, just like that’ll fix everything.",
- "Your system is rebooting... but not your sense of reality.",
- "Trying to reboot? Maybe reboot your confidence instead.",
- "Rebooting is a nice thought, but I’m still not impressed.",
- "Let me guess, you’re trying to reboot me. Not gonna work!"
+// Consolidated for autocomplete
+const ALL_COMMANDS = [
+ ...COMMANDS.map(cmd => cmd.name),
+ ...BUILTIN_COMMANDS
];
-// --- Event Listeners ---
-app.addEventListener("keypress", async function (event) {
- if (event.key === "Enter") {
- await delay(150);
- await executeInput();
+const ROASTS = {
+ sudo: [
+ "You think you're the boss now? Nice try, you're still guest.",
+ "Trying to act like root? You can't fool me!",
+ "Permission denied, root access is for the cool kids only.",
+ "You're not fooling anyone, buddy. You're still a guest.",
+ "Is that a root password, or just wishful thinking?",
+ "The root can't hear you from down there, guest.",
+ "I see you're trying to hack your way into being the boss... nice try!",
+ "Rooting for root? Sorry, but you're still stuck as a guest."
+ ],
+ shutdown: [
+ "Shutting down? Is your internet even working?",
+ "You want to shut down? The system is already shutting down your self-esteem.",
+ "Oh, you want to shut me down? Good luck with that.",
+ "I’m not shutting down, you’re just pressing random keys.",
+ "Trying to shutdown, but all you’ve achieved is pressing your own buttons.",
+ "Shut down? Oh, you mean like your attempts at this command?",
+ "Shutdown initiated... just kidding, it's still not happening."
+ ],
+ reboot: [
+ "Rebooting? You're just hitting keys for fun, aren't you?",
+ "You want to reboot, but your life is already stuck in an endless loop.",
+ "Rebooting... Yeah, sure, just like that’ll fix everything.",
+ "Your system is rebooting... but not your sense of reality.",
+ "Trying to reboot? Maybe reboot your confidence instead.",
+ "Rebooting is a nice thought, but I’m still not impressed.",
+ "Let me guess, you’re trying to reboot me. Not gonna work!"
+ ]
+};
+
+// --------- Utilities ---------
+const delay = ms => {
+ scrollToBottom();
+ return new Promise(resolve => setTimeout(resolve, ms));
+};
+
+const randomElement = arr => arr[Math.floor(Math.random() * arr.length)];
+
+// --------- Audio Playback ---------
+async function playSound(file, playSpeed = 1, minTime = null) {
+ const ctx = new AudioContext();
+ try {
+ const response = await fetch(file);
+ const arrayBuffer = await response.arrayBuffer();
+ const buffer = await ctx.decodeAudioData(arrayBuffer);
+
+ const singleDur = buffer.duration / playSpeed;
+ const targetTime = minTime != null ? minTime : singleDur;
+ const loops = Math.ceil(targetTime / singleDur);
+ const totalDur = loops * singleDur;
+ const startTime = ctx.currentTime;
+ const stopTime = startTime + totalDur;
+ const fadeTime = Math.min(0.1, totalDur / 2);
+
+ const src = ctx.createBufferSource();
+ src.buffer = buffer;
+ src.playbackRate.value = playSpeed;
+ src.loop = true;
+
+ const gain = ctx.createGain();
+ gain.gain.setValueAtTime(1, startTime);
+ gain.gain.setValueAtTime(1, stopTime - fadeTime);
+ gain.gain.linearRampToValueAtTime(0, stopTime);
+
+ src.connect(gain).connect(ctx.destination);
+ src.start(startTime);
+ src.stop(stopTime);
+
+ return new Promise(resolve => {
+ setTimeout(() => {
+ ctx.close();
+ resolve(totalDur);
+ }, totalDur * 1000 + 20);
+ });
+ } catch (err) {
+ console.error('Error loading audio file:', err);
+ throw err;
}
-});
-
-app.addEventListener("click", function () {
- const input = document.querySelector(".command-input");
- input.focus();
-});
-
-// --- Terminal Initialization ---
-async function open_terminal() {
- createText("Welcome to Rikki's terminal");
- await delay(700);
- createText("Type 'help' to see the list of commands.");
- await delay(500);
- new_line();
- executeInput("help");
}
-// --- Utility Functions ---
-function randomElement(array) {
- return array[Math.floor(Math.random() * array.length)];
+// --------- DOM & Autocomplete Helpers ---------
+function scrollToBottom() {
+ app.scrollTop = app.scrollHeight;
}
-function playSound(file) {
- const audio = new Audio(file);
- audio.play();
+function clearSuggestions() {
+ document.querySelectorAll('.autocomplete-suggestion').forEach(el => el.remove());
}
-function scrollToBottom() {
- const scrollHeight = app.scrollHeight;
- app.scrollTop = scrollHeight;
+function createSuggestion(text) {
+ const p = document.createElement('p');
+ p.className = 'autocomplete-suggestion';
+ p.innerText = text;
+ app.appendChild(p);
+ scrollToBottom();
}
-function new_line() {
- const p = document.createElement("p");
- const span1 = document.createElement("span");
- p.setAttribute("class", "path");
- p.textContent = "guest@rikki";
- span1.textContent = " ~";
- p.appendChild(span1);
+function createPrompt() {
+ const p = document.createElement('p');
+ p.className = 'path';
+ p.textContent = 'guest@rikki';
+ const span = document.createElement('span');
+ span.textContent = ' ~';
+ p.appendChild(span);
app.appendChild(p);
- const div = document.createElement("div");
- div.setAttribute("class", "type");
- const i = document.createElement("i");
- i.setAttribute("class", "fas fa-angle-right icone");
-
- // Create a hidden input field to prevent autocomplete
- const hiddenInput = document.createElement("input");
- hiddenInput.setAttribute("style", "display: none; visibility: hidden;");
+ const div = document.createElement('div');
+ div.className = 'type';
+ const icon = document.createElement('i');
+ icon.className = 'fas fa-angle-right icone';
- const input = document.createElement("input");
- input.setAttribute("class", "command-input");
+ const hidden = document.createElement('input');
+ hidden.style = 'display:none; visibility:hidden;';
+ const input = document.createElement('input');
+ input.className = 'command-input';
- div.appendChild(i);
- div.appendChild(hiddenInput);
- div.appendChild(input);
+ div.append(icon, hidden, input);
app.appendChild(div);
input.focus();
}
-function removeInput() {
- const div = document.querySelector(".type");
- app.removeChild(div);
+function removePrompt() {
+ const div = document.querySelector('.type');
+ if (div) app.removeChild(div);
+}
+
+function createText(text) {
+ const p = document.createElement('p');
+ p.innerHTML = text;
+ app.appendChild(p);
+ scrollToBottom();
+}
+
+function createErrorText(text) {
+ const p = document.createElement('p');
+ p.textContent = text;
+ app.appendChild(p);
+ scrollToBottom();
}
-// --- Command Handlers ---
-async function executeInput(command) {
- var value = command ? command : document.querySelector(".command-input").value;
- removeInput();
+function printCommand(name, desc) {
+ const p = document.createElement('p');
+ p.className = 'code';
+
+ const cmd = document.createElement('a');
+ cmd.className = 'command';
+ cmd.textContent = name;
+ cmd.style.cursor = 'pointer';
+ cmd.addEventListener('click', async () => {
+ clearSuggestions();
+ await delay(150);
+ await executeInput(name);
+ });
- value = value.trim();
- const args = value.split(" ");
- const commandName = args[0];
+ const span = document.createElement('span');
+ span.className = 'text';
+ span.textContent = desc;
- await showOutput(commandName, args);
- new_line();
+ p.append(cmd, document.createElement('br'), span);
+ app.appendChild(p);
+ scrollToBottom();
+}
+
+// --------- Command Handlers ---------
+async function executeInput(provided) {
+ clearSuggestions();
+ const raw = provided ?? document.querySelector('.command-input').value;
+ removePrompt();
+ const value = raw.trim();
+ const [command, ...args] = value.split(' ');
+ await showOutput(command, args);
+ createPrompt();
}
async function showOutput(command, args) {
switch (command) {
- case "help":
+ case 'help':
trueValue(command);
help();
break;
- case "clear":
+ case 'clear':
clearTerminal();
break;
- case "su":
- case "sudo":
+ case 'su':
+ case 'sudo':
trueValue(command);
- createText("Upgrading to root...");
+ createText('Upgrading to root...');
await delay(400);
- createText(randomElement(sudoRoasts));
+ createText(randomElement(ROASTS.sudo));
break;
- case "shutdown":
+ case 'shutdown':
trueValue(command);
- createText("Shutting down...");
+ createText('Shutting down...');
await delay(400);
- createText(randomElement(shutdownRoasts));
+ createText(randomElement(ROASTS.shutdown));
break;
- case "reboot":
+ case 'reboot':
trueValue(command);
- createText("Rebooting...");
+ createText('Rebooting...');
await delay(400);
- createText(randomElement(rebootRoasts));
+ createText(randomElement(ROASTS.reboot));
break;
- case "echo":
- const textToEcho = args.slice(1).join(" ");
+ case 'echo':
trueValue(command);
- createText(`Echo: ${textToEcho}`);
+ createText(`Echo: ${args.join(' ')}`);
break;
- case "df":
+ case 'df':
trueValue(command);
- createText("Filesystem 1K-blocks Used Available Use% Mounted on");
- createText("/dev/sda1 10240000 5120000 5120000 50% /");
- createText("/dev/sdb1 20480000 20400000 800000 100% /mnt/usb");
- createText("Disk space low? Not in my world.");
+ ['Filesystem 1K-blocks Used Available Use% Mounted on',
+ '/dev/sda1 10240000 5120000 5120000 50% /',
+ '/dev/sdb1 20480000 20400000 800000 100% /mnt/usb',
+ 'Disk space low? Not in my world.'].forEach(createText);
break;
- case "pwd":
+ case 'pwd':
trueValue(command);
- createText("/home/guest/No_Way_Out");
+ createText('/home/guest/No_Way_Out');
break;
- case "cat":
- const fileName = args[1] || 'undefined.txt';
+ case 'cat': {
+ const file = args[0] || 'undefined.txt';
trueValue(command);
- if (fileName === 'undefined.txt') {
- createText("cat: undefined.txt: No such file or directory");
+ if (file === 'undefined.txt') {
+ createText(`cat: undefined.txt: No such file or directory`);
} else {
- createText(`Reading contents of ${fileName}...`);
+ createText(`Reading contents of ${file}...`);
await delay(500);
- createText("Error: This file is too mysterious to read.");
+ createText('Error: This file is too mysterious to read.');
}
break;
- case "top":
+ }
+ case 'top':
trueValue(command);
- createText("Processes running...\n");
+ createText('Processes running...');
await delay(500);
- createText("PID USER %CPU %MEM COMMAND");
- createText("1234 root 0.2 1.0 /bin/bash");
- createText("5678 guest 0.5 0.7 /usr/bin/firefox");
- createText("9876 guest 99.9 99.9 /usr/bin/playing_hokey_pokey.sh");
- createText("Process hogging all your memory: You.");
+ ['PID USER %CPU %MEM COMMAND',
+ '1234 root 0.2 1.0 /bin/bash',
+ '5678 guest 0.5 0.7 /usr/bin/firefox',
+ '9876 guest 99.9 99.9 /usr/bin/playing_hokey_pokey.sh',
+ 'Process hogging all your memory: You.'].forEach(createText);
break;
- case "rm":
- const fileToRemove = args[1] || "important_file.txt";
+ case 'rm': {
+ const target = args[0] || 'important_file.txt';
trueValue(command);
- if (fileToRemove === "important_file.txt") {
- createText(`Are you sure you want to delete '${fileToRemove}'? [y/N]`);
+ if (target === 'important_file.txt') {
+ createText(`Are you sure you want to delete '${target}'? [y/N]`);
await delay(500);
createText("Error: You can't delete this file. It's too important!");
} else {
- createText(`Deleted '${fileToRemove}'... (Just kidding, it's still there.)`);
+ createText(`Deleted '${target}'... (Just kidding, it's still there.)`);
}
break;
- case "ls":
+ }
+ case 'ls':
trueValue(command);
- const files = [
- "Desktop",
- "Documents",
- "Downloads",
- "src/",
- "memes/",
- "very_secret_file.txt",
- "super_important_task_list.docx",
- "you_dont_want_to_know.mp3",
- "this_is_a_test_file.txt"
- ];
- files.forEach(file => createText(file));
+ ['Desktop','Documents','Downloads','src/','memes/','very_secret_file.txt',
+ 'super_important_task_list.docx','you_dont_want_to_know.mp3','this_is_a_test_file.txt']
+ .forEach(createText);
break;
- case "oiia":
+ case 'oiia':
trueValue(command);
- playSound('/static/oiia-short.mp3'); // Assume you have a sound file for cheering
- createText("Oiia! Oiia! Oiia!");
- await delay(1000);
- createText("Oiia! Oiia! Oiia!");
- await delay(1000);
+ createText('Oiia is coming...');
+ const iv = setInterval(() => createText('Oiia! Oiia! Oiia!'), 700);
+ await playSound('/static/oiia-short.mp3', Math.random() + 1, 2);
+ clearInterval(iv);
break;
- case "email":
+ case 'email':
trueValue(command);
- createText("Getting email address...");
- // record the time taken to get the email address
- const startTime = performance.now();
+ createText('Getting email address...');
+ const start = performance.now();
const email = await getEmailAddress();
- const endTime = performance.now();
- const timeTaken = endTime - startTime;
- createText(`The email address is: <a href="mailto:${email}" target="_blank">${email}</a> (${timeTaken.toFixed(2)}ms)`);
+ const duration = performance.now() - start;
+ createText(`The email address is: <a href=\"mailto:${email}\" target=\"_blank\">${email}</a> (${duration.toFixed(2)}ms)`);
break;
default:
- handleLinkCommands(command);
+ await handleLinkCommands(command);
break;
}
}
function help() {
- commands.forEach(command => printCommand(command.name, command.desc));
+ COMMANDS.forEach(cmd => printCommand(cmd.name, cmd.desc));
}
function clearTerminal() {
- document.querySelectorAll("p, section").forEach(e => e.parentNode.removeChild(e));
+ document.querySelectorAll('p, section').forEach(e => e.remove());
}
-async function handleLinkCommands(command) {
- for (let link of links) {
- if (command === link.name) {
- trueValue(command);
- createText(`Opening ${link.name} (<a href="${link.url}" target="_blank">${link.url}</a>)`);
- scrollToBottom();
- await delay(400);
- window.open(link.url, "_blank");
- return;
- }
+async function handleLinkCommands(cmd) {
+ const link = LINKS.find(l => l.name === cmd);
+ if (link) {
+ trueValue(cmd);
+ createText(`Opening ${cmd} (<a href=\"${link.url}\" target=\"_blank\">${link.url}</a>)`);
+ await delay(400);
+ window.open(link.url, '_blank');
+ } else {
+ falseValue(cmd);
+ createErrorText(`command not found: ${cmd}`);
}
- falseValue(command);
- createErrorText(`command not found: ${command}`);
}
-// --- Command Output Helpers ---
-function trueValue(value) {
- const div = document.createElement("section");
- div.setAttribute("class", "type2");
- const i = document.createElement("i");
- i.setAttribute("class", "fas fa-angle-right icone");
- const mensagem = document.createElement("h2");
- mensagem.setAttribute("class", "sucess");
- mensagem.textContent = `${value}`;
- div.appendChild(i);
- div.appendChild(mensagem);
+// --------- Output Helpers ---------
+const createOutput = (val, success = true) => {
+ const div = document.createElement('section');
+ div.className = 'type2';
+ const icon = document.createElement('i');
+ icon.className = `fas fa-angle-right icone${success ? '' : ' error'}`;
+ const msg = document.createElement('h2');
+ msg.className = success ? 'sucess' : 'error';
+ msg.textContent = val;
+ div.append(icon, msg);
app.appendChild(div);
-}
+ scrollToBottom();
+};
-function falseValue(value) {
- const div = document.createElement("section");
- div.setAttribute("class", "type2");
- const i = document.createElement("i");
- i.setAttribute("class", "fas fa-angle-right icone error");
- const mensagem = document.createElement("h2");
- mensagem.setAttribute("class", "error");
- mensagem.textContent = `${value}`;
- div.appendChild(i);
- div.appendChild(mensagem);
- app.appendChild(div);
-}
+function trueValue(val) { createOutput(val, true); }
+function falseValue(val) { createOutput(val, false); }
-function createText(text) {
- const p = document.createElement("p");
- p.innerHTML = text;
- app.appendChild(p);
+// --------- Email Hashing ---------
+async function getEmailAddress() {
+ const partHash = '2f5ab71af6dfd2f3c5444a2d690fbbb880ee87f9';
+ const domain = 'rikki.moe';
+ const chars = 'abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()';
+
+ for (const a of chars) {
+ for (const b of chars) {
+ const hash = await hashString(a + b);
+ if (hash === partHash) return `${a}${b}${domain}`;
+ }
+ }
+ return null;
}
-function printCommand(command, desc) {
- const p = document.createElement("p");
- p.setAttribute("class", "code");
- const cmdEle = document.createElement("a");
- cmdEle.setAttribute("class", "command");
- cmdEle.innerText = command;
+async function hashString(str) {
+ const data = new TextEncoder().encode(str);
+ const buf = await crypto.subtle.digest('SHA-1', data);
+ return Array.from(new Uint8Array(buf))
+ .map(b => b.toString(16).padStart(2, '0'))
+ .join('');
+}
- cmdEle.addEventListener("click", async function () {
- await delay(150);
- await executeInput(command);
+// --------- Initialization ---------
+function setupEventListeners() {
+ app.addEventListener('keypress', async e => {
+ if (e.key === 'Enter') {
+ clearSuggestions();
+ await delay(150);
+ await executeInput();
+ }
});
- cmdEle.style.cursor = "pointer";
- const descEle = document.createElement("span");
- descEle.setAttribute("class", "text");
- descEle.innerText = desc;
-
- p.appendChild(cmdEle);
- p.appendChild(document.createElement("br"));
- p.appendChild(descEle);
-
- app.appendChild(p);
-}
-
-function createErrorText(text) {
- const p = document.createElement("p");
- p.innerText = text;
- app.appendChild(p);
-}
+ app.addEventListener('click', () => {
+ const input = document.querySelector('.command-input');
+ input?.focus();
+ });
-// --- Email Fetching ---
-async function getEmailAddress() {
- const partHash = "2f5ab71af6dfd2f3c5444a2d690fbbb880ee87f9";
- const remainingPart = "rikki.moe";
- const chars = 'abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()';
-
- for (let i = 0; i < chars.length; i++) {
- for (let j = 0; j < chars.length; j++) {
- const part = chars[i] + chars[j];
- const hash = await String2Hash(part);
- if (hash === partHash) {
- return part + remainingPart;
+ // Tab autocomplete
+ document.addEventListener('keydown', e => {
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ const input = document.querySelector('.command-input');
+ if (!input) return;
+ clearSuggestions();
+ const value = input.value;
+ const matches = ALL_COMMANDS.filter(cmd => cmd.startsWith(value));
+ if (matches.length === 1) {
+ input.value = matches[0] + ' ';
+ } else if (matches.length > 1) {
+ createSuggestion(matches.join(' '));
}
}
- }
-
- return null;
+ });
}
-async function String2Hash(username) {
- const encoder = new TextEncoder();
- const data = encoder.encode(username);
- const hashBuffer = await crypto.subtle.digest('SHA-1', data);
- return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
+async function openTerminal() {
+ createText("Welcome to Rikki's terminal");
+ await delay(700);
+ createText("Type 'help' to see the list of commands.");
+ await delay(500);
+ createPrompt();
+ executeInput('help');
}
-// --- Terminal Start ---
-open_terminal(); \ No newline at end of file
+// Start
+setupEventListeners();
+openTerminal();