Init
This commit is contained in:
commit
b96ed99cc4
16 changed files with 620 additions and 0 deletions
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
README.md
Normal file
15
README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# bun-ssh-portfolio
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.5. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
62
bun.lock
Normal file
62
bun.lock
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bun-ssh-portfolio",
|
||||
"dependencies": {
|
||||
"@types/ssh2": "^1.15.4",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"ssh2": "^1.16.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="],
|
||||
|
||||
"@types/node": ["@types/node@18.19.80", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ=="],
|
||||
|
||||
"@types/ssh2": ["@types/ssh2@1.15.4", "", { "dependencies": { "@types/node": "^18.11.18" } }, "sha512-9JTQgVBWSgq6mAen6PVnrAmty1lqgCMvpfN+1Ck5WRUsyMYPa6qd50/vMJ0y1zkGpOEgLzm8m8Dx/Y5vRouLaA=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
|
||||
|
||||
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
|
||||
|
||||
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
|
||||
|
||||
"buildcheck": ["buildcheck@0.0.6", "", {}, "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="],
|
||||
|
||||
"cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
|
||||
|
||||
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
||||
|
||||
"nan": ["nan@2.22.2", "", {}, "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"ssh2": ["ssh2@1.16.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.20.0" } }, "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg=="],
|
||||
|
||||
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
|
||||
|
||||
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
|
||||
|
||||
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/ws/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
|
||||
|
||||
"@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
}
|
||||
}
|
||||
33
contact/ContactForm.ts
Normal file
33
contact/ContactForm.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { newLine } from "../front";
|
||||
|
||||
interface ContactFormProps {
|
||||
columns: number;
|
||||
rows: number;
|
||||
contactFormData: {
|
||||
name: string;
|
||||
email: string;
|
||||
message: string;
|
||||
};
|
||||
contactFormSelectedField: "name" | "email" | "message" | "submit";
|
||||
}
|
||||
|
||||
const ContactForm = ({ rows, columns, contactFormData, contactFormSelectedField }: ContactFormProps) => {
|
||||
return (
|
||||
`${newLine}` +
|
||||
`Send your message to Jokin here.${newLine}` +
|
||||
`You can switch between fields with the tab key.${newLine}` +
|
||||
`Then, send the message by selecting the "Submit" button and hitting enter.${newLine}${newLine}` +
|
||||
` ${contactFormSelectedField == "name" ? "\x1b[7mYour name\x1b[0m" : "Your name"}: ${contactFormData.name}${
|
||||
contactFormSelectedField == "name" ? "|" : ""
|
||||
}${newLine}` +
|
||||
` ${contactFormSelectedField == "email" ? "\x1b[7mYour email\x1b[0m" : "Your email"}: ${
|
||||
contactFormData.email
|
||||
}${contactFormSelectedField == "email" ? "|" : ""}${newLine}` +
|
||||
` ${contactFormSelectedField == "message" ? "\x1b[7mYour message\x1b[0m" : "Your message"}:${newLine} ${
|
||||
contactFormData.message
|
||||
}${contactFormSelectedField == "message" ? "|" : ""}${newLine}` +
|
||||
`${newLine}` +
|
||||
` ${contactFormSelectedField == "submit" ? "\x1b[7mSubmit\x1b[0m" : "Submit"}`
|
||||
);
|
||||
};
|
||||
export default ContactForm;
|
||||
20
front/Categories.ts
Normal file
20
front/Categories.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { newLine } from ".";
|
||||
|
||||
interface CategoriesProps {
|
||||
width: number;
|
||||
height: 3;
|
||||
selectedTab: "About" | "Projects" | "Contact";
|
||||
}
|
||||
|
||||
const Categories = ({ width, height, selectedTab }: CategoriesProps) => {
|
||||
const categories = ["🇦 about", "🇵 projects", "🇨 contact", "🇶 quit"];
|
||||
const separatorSpaces = " ".repeat(width / 2 - categories.join(" | ").length / 2 - 4);
|
||||
const separatorLine = "─".repeat(width - separatorSpaces.length * 2 - 6 - (width % 2 ? 0 : 1));
|
||||
return (
|
||||
`${separatorSpaces}┃ ${categories
|
||||
.map((value) => (value.includes(selectedTab.toLowerCase()) ? `\x1b[7m${value}\x1b[0m` : value))
|
||||
.join(" | ")} ┃${separatorSpaces}${newLine}` + `${separatorSpaces}┗${separatorLine}┛${separatorSpaces}`
|
||||
);
|
||||
};
|
||||
|
||||
export default Categories;
|
||||
20
front/Header.ts
Normal file
20
front/Header.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { newLine } from ".";
|
||||
|
||||
interface HeaderProps {
|
||||
width: number;
|
||||
height: 3;
|
||||
}
|
||||
|
||||
const Header = ({ width, height }: HeaderProps) => {
|
||||
const title = "🔥 Welcome to My Portfolio CLI 🔥";
|
||||
const separator = "─".repeat(width / 2 - title.length / 2);
|
||||
const subTitle = "Made by: Jokin Suares";
|
||||
const subTitleSeparator = " ".repeat(width / 2 - subTitle.length / 2);
|
||||
return (
|
||||
`${separator}${title}${separator}${newLine}` +
|
||||
`${subTitleSeparator}${subTitle}${subTitleSeparator}${newLine}` +
|
||||
`${"─".repeat(width)}`
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
9
front/Tabs/About.ts
Normal file
9
front/Tabs/About.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
interface AboutProps {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const About = ({ width, height }: AboutProps) => {
|
||||
return "About";
|
||||
};
|
||||
export default About;
|
||||
20
front/Tabs/Contact.ts
Normal file
20
front/Tabs/Contact.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { newLine } from "..";
|
||||
import { link } from "ansi-escapes";
|
||||
|
||||
interface ContactProps {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const Contact = ({ width, height }: ContactProps) => {
|
||||
return (
|
||||
`${newLine}` +
|
||||
` Email: ${link("contact@jokinsuares.fr", "mailto:contact@jokinsuares.fr")}${newLine}` +
|
||||
` Phone: ${link("+33 6 02 26 22 00", "tel:+33602262200")}${newLine}` +
|
||||
` GitHub: ${link("Feror-BotMaker", "https://github.com/Feror-BotMaker")}${newLine}` +
|
||||
` Discord: ${link("Feror", "https://discordapp.com/users/323206956640763916")}${newLine}` +
|
||||
` LinkedIn: ${link("Jokin Suares", "https://www.linkedin.com/in/jokin-suares/")}${newLine}` +
|
||||
` ssh: $ ssh contact@jokinsuares.fr${newLine}`
|
||||
);
|
||||
};
|
||||
export default Contact;
|
||||
9
front/Tabs/Projects.ts
Normal file
9
front/Tabs/Projects.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
interface ProjectsProps {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const Projects = ({ width, height }: ProjectsProps) => {
|
||||
return "Projects";
|
||||
};
|
||||
export default Projects;
|
||||
21
front/Tabs/TabRouter.ts
Normal file
21
front/Tabs/TabRouter.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import About from "./About";
|
||||
import Contact from "./Contact";
|
||||
import Projects from "./Projects";
|
||||
|
||||
interface TabRouterProps {
|
||||
width: number;
|
||||
height: number;
|
||||
selectedTab: "About" | "Projects" | "Contact";
|
||||
}
|
||||
|
||||
const TabRouter = ({ width, height, selectedTab }: TabRouterProps) => {
|
||||
switch (selectedTab) {
|
||||
case "About":
|
||||
return About({ width, height });
|
||||
case "Projects":
|
||||
return Projects({ width, height });
|
||||
case "Contact":
|
||||
return Contact({ width, height });
|
||||
}
|
||||
};
|
||||
export default TabRouter;
|
||||
34
front/index.ts
Normal file
34
front/index.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import Categories from "./Categories";
|
||||
import Header from "./Header";
|
||||
import TabRouter from "./Tabs/TabRouter";
|
||||
|
||||
interface FrontProps {
|
||||
columns: number;
|
||||
rows: number;
|
||||
selectedTab: "About" | "Projects" | "Contact";
|
||||
}
|
||||
|
||||
const terminalTooSmall = (width: number, height: number) => {
|
||||
return (
|
||||
`Oh no! Your terminal window is too small!${newLine}` +
|
||||
`Minimum size required: 60x20` +
|
||||
`Current size: ${width}x${height}`
|
||||
);
|
||||
};
|
||||
|
||||
const Front = ({ columns, rows, selectedTab }: FrontProps) => {
|
||||
if (columns < 60 || rows < 20) {
|
||||
return terminalTooSmall(columns, rows);
|
||||
}
|
||||
return (
|
||||
`${Header({ width: columns, height: 3 })}${newLine}` +
|
||||
`${Categories({ width: columns, height: 3, selectedTab })}${newLine}` +
|
||||
`${TabRouter({ width: columns, height: 3, selectedTab })}`
|
||||
);
|
||||
};
|
||||
|
||||
const newLine = "\n\r";
|
||||
|
||||
export default Front;
|
||||
|
||||
export { newLine };
|
||||
38
host.key
Normal file
38
host.key
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
host.key.pub
Normal file
1
host.key.pub
Normal file
|
|
@ -0,0 +1 @@
|
|||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDPXG3OLEYlb53XPbL5cqPpGby+2XIgKc1eIbi/o1XOfAKLvxO7Kwl86s/Uqz4bZwXGqK3N3/QytjXtyg8fsYNasBjAZos3BwX0VkUpdmOkh62jeKG8au6ftX0QC/5DHF4jv6/tFaH3tyLdhAVj2J4XnY2O2u6f3MVWJb8lFxDfhqzED9wggAP96RRwXpnbBV82JGcXhs1i8NPlJuTYMrYAEqwlayMZ7L48rTL7JMRHqryGVf5DZft+4B43f5r/9JUei+qSeJzgyCeM1rK9jT9DukDjFaUA/xS3HeRSj1slyd8iMrbYid8Mu4XR6rYWeZTxjz7D/FXtVyO81/EvVLpVoWINbdeelWo9KiyaCjPsO7PR+mOncfkewxOmau7iCXXdR13eQSJIJ+7DAK4ZF6IUltzjotRrRaMNKPNrthgYoEBE7GYrzsjpCaMqxcj4i2e4P1U495a3t9umv4BW7k12Uu2lsneAYP0xyYxYx4/d81/uyu0+f5/iYKdkZ32MX9c= feror@MBP-de-Feror.local
|
||||
259
index.ts
Normal file
259
index.ts
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
import { Server, type ServerChannel } from "ssh2";
|
||||
import Front, { newLine } from "./front";
|
||||
|
||||
import { eraseScreen, cursorTo, cursorHide, cursorShow } from "ansi-escapes";
|
||||
import ContactForm from "./contact/ContactForm";
|
||||
|
||||
const inputCodes = {
|
||||
int8: {
|
||||
backspace: 127,
|
||||
tab: 9,
|
||||
enter: 13,
|
||||
space: 32,
|
||||
escape: 27,
|
||||
ctrlC: 3,
|
||||
a: 97,
|
||||
p: 112,
|
||||
c: 99,
|
||||
q: 113,
|
||||
},
|
||||
base64: {
|
||||
arrows: {
|
||||
up: "G1tB",
|
||||
down: "G1tC",
|
||||
right: "G1tD",
|
||||
left: "G1tE",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
hostKeys: [await Bun.file("./host.key").text()],
|
||||
ident: "portfolio.sh",
|
||||
},
|
||||
(client) => {
|
||||
console.log("Client connected!");
|
||||
|
||||
const userSpecs: {
|
||||
userName?: string;
|
||||
} = {};
|
||||
|
||||
client
|
||||
.on("authentication", (ctx) => {
|
||||
userSpecs.userName = ctx.username;
|
||||
ctx.accept(); // On accepte toutes les connexions
|
||||
})
|
||||
.on("ready", () => {
|
||||
client.on("session", (accept) => {
|
||||
const session = accept();
|
||||
|
||||
const userWindow: {
|
||||
columns: number;
|
||||
rows: number;
|
||||
selectedTab: "About" | "Projects" | "Contact";
|
||||
stream: ServerChannel | null;
|
||||
contactFormData: {
|
||||
name: string;
|
||||
email: string;
|
||||
message: string;
|
||||
};
|
||||
contactFormSelectedField: "name" | "email" | "message" | "submit";
|
||||
} = {
|
||||
columns: 80,
|
||||
rows: 24,
|
||||
selectedTab: "About",
|
||||
stream: null,
|
||||
contactFormData: {
|
||||
name: "",
|
||||
email: "",
|
||||
message: "",
|
||||
},
|
||||
contactFormSelectedField: "name",
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
userWindow.stream?.write(eraseScreen);
|
||||
userWindow.stream?.write(cursorTo(0, 0));
|
||||
userWindow.stream?.write(cursorHide);
|
||||
if (userSpecs.userName === "contact") {
|
||||
userWindow.stream?.write(ContactForm(userWindow));
|
||||
return;
|
||||
}
|
||||
userWindow.stream?.write(Front(userWindow));
|
||||
};
|
||||
|
||||
session.on("pty", (accept, reject, info) => {
|
||||
console.log(info);
|
||||
userWindow.columns = info.cols;
|
||||
userWindow.rows = info.rows;
|
||||
accept();
|
||||
});
|
||||
|
||||
session.on("window-change", (_accept, _reject, info) => {
|
||||
console.log(info);
|
||||
userWindow.columns = info.cols;
|
||||
userWindow.rows = info.rows;
|
||||
render();
|
||||
});
|
||||
|
||||
session.on("shell", (accept) => {
|
||||
const stream = accept();
|
||||
|
||||
// stream.write(`
|
||||
// 🔥 Welcome to My Portfolio CLI 🔥
|
||||
// ------------------------------
|
||||
// Commands: about | projects | contact | exit
|
||||
|
||||
// `);
|
||||
|
||||
userWindow.stream = stream;
|
||||
|
||||
render();
|
||||
|
||||
stream.on("data", (data: Buffer) => {
|
||||
const cmd = data.toString();
|
||||
const cmdAsInt = data.readInt8();
|
||||
|
||||
if (
|
||||
cmdAsInt == inputCodes.int8.ctrlC ||
|
||||
(cmdAsInt === inputCodes.int8.q && userSpecs.userName !== "contact")
|
||||
) {
|
||||
userWindow.stream?.write(eraseScreen);
|
||||
userWindow.stream?.write(cursorTo(0, 0));
|
||||
stream.write("Goodbye!\n");
|
||||
stream.write(cursorShow);
|
||||
stream.close();
|
||||
}
|
||||
|
||||
if (userSpecs.userName === "contact") {
|
||||
console.log(cmdAsInt);
|
||||
if (cmdAsInt == inputCodes.int8.tab) {
|
||||
if (userWindow.contactFormSelectedField === "name") {
|
||||
userWindow.contactFormSelectedField = "email";
|
||||
} else if (userWindow.contactFormSelectedField === "email") {
|
||||
userWindow.contactFormSelectedField = "message";
|
||||
} else if (userWindow.contactFormSelectedField === "message") {
|
||||
userWindow.contactFormSelectedField = "submit";
|
||||
} else if (userWindow.contactFormSelectedField === "submit") {
|
||||
userWindow.contactFormSelectedField = "name";
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
if (cmdAsInt == inputCodes.int8.backspace) {
|
||||
if (userWindow.contactFormSelectedField === "name") {
|
||||
userWindow.contactFormData.name = userWindow.contactFormData.name.slice(0, -1);
|
||||
} else if (userWindow.contactFormSelectedField === "email") {
|
||||
userWindow.contactFormData.email = userWindow.contactFormData.email.slice(
|
||||
0,
|
||||
-1
|
||||
);
|
||||
} else if (userWindow.contactFormSelectedField === "message") {
|
||||
userWindow.contactFormData.message = userWindow.contactFormData.message.slice(
|
||||
0,
|
||||
-1
|
||||
);
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
if (cmdAsInt == inputCodes.int8.space) {
|
||||
if (userWindow.contactFormSelectedField === "name") {
|
||||
userWindow.contactFormData.name += " ";
|
||||
} else if (userWindow.contactFormSelectedField === "email") {
|
||||
userWindow.contactFormData.email += " ";
|
||||
} else if (userWindow.contactFormSelectedField === "message") {
|
||||
userWindow.contactFormData.message += " ";
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
if (cmdAsInt == inputCodes.int8.enter) {
|
||||
if (userWindow.contactFormSelectedField === "message") {
|
||||
userWindow.contactFormData.message += newLine;
|
||||
} else if (userWindow.contactFormSelectedField === "submit") {
|
||||
const webhookUrl =
|
||||
"https://discord.com/api/webhooks/1349123668914999361/2rvvgNNRyzgGpKFJ0JLnT-PbkbM-phyQZwTxRBkxDWSA_9SybsP2eILbxsegthsWsu8d";
|
||||
const messageData = {
|
||||
content: `<@323206956640763916>\nName: ${
|
||||
userWindow.contactFormData.name
|
||||
}\nEmail: ${
|
||||
userWindow.contactFormData.email
|
||||
}\nMessage: ${userWindow.contactFormData.message.replace(newLine, "\n")}`,
|
||||
};
|
||||
|
||||
fetch(webhookUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(messageData),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to send message to Discord webhook");
|
||||
}
|
||||
console.log("Message sent to Discord webhook successfully");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error sending message to Discord webhook:", error);
|
||||
});
|
||||
userWindow.stream?.write(eraseScreen);
|
||||
userWindow.stream?.write(cursorTo(0, 0));
|
||||
stream.write(cursorShow);
|
||||
userWindow.stream?.write("Message sent!\n");
|
||||
stream.close();
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
if (
|
||||
cmdAsInt != inputCodes.int8.backspace &&
|
||||
cmdAsInt != inputCodes.int8.escape &&
|
||||
cmdAsInt != inputCodes.int8.tab &&
|
||||
cmdAsInt != inputCodes.int8.space &&
|
||||
cmdAsInt != inputCodes.int8.enter
|
||||
) {
|
||||
if (userWindow.contactFormSelectedField === "name") {
|
||||
userWindow.contactFormData.name += cmd.trim();
|
||||
} else if (userWindow.contactFormSelectedField === "email") {
|
||||
userWindow.contactFormData.email += cmd.trim();
|
||||
} else if (userWindow.contactFormSelectedField === "message") {
|
||||
userWindow.contactFormData.message += cmd.trim();
|
||||
}
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(cmd);
|
||||
|
||||
switch (cmdAsInt) {
|
||||
case inputCodes.int8.a:
|
||||
userWindow.selectedTab = "About";
|
||||
render();
|
||||
break;
|
||||
case inputCodes.int8.p:
|
||||
userWindow.selectedTab = "Projects";
|
||||
render();
|
||||
break;
|
||||
case inputCodes.int8.c:
|
||||
userWindow.selectedTab = "Contact";
|
||||
render();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client.on("end", () => {
|
||||
console.log("Client disconnected");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
server.listen(2222, "0.0.0.0", () => {
|
||||
console.log("🚀 SSH server running on port 2222");
|
||||
});
|
||||
17
package.json
Normal file
17
package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "bun-ssh-portfolio",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/ssh2": "^1.15.4",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"ssh2": "^1.16.0"
|
||||
}
|
||||
}
|
||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["esnext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue