function _internal_joinPaths(path1, path2) { if (path2[0] == "/") { return _internal_sanitizePath(path2); } else if (path2[0] == "~" && (path2.length == 1 || path2[1] == "/")) { return _internal_sanitizePath(HOME_DIR + path2.slice(1)); } else { return _internal_sanitizePath(path1 + "/" + path2); } } function _internal_sanitizePath(path) { while (path.includes("//")) { path = path.replaceAll("//", "/"); } while (path[path.length - 1] == "/") { path = path.slice(0, path.length - 1); } let newPath = []; for (const dir of path.split("/")) { if (dir == ".") { } else if (dir == "..") { if (newPath.length) { newPath = newPath.slice(0, newPath.length - 1); } } else if (dir) { newPath.push(dir); } } return "/" + newPath.join("/"); } function _internal_getFile(path) { function getFile_recursive(files, path) { console.log(files, path); let file = files[path[0]]; if (!file) { return null; } if (path.length == 1) { return file; } if (file.type == "file") { return null; } return getFile_recursive(file.files, path.slice(1)); } if (path == "/") { return { type: "directory", name: "/", files: FILESYSTEM }; } let dirs = path.split("/"); if (dirs[0] == "") { dirs = dirs.slice(1); } return getFile_recursive(FILESYSTEM, dirs); } function _internal_stringifyPath(path) { if (path.startsWith(HOME_DIR)) { return path.replace(HOME_DIR, "~"); } return path; } function _internal_getPS1(winInfo) { return `${winInfo.ps1Override || "trinkey@website"}:${_internal_stringifyPath(winInfo.PWD)}$ `; } function _internal_getFlags(command) { let flags = []; let newCommand = []; for (const thing of command.split(" ")) { if (thing.startsWith("-")) { flags.push(...thing.slice(1).split("")); } else { newCommand.push(thing); } } return { flags: flags, removed: newCommand.join(" ") }; } function _internal_fileSize(size) { let suffix = ""; let amount = size; const sizes = [ { suffix: "K", amount: 1024, threshold: 10000 }, { suffix: "M", amount: Math.pow(1024, 2), threshold: 1000000 } ]; for (const info of sizes) { if (size > info.threshold) { suffix = info.suffix; amount = Math.round(size / info.amount * 10) / 10; if (String(amount).length > 3) { amount = Math.round(info.amount); } } } return `${amount}${suffix}`.padStart(4, " "); } function cat(path, windowID) { if (!path) { return "
cat: You must specify a file
"; } path = _internal_joinPaths(windowInformation[windowID].PWD || HOME_DIR, path); let file = _internal_getFile(path); if (!file) { return `
cat: ${escapeHTML(path)}: No such file or directory
`; } else if (file.type == "directory") { return `
cat: ${escapeHTML(path)}: Is a directory
`; } else { return file.content; } } function cd(path, windowID) { if (!path) { windowInformation[windowID].PWD = HOME_DIR; return ""; } let newPWD = _internal_joinPaths(windowInformation[windowID].PWD, path); let newFileObj = _internal_getFile(newPWD); if (newFileObj === null) { return `
cd: ${escapeHTML(path)}: No such file or directory
`; } else if (newFileObj.type == "file") { return `
cd: ${escapeHTML(path)}: Not a directory
`; } windowInformation[windowID].PWD = newPWD; return ""; } function clear(path, windowID) { WINDOWS[windowID].element.querySelector(".window").innerHTML = ""; return ""; } function exit(path, windowID) { setTimeout(() => { WINDOWS[windowID].element.querySelector(".close").click(); }, 1); return ""; } function help(path, windowID) { return helpText; } function ls(path, windowID) { let { flags, removed } = _internal_getFlags(path); path = _internal_joinPaths(windowInformation[windowID].PWD || HOME_DIR, removed); let file = _internal_getFile(path); let files; if (!file) { return `
ls: ${escapeHTML(path)}: No such file or directory
`; } else if (file.type == "file") { files = { [file.name]: file }; } else { files = file.files; } let directories = []; let hidden = flags.includes("a") || flags.includes("A"); let long = flags.includes("l"); let out = flags.includes("a") ? (long ? "
drwxrwxr-x trinkey trinkey 4096 .
drwxr-xr-x trinkey trinkey 4096 ..
" : ".  ..  ") : ""; for (const file of Object.keys(files).sort((a, b) => (({ true: 1, false: -1 })[String(flags.includes("r") ? a < b : a > b)]))) { if (file.startsWith(".") && !hidden) { continue; } let fObj = files[file]; if (long) { if (fObj.type == "directory") { directories.push(fObj); out += `
drwxrwxr-x trinkey trinkey 4096 ${escapeHTML(file)}
`; } else { out += `
-rw-rw-r-- trinkey trinkey ${_internal_fileSize(fObj.content.length).replaceAll(" ", " ")} ${escapeHTML(file)}
`; } } else { if (fObj.type == "directory") { directories.push(fObj); out += `${escapeHTML(file)}  `; } else { out += `${escapeHTML(file)}  `; } } } if (!long) { out = out.slice(0, out.length - 7); } if (flags.includes("R")) { for (const dir of directories) { out += `

${escapeHTML(_internal_stringifyPath(_internal_joinPaths(path, dir.name)))}:
${ls(`${_internal_joinPaths(path, dir.name)} -${flags.join("")}`, windowID)}`; } } return out; } function _internal_set_ps1(args, windowID) { windowInformation[windowID].ps1Override = args.split("|")[0]; WINDOWS[windowID].element.querySelector(".window [data-type-area]").innerHTML = args.split("|")[1]; return ""; } function _internal_neofetch(args, windowID) { WINDOWS[windowID].element.querySelector(".window [data-type-area]").innerHTML = "neofetch"; return _internal_neofetchOutputs[args]; } const _internal_commands = { cat: cat, cd: cd, clear: clear, help: help, ls: ls, exit: exit, _internal_set_ps1: _internal_set_ps1, _internal_neofetch: _internal_neofetch }; const _internal_defaultFiles = { about: `
hi there! i'm trinkey!
--------------------
i'm a silly little kitty cat who lives in the usa.
i'm trans (she/her, they/them and it/its are also fine).
i'm not actively in a relationship, however i'm also not looking to get into one either.
--------------------
i like to code stuff (mostly websites)! some of my programs can be found on the projects page. i know a few languages, those being python, javascript/typescript, html/css (if you count those), and a little bit of java.
--------------------
well, that's about it! i hope you like my website!
`, socials: `
- fedi - @trinkey@trinkey.com (or @trinkey@is.trinkey.com)
- forgejo - trinkey
- github - trinkey
- git.gay - trinkey (inactive)
- smiggins - trinkey
- signal - @trinkey.01
- email - trinkey [at] proton [dot] me
- youtube - @trinkey (inactive)
`, buttons: `
my button: (click to copy html)
trinkey's 88x31. image of her cat on the right with the word 'trinkey' taking up the rest of the button.
--------------------
cool people:
notfire.cc a non-spinning demigirl blobcat angled slightly with a black border to the left of "Micro" kopper's button Sneexy unnick
`, testimonials: `
"warning: this user is trinkey"
- bread

"This user is only slightly crazy once was I. 10/10 would recommend"
- subroutine

"the f slur but repeated 36 times"
- corn fields seventy four
`, webrings: `
catppuccin webring
fediring
`, projects: `
projects - the things i made
- smiggins (website) - a social media platform i made
- this website - check out the code
- dotindex (pypi) - a python library that lets you access dicts using the dot notation (dict.key) instead of whatever python does (dict["key"])
- infopage (website) - my very own pronouns.page clone
- tSuite (website) - a collection of services that are all interconnected

i'll likely add more in the future, these are just the ones i'm most proud of at the moment.
`, directory: `
there's a lot that goes into this website. here are some links for your usage to help you navigate this hellhole

trinkey.com:
this is where you are right now

*akkofe.trinkey.com:
the frontend i use for fedi

auth.trinkey.com:
authentication manager for tSuite

blog.trinkey.com:
tBlog, from tSuite

everyone.trinkey.com:
frontend to a fedi bot that anyone can post to (@everyonebot@is.trinkey.com)

*git.trinkey.com:
holds some of my git projects (older ones on github or git.gay)

*is.trinkey.com:
hosts iceshrimp.net, which is the fedi backend i use

message.trinkey.com:
tMessage, from tSuite

music.trinkey.com:
has some music. i haven't actually updated the site in a while but i've been meaning to do rewrite it at some point. 100% legal i pinky promise

smiggins.trinkey.com:
official jerimiah smiggins instance, that being my own social media platform

(asterisk (*) means i haven't written the code for it)

` }; const _internal_neofetchOutputs = { desktop: `
             ...-:::::-...                 trinkey@desktop
          .-MMMMMMMMMMMMMMM-.              ---------------
      .-MMMM\`..-:::::::-..\`MMMM-.          OS: Linux Mint 21.3 x86_64
    .:MMMM.:MMMMMMMMMMMMMMM:.MMMM:.        Host: MS-7E27 1.0
   -MMM-M---MMMMMMMMMMMMMMMMMMM.MMM-       Resolution: 1680x1050, 2560x1440
 \`:MMM:MM\`  :MMMM:....::-...-MMMM:MMM:\`    DE: Cinnamon 6.0.4
 :MMM:MMM\`  :MM:\`  \`\`    \`\`  \`:MMM:MMM:    WM: Mutter (Muffin)
.MMM.MMMM\`  :MM.  -MM.  .MM-  \`MMMM.MMM.   CPU: AMD Ryzen 9 7950X (32) @ 5.881GHz
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM-MMM:   GPU: AMD ATI 03:00.0 Device 747e
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM:MMM:   GPU: AMD ATI 11:00.0 Device 164e
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM-MMM:   Memory: 1MiB / 127901MiB
.MMM.MMMM\`  :MM:--:MM:--:MM:  \`MMMM.MMM.
 :MMM:MMM-  \`-MMMMMMMMMMMM-\`  -MMM-MMM:
  :MMM:MMM:\`                \`:MMM:MMM:
   .MMM.MMMM:--------------:MMMM.MMM.
     '-MMMM.-MMMMMMMMMMMMMMM-.MMMM-'
       '.-MMMM\`\`--:::::--\`\`MMMM-.'
            '-MMMMMMMMMMMMM-'
               \`\`-:::::-\`\`
`, laptop: `
             ...-:::::-...                 trinkey@laptop
          .-MMMMMMMMMMMMMMM-.              ---------------
      .-MMMM\`..-:::::::-..\`MMMM-.          OS: Linux Mint 21.2 x86_64
    .:MMMM.:MMMMMMMMMMMMMMM:.MMMM:.        Host: Dell G15 5510
   -MMM-M---MMMMMMMMMMMMMMMMMMM.MMM-       Resolution: 2560x1440, 1920x1080, 1680x1050
 \`:MMM:MM\`  :MMMM:....::-...-MMMM:MMM:\`    DE: Cinnamon 5.8.4
 :MMM:MMM\`  :MM:\`  \`\`    \`\`  \`:MMM:MMM:    WM: Mutter (Muffin)
.MMM.MMMM\`  :MM.  -MM.  .MM-  \`MMMM.MMM.   CPU: Intel i5-10500H (12) @ 4.500GHz
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM-MMM:   GPU: NVIDIA GeForce RTX 3050 Ti Mobile
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM:MMM:   GPU: Intel CometLake-H GT2 [UHD Graphics]
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM-MMM:   Memory: 2001MiB / 15765MiB
.MMM.MMMM\`  :MM:--:MM:--:MM:  \`MMMM.MMM.
 :MMM:MMM-  \`-MMMMMMMMMMMM-\`  -MMM-MMM:
  :MMM:MMM:\`                \`:MMM:MMM:
   .MMM.MMMM:--------------:MMMM.MMM.
     '-MMMM.-MMMMMMMMMMMMMMM-.MMMM-'
       '.-MMMM\`\`--:::::--\`\`MMMM-.'
            '-MMMMMMMMMMMMM-'
               \`\`-:::::-\`\`
`, server: `
             ...-:::::-...                 trinkey@server
          .-MMMMMMMMMMMMMMM-.              ---------------
      .-MMMM\`..-:::::::-..\`MMMM-.          OS: Linux Mint 21.2 x86_64
    .:MMMM.:MMMMMMMMMMMMMMM:.MMMM:.        Host: Macmini7,1 1.0
   -MMM-M---MMMMMMMMMMMMMMMMMMM.MMM-       CPU: Intel i5-4278U (4) @ 3.100GHz
 \`:MMM:MM\`  :MMMM:....::-...-MMMM:MMM:\`    GPU: Intel Haswell-ULT
 :MMM:MMM\`  :MM:\`  \`\`    \`\`  \`:MMM:MMM:    Memory: 9011MiB / 15866MiB
.MMM.MMMM\`  :MM.  -MM.  .MM-  \`MMMM.MMM.
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM-MMM:
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM:MMM:
:MMM:MMMM\`  :MM.  -MM-  .MM:  \`MMMM-MMM:
.MMM.MMMM\`  :MM:--:MM:--:MM:  \`MMMM.MMM.
 :MMM:MMM-  \`-MMMMMMMMMMMM-\`  -MMM-MMM:
  :MMM:MMM:\`                \`:MMM:MMM:
   .MMM.MMMM:--------------:MMMM.MMM.
     '-MMMM.-MMMMMMMMMMMMMMM-.MMMM-'
       '.-MMMM\`\`--:::::--\`\`MMMM-.'
            '-MMMMMMMMMMMMM-'
               \`\`-:::::-\`\`
` }; const helpText = `
 -=== tSh help ===-
--------------------
-= cat =-
Displays the contents of a file.
-= cd =-
Changes the working directory.
-= clear =-
Clears the terminal output.
-= help =-
Shows this help menu.
-= ls =-
Lists all files in a directory.
  -l - displays more information about each file
  -a - displays all files
  -A - displays all files except implied . and ..
  -r - reverses the order of the files
  -R - recurse through all subdirectories
-= exit =-
Closes the terminal.
`; const HOME_DIR = "/home/trinkey"; let FILESYSTEM = { home: { type: "directory", name: "home", files: { trinkey: { type: "directory", name: "trinkey", files: { people: { type: "directory", name: "people", files: { "88x31.txt": { type: "file", name: "88x31.txt", content: _internal_defaultFiles.buttons }, "testimonials.txt": { type: "file", name: "testimonials.txt", content: _internal_defaultFiles.testimonials }, "webrings.txt": { type: "file", name: "webrings.txt", content: _internal_defaultFiles.webrings } } }, "about-me.txt": { type: "file", name: "about-me.txt", content: _internal_defaultFiles.about }, "socials.txt": { type: "file", name: "socials.txt", content: _internal_defaultFiles.socials }, "projects.txt": { type: "file", name: "projects.txt", content: _internal_defaultFiles.projects }, "subdomains.txt": { type: "file", name: "subdomains.txt", content: _internal_defaultFiles.directory } } } } }, bin: { type: "directory", name: "bin", files: { cat: { type: "file", name: "cat", content: "
function cat(file: string): string { ... }
" }, cd: { type: "file", name: "cd", content: "
function cd(directory: string): void { ... }
" }, clear: { type: "file", name: "clear", content: "
function clear(): void { ... }
" }, help: { type: "file", name: "help", content: "
function help(): string { ... }
" }, ls: { type: "file", name: "ls", content: "
function ls(directory: string): string { ... }
" }, neofetch: { type: "file", name: "neofetch", content: "
function neofetch(): string { ... }
" } } }, ".secret-file": { type: "file", name: ".secret-file", content: "
meow :3
" } }; let windowInformation = {}; function commandManager(windowID, command) { if (!windowInformation[windowID]) { windowInformation[windowID] = { PWD: HOME_DIR }; } let out; if (_internal_commands[command.split(" ")[0]]) { out = _internal_commands[command.split(" ")[0]](command.split(" ").slice(1).join(" ").trim(), windowID); } else if (command == "") { out = ""; } else { out = `
Unknown command '${escapeHTML(command.split(" ")[0])}'.
Type 'help' for a list of commands
`; } let el = document.createElement("div"); el.innerHTML = out; WINDOWS[windowID].element.querySelector(".window").append(el); let dTE = WINDOWS[windowID].element.querySelector("[data-type-area]"); if (dTE) { dTE.removeAttribute("data-type-area"); if (dTE.querySelector("i.cursor")) { dTE.querySelector("i.cursor").remove(); } else if (dTE.querySelector(".cursor")) { dTE.querySelector(".cursor").classList.remove("cursor"); } } WINDOWS[windowID].element.querySelector(".window-header-title").innerText = `${_internal_stringifyPath(windowInformation[windowID].PWD)} - tSh`; let ps1 = document.createElement("div"); ps1.innerHTML = _internal_getPS1(windowInformation[windowID]); let typeArea = document.createElement("span"); typeArea.dataset.typeArea = ""; typeArea.innerHTML = " "; ps1.append(typeArea); WINDOWS[windowID].element.querySelector(".window").append(ps1); }