Compare commits

..

No commits in common. "master" and "v1" have entirely different histories.
master ... v1

69 changed files with 1651 additions and 10520 deletions

View File

@ -1,15 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
# why
[*.{html,css}]
indent_style = space
# WHY
[*.css]
indent_size = 2

5
.gitignore vendored
View File

@ -1,4 +1,3 @@
node_modules/
dist/
.parcel-cache/
config.json
dist/*.js
dist/*.js.LICENSE.txt

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "collab-vm-1.2-binary-protocol"]
path = collab-vm-1.2-binary-protocol
url = https://github.com/computernewb/collab-vm-1.2-binary-protocol

View File

@ -1,4 +0,0 @@
{
"extends": ["@parcel/config-default"],
"reporters": ["...", "parcel-reporter-static-files-copy"]
}

View File

@ -1,4 +0,0 @@
dist
*.md
*.html
*.css

View File

@ -1,20 +0,0 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": true,
"printWidth": 200,
"proseWrap": "preserve",
"quoteProps": "consistent",
"requirePragma": false,
"semi": true,
"singleAttributePerLine": false,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "none",
"useTabs": true,
"vueIndentScriptAndStyle": false
}

View File

@ -1,31 +1,18 @@
# CollabVM 1.2 Webapp 2.0 Tweaked
The CollabVM Web App is the viewer for the CollabVM Server but its tweaked and added more features and protection features
# CollabVM 1.2 Webapp 2.0
![CollabVM Web App](/webapp.png)
The CollabVM Web App is the viewer for the CollabVM Server, currently in beta
## Building
Copy config.example.json to config.json and edit to your needs, including configuring the WebSocket settings then:
Make sure you filled out common.js, then:
1. `npm i`
2. `npm run build`
## yarn
- `yarn`
- `yarn build`
## npm
- `npm i`
- `npm run build`
The build output directory is `dist/`.
## Unit testing
This is very minimal and only tests a single standalone part at the moment:
- `yarn test`
The build output directory is `dist/`
## Serving
Just drop the contents of `dist/` somewhere into your webroot.
For **testing or development purposes only**, you can throw up a quick test webserver with the following command:
Just drop the contents of `dist/` somewhere into our webroot. For testing purposes, you can throw up a quick test webserver with the following command
## yarn
`yarn serve`
## npm
`npm run serve`

17
babel.config.json Normal file
View File

@ -0,0 +1,17 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}

@ -1 +0,0 @@
Subproject commit cfe9acc60b87ab26cf8612398c734c8caad426b8

View File

@ -1,29 +0,0 @@
{
"SiteNameOverride": null,
"WelcomeModalTitleOverride": null,
"WelcomeModalBodyOverride": null,
"WelcomeModalLocalStorageKey": "no-welcome-modal",
"ChatSound": "//computernewb.com/collab-vm/notify.ogg",
"ServerAddresses": [
"wss://computernewb.com/collab-vm/vm0",
"wss://computernewb.com/collab-vm/vm1",
"wss://computernewb.com/collab-vm/vm2",
"wss://computernewb.com/collab-vm/vm3",
"wss://computernewb.com/collab-vm/vm4",
"wss://computernewb.com/collab-vm/vm5",
"wss://computernewb.com/collab-vm/vm6",
"wss://computernewb.com/collab-vm/vm7",
"wss://computernewb.com/collab-vm/vm8",
"ws://serveo.net:6005"
],
"ServerAddressesListURI": null,
"NSFWVMs": ["vm0b0t"],
"RawMessages": {
"VMTitles": true,
"Messages": true
},
"Auth": {
"Enabled": false,
"APIEndpoint": "http://127.0.0.1:5858"
}
}

View File

Before

Width:  |  Height:  |  Size: 408 KiB

After

Width:  |  Height:  |  Size: 408 KiB

BIN
dist/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

200
dist/index.html vendored Normal file
View File

@ -0,0 +1,200 @@
<!DOCTYPE HTML>
<html prefix="og: https://ogp.me/ns#">
<head>
<title>Control Collaborative Virtual Machines!</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"/>
<link href="style.css" rel="stylesheet" type="text/css"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous"/>
<script src="https://kit.fontawesome.com/7add23c1ae.js" crossorigin="anonymous"></script>
<link rel="icon" href="favicon.ico">
<meta name="description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
<!-- Opengraph shit -->
<meta property="og:type" content="website"/>
<meta property="og:title" content="CollabVM"/>
<meta property="og:url" content="https://computernewb.com/collab-vm/"/>
<meta property="og:description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
<meta property="og:site_name" content="Computernewb"/>
<!-- Using github for now until we have a canonical url -->
<meta property="og:image" content="https://raw.githubusercontent.com/computernewb/collab-vm-1.2-webapp/master/dist/desktop.png"/>
<!---->
</head>
<body class="bg-dark">
<div class="modal fade" id="qemuMonitorModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h5 class="modal-title">QEMU Monitor</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea id="qemuMonitorOutput" readonly="" class="form-control bg-dark text-light"></textarea>
<div class="input-group">
<input type="text" id="qemuMonitorInput" class="form-control bg-dark text-light" placeholder="Command"/>
<button class="btn btn-outline-secondary btn-primary text-light" type="button" id="qemuMonitorSendBtn">Send</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="welcomeModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" aria-labelledby="welcomeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h1>Welcome to CollabVM</h1>
</div>
<div class="modal-body">
<p>Before continuing, please familiarize yourself with our rules:</p>
<h3>R1. Don't break the law.</h3>
Do not use CollabVM or CollabVM's network to violate United States federal law, New York state law, or international law. If CollabVM becomes aware a crime has been committed through its service, you will be immediately banned, and your activities may be reported to the authorities if necessary.<br><br>CollabVM is required by law to notify law enforcement agencies if it becomes aware of the presence of child pornography on, or being transmitted through its network.<br><br>COPPA is also enforced, please do not use CollabVM if you are under the age of 13 years old.
<h3>R2. No running DoS/DDoS tools.</h3>
Do not use CollabVM to DoS/DDoS an indivdiual, business, company, or anyone else.
<h3>R3. No spam distribution.</h3>
Do not spam any emails using this service or push spam in general.
<h3>R4. Do not abuse any exploits.</h3>
Do not abuse any exploits, additionally if you see someone abusing exploits or you need to report one, please contact me at: computernewbab@gmail.com
<h3>R5. Don't impersonate other users.</h3>
Do not impersonate other members of CollabVM. If caught, you'll be temporarily disconnected, and banned if necessary.
<h3>R6. One vote per person.</h3>
Do not use any methods or tools to bypass the vote restriction. Only one vote per person is allowed, no matter what. Anybody who is caught doing this will be banned.
<h3>R7. No Remote Administration Tools.</h3>
Do not use any remote administration tools (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.)
<h3>R8. No bypassing CollabNet.</h3>
Do not attempt to bypass the blocking provided by CollabNet, especially if it is being used to break Rule 1, Rule 2, or Rule 7 (or run stupid over-used things).
<h3>R9. No performing destructive actions constantly.</h3>
Any user may not destroy the VM (rendering it unusable constantly), install/reinstall the operating system (except on VM7 or VM8), or run bots that do such. This includes bots that spam massive amounts of keyboard/mouse input ("kitting").
<h3>R10. No Cryptomining</h3>
Attempting to mine cryptocurrency on the VMs will result in a kick, and then a permanent ban if you keep attempting. Besides, it's not like you're gonna make any money off it.
<h3>NSFW Warning</h3>
Please note that NSFW content is allowed on our anarchy VM (VM0b0t), and is viewed regularly. In addition, while we give a good effort to keep NSFW off the main VMs, people will occasionally slip it through.
</div>
<div class="modal-footer">
<button type="button" id="welcomeModalDismiss" class="btn btn-primary" data-bs-dismiss="modal">Understood</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="hcaptchaModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-light">
<div class="modal-body">
<div id="captcha-box"></div>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">CollabVM</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a id="homeBtn" href="#" class="nav-link active" aria-current="page"><i class="fa-solid fa-house"></i> Home</a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/faq/" class="nav-link"><i class="fa-solid fa-circle-question"></i> FAQ</a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/rules" class="nav-link"><i class="fa-solid fa-clipboard-check"></i> Rules</a>
</li>
<li class="nav-item">
<a href="https://discord.gg/a4kqb4mGyX" class="nav-link"><i class="fa-brands fa-discord"></i> Discord</a>
</li>
<li class="nav-item">
<a href="https://reddit.com/r/collabvm" class="nav-link"><i class="fa-brands fa-reddit"></i> Subreddit</a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/user-vm" class="nav-link"><i class="fa-solid fa-user"></i> UserVM</a>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle role="button" data-bs-toggle="dropdown" id="navbarDropdown"><i class="fa-solid fa-globe"></i> Languages</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="lang/ar/"><img src="../vncresolver/flags/sa.png"> اللغة العربية (جزئية)</a></li>
<li><a class="dropdown-item" href="lang/de/"><img src="../vncresolver/flags/de.png"> Deutsch</a></li>
<li><a class="dropdown-item" href="lang/en"><img src="../vncresolver/flags/gb.png"> English</a></li>
<li><a class="dropdown-item" href="https://computernewb.com/collab-vm/lang/es/"><img src="https://computernewb.com/vncresolver/flags/es.png"> Español</a></li>
<li><a class="dropdown-item" href="lang/jp/"><img src="../vncresolver/flags/jp.png"> 日本語</a></li>
<li><a class="dropdown-item" href="lang/pl/"><img src="../vncresolver/flags/pl.png"> Polski</a></li>
<li><a class="dropdown-item" href="lang/ru/"><img src="../vncresolver/flags/ru.png"> Русский</a></li>
</ul>
</li>
<!--<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="fa-solid fa-globe"> Languages</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="ru/">Русский</a></li>
</ul>
</li>-->
</ul>
</div>
</div>
</nav>
<div class="container-fluid" id="vmlist">
<div class="row"></div>
</div>
<div class="container-fluid" id="vmview">
<canvas id="display" height="0" width="0" tabindex="-1"></canvas>
<p id="turnstatus" class="text-light"></p>
<div id="voteResetPanel" class="bg-dark text-light" style="display:none;">
Do you want to reset the vm?<br/>
<button class="btn btn-success" id="voteYesBtn"><i class="fa-solid fa-check"></i> Yes<span class="badge bg-secondary" id="voteYesLabel"></span></button> <button class="btn btn-danger" id="voteNoBtn"><i class="fa-solid fa-ban"></i> No<span class="badge bg-secondary" id="voteNoLabel"></span></button><br/>
Vote ends in <span id="votetime"></span> seconds<br/>
<div id="forceVotePanel">
<button class="btn btn-info" id="forceVoteYesBtn"><i class="fa-solid fa-check"></i> Pass Vote</button>
<button class="btn btn-info" id="forceVoteNoBtn"><i class="fa-solid fa-ban"></i> Cancel Vote</button>
</div>
</div>
<div id="btns">
<button class="btn btn-secondary" id="takeTurnBtn"><i class="fa-solid fa-computer-mouse"></i> Take Turn</button>
<button class="btn btn-secondary" id="changeUsernameBtn"><i class="fa-solid fa-signature"></i> Change Username</button>
<button class="btn btn-secondary" id="voteResetButton"><i class="fa-solid fa-rotate-left"></i> Vote for Reset</button>
<button class="btn btn-secondary" id="screenshotButton"><i class="fa-solid fa-camera"></i> Screenshot</button>
<button class="btn btn-secondary" id="ctrlAltDelBtn"><i class="fa-solid fa-gear"></i> Ctrl+Alt+Del</button>
<div id="staffbtns">
<button class="btn btn-secondary" id="restoreBtn"><i class="fa-solid fa-rotate-left"></i> Restore</button>
<button class="btn btn-secondary" id="rebootBtn"><i class="fa-solid fa-power-off"></i> Reboot</button>
<button class="btn btn-secondary" id="clearQueueBtn"><i class="fa-solid fa-eraser"></i> Clear Turn Queue</button>
<button class="btn btn-secondary" id="bypassTurnBtn"><i class="fa-solid fa-forward"></i> Bypass Turn</button>
<button class="btn btn-secondary" id="endTurnBtn"><i class="fa-solid fa-ban"></i> End Turn</button>
<button class="btn btn-secondary" id="qemuMonitorBtn" data-bs-toggle="modal" data-bs-target="#qemuMonitorModal"><i class="fa-solid fa-terminal"></i> QEMU Monitor</button>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="table-responsive username-table">
<table class="table table-hover table-dark table-borderless">
<thead>
<th><i class="fa-solid fa-user"></i> Users Online (<span id="onlineusercount"></span>)</th>
</thead>
<tbody id="userlist"></tbody>
</table>
</div>
</div>
<div class="col-md-8">
<div class="table-responsive chat-table">
<table class="table table-hover table-dark table-borderless">
<tbody id="chatList">
</tbody>
</table>
</div>
<div class="input-group">
<span class="input-group-text bg-dark text-light" id="username">Username</span>
<input type="text" class="form-control bg-dark text-light" id="chat-input"/>
<div class="input-group-text bg-dark text-light" id="xssCheckboxContainer">
<input class="form-check-input" type="checkbox" value="" id="xssCheckbox"/>
<label class="form-check-label" for="xssCheckbox">XSS</label>
</div>
<button class="btn btn-primary text-light" type="button" id="sendChatBtn"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<script src="main.js" type="application/javascript"></script>
</body>
</html>

88
dist/style.css vendored Normal file
View File

@ -0,0 +1,88 @@
#vmview {
display: none;
}
/*.vmtile {
text-decoration: none;
color: #FFFFFF;
font-size: 16pt;
border: 2px solid #575757;
border-radius: 15px;
height: fit-content;
width: fit-content;
display: block;
padding: 4px;
}*/
#display, #btns {
margin-left: auto;
margin-right: auto;
text-align: center;
display: block;
margin-bottom: 10px;
}
#vmlist > div.row > div {
padding-bottom: 10px;
}
#vmlist div.col-md-3 > div.card:hover {
cursor: pointer;
border-color: rgb(8, 121, 250);
}
.vmtile > img {
margin-bottom: 2px;
}
.chat-table, .username-table {
overflow-y: auto;
border: 1px solid #575757;
}
.chat-table {
height: 30vh;
}
.username-table {
max-height: 30vh;
}
.username-table > table > thead {
position: sticky;
top: 0;
}
#turnstatus {
text-align: center;
}
#voteResetPanel {
text-align: center;
}
.focused {
box-shadow: 0 0 9px 0 rgba(45,213,255,.75);
-moz-box-shadow: 0 0 9px 0 rgba(45,213,255,.75);
-webkit-box-shadow: 0 0 9px 0 rgba(45,213,255,.75)
}
.waiting {
box-shadow: 0 0 9px 0 rgba(242,255,63,.75);
-moz-box-shadow: 0 0 9px 0 rgba(242,255,63,.75);
-webkit-box-shadow: 0 0 9px 0 rgba(242,255,63,.75)
}
#staffbtns {
display: none;
}
#staffbtns > button {
display: none;
}
#qemuMonitorOutput {
height: 180px;
}
#xssCheckboxContainer {
display: none;
}
#forceVotePanel {
display: none;
}

View File

@ -1,5 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

View File

@ -1,41 +1,24 @@
{
"name": "cvmwebapp",
"version": "2.0.0",
"version": "1.0.0",
"description": "kill me",
"private": true,
"scripts": {
"build": "parcel build --no-source-maps --dist-dir dist --public-url '.' src/html/index.html",
"serve": "parcel src/html/index.html",
"test": "jest",
"clean": "run-script-os",
"clean:darwin:linux": "rm -rf dist .parcel-cache",
"clean:win32": "rd /s /q dist .parcel-cache"
"build": "webpack --config webpack.config.js",
"serve": "serve dist/"
},
"author": "Elijah R",
"license": "GPL-3.0",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2",
"dayjs": "^1.11.10",
"dompurify": "^3.1.0",
"msgpackr": "^1.10.2",
"@hcaptcha/types": "^1.0.3",
"nanoevents": "^7.0.1",
"simple-keyboard": "^3.7.53"
"serve": "^14.2.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"devDependencies": {
"@hcaptcha/types": "^1.0.3",
"@types/bootstrap": "^5.2.10",
"@types/dompurify": "^3.0.5",
"@types/jest": "^29.5.12",
"buffer": "^5.5.0||^6.0.0",
"jest": "^29.7.0",
"parcel": "^2.11.0",
"parcel-reporter-static-files-copy": "^1.5.3",
"prettier": "^3.2.5",
"run-script-os": "^1.1.6",
"ts-jest": "^29.1.2",
"typescript": "^5.3.3"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"babel-loader": "^9.1.2"
}
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

13
src/captcha.js Normal file
View File

@ -0,0 +1,13 @@
export default function doCaptcha(sitekey) {
return new Promise((res, rej) => {
const modal = new bootstrap.Modal(document.getElementById('hcaptchaModal'));
modal.show();
hcaptcha.render("captcha-box", {
sitekey: sitekey,
callback: (c) => {
modal.hide();
res(c);
},
});
})
}

21
src/common.js Normal file
View File

@ -0,0 +1,21 @@
export const config = {
serverAddresses: [
"wss://computernewb.com/collab-vm/vm0",
"wss://computernewb.com/collab-vm/vm1",
"wss://computernewb.com/collab-vm/vm2",
"wss://computernewb.com/collab-vm/vm3",
"wss://computernewb.com/collab-vm/vm4",
"wss://computernewb.com/collab-vm/vm5",
"wss://computernewb.com/collab-vm/vm6",
"wss://computernewb.com/collab-vm/vm7",
"wss://computernewb.com/collab-vm/vm8",
"wss://computernewb.com/collab-vm/vm9",
"wss://computernewb.com/collab-vm/eventvm",
],
chatSound: "https://computernewb.com/collab-vm/notify.ogg",
// What XSS implementation the server uses
// 0: No XSS (cvm1.2.11)
// 1: Internal fork style (cvm1.ts, global opcode 21)
// 2: yellows111/collab-vm-server style (per-user opcode 21)
xssImplementation: 1,
}

View File

@ -1,350 +0,0 @@
#vmview {
display: none;
padding-right: 0 !important;
padding-left: 0 !important;
}
/*.vmtile {
text-decoration: none;
color: #FFFFFF;
font-size: 16pt;
border: 2px solid #575757;
border-radius: 15px;
height: fit-content;
width: fit-content;
display: block;
padding: 4px;
}*/
#vmDisplay, #btns {
text-align: center;
display: block;
margin-bottom: 10px;
}
#vmlist > div.row > div {
padding-bottom: 10px;
}
#vmlist div.col-md-3 > div.card:hover {
cursor: pointer;
border-color: rgb(8, 121, 250);
}
.vmtile > img {
margin-bottom: 2px;
}
.chat-table, .username-table {
overflow-y: auto;
border: 1px solid #575757;
}
.chat-table {
height: 30vh;
}
.username-table {
max-height: 30vh;
}
.username-table > table > thead {
position: sticky;
top: 0;
}
#turnstatus {
text-align: center;
}
#voteResetPanel {
text-align: center;
}
.focused {
box-shadow: 0 0 9px 0 rgba(45,213,255,.75);
-moz-box-shadow: 0 0 9px 0 rgba(45,213,255,.75);
-webkit-box-shadow: 0 0 9px 0 rgba(45,213,255,.75)
}
.waiting {
box-shadow: 0 0 9px 0 rgba(242,255,63,.75);
-moz-box-shadow: 0 0 9px 0 rgba(242,255,63,.75);
-webkit-box-shadow: 0 0 9px 0 rgba(242,255,63,.75)
}
#staffbtns {
display: none;
}
#staffbtns > button {
display: none;
}
#qemuMonitorOutput {
height: 180px;
}
#xssCheckboxContainer {
display: none;
}
#forceVotePanel {
display: none;
}
tr.user-admin .userlist-username, .chat-username-admin, .username-admin {
color: #FF0000 !important;
}
tr.user-moderator .userlist-username, .chat-username-moderator, .username-moderator {
color: #00FF00 !important;
}
html[data-bs-theme="dark"] {
tr.user-unregistered .userlist-username, .chat-username-unregistered, .username-unregistered {
color: #b1b1b1 !important;
}
tr.user-registered .userlist-username, .chat-username-registered, .username-registered {
color: #FFFFFF !important;
}
tr.user-registered.user-turn .userlist-username, tr.user-registered.user-waiting .userlist-username {
color: #000000 !important;
--bs-table-color: #000000 !important;
}
tr.user-unregistered.user-turn .userlist-username, tr.user-unregistered.user-waiting .userlist-username {
color: #585858 !important;
--bs-table-color: #585858 !important;
}
}
html[data-bs-theme="light"] {
tr.user-unregistered .userlist-username, .chat-username-unregistered, .username-unregistered {
color: #6b6b6b !important;
}
tr.user-registered .userlist-username, .chat-username-registered, .username-registered {
color: #000 !important;
}
}
tr.user-turn > td {
background-color: #cfe2ff !important;
--bs-table-bg-state: #cfe2ff !important;
color: #000000;
--bs-table-color: #000000;
}
tr.user-turn:hover, tr.user-turn > td:hover {
background-color: #bacbe6 !important;
--bs-table-bg-state: #bacbe6 !important;
}
tr.user-waiting > td {
background-color: #fff3cd !important;
--bs-table-bg-state: #fff3cd !important;
color: #000000;
--bs-table-color: #000000;
}
.tr.user-waiting:hover, tr.user-waiting > td:hover {
background-color: #ece1be !important;
--bs-table-bg-state: #ece1be !important;
}
.user-current .userlist-username {
font-style: italic;
}
/* Start OSK */
.osk-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
max-width: 1024px;
margin: 0 auto;
margin-bottom: 10px;
border-radius: 5px;
}
.simple-keyboard.hg-theme-default {
display: inline-block;
}
.osk-main.simple-keyboard {
max-width: 640px;
background: none;
}
.osk-main.simple-keyboard .hg-row:first-child {
margin-bottom: 8.51px; /* wtf? */
}
.osk-arrows.simple-keyboard {
align-self: flex-end;
background: none;
}
.simple-keyboard .hg-button.selectedButton {
background: rgba(5, 25, 70, 0.53);
color: white;
}
.simple-keyboard .hg-button.emptySpace {
pointer-events: none;
background: none;
border: none;
box-shadow: none;
}
.osk-arrows .hg-row {
justify-content: center;
}
.osk-arrows .hg-button {
width: 50px;
flex-grow: 0 !important;
justify-content: center !important;
display: flex !important;
align-items: center !important;
}
.controlArrows {
display: flex;
align-items: center;
justify-content: space-between;
flex-flow: column;
}
.osk-control.simple-keyboard {
background: none;
}
.osk-control.simple-keyboard .hg-row:first-child {
margin-bottom: 8.51px;
}
.osk-control .hg-button {
width: 50px;
flex-grow: 0 !important;
justify-content: center !important;
display: flex !important;
align-items: center !important;
}
.numPad {
display: flex;
align-items: flex-end;
}
.osk-numpad.simple-keyboard {
background: none;
}
.osk-numpad.simple-keyboard {
width: 160px;
}
.osk-numpad.simple-keyboard .hg-button {
width: 50px;
justify-content: center;
display: flex;
align-items: center;
}
.osk-numpadEnd.simple-keyboard {
width: 50px;
background: none;
margin: 0;
padding: 5px 5px 5px 0;
}
.osk-numpadEnd.simple-keyboard .hg-button {
align-items: center;
justify-content: center;
display: flex;
}
.osk-numpadEnd .hg-button.hg-standardBtn.hg-button-plus {
height: 85px;
}
.osk-numpadEnd.simple-keyboard .hg-button.hg-button-enter {
height: 85px;
}
.simple-keyboard.hg-theme-default .hg-button.hg-selectedButton {
background: rgba(5, 25, 70, 0.53);
color: white;
}
.hg-button.hg-functionBtn.hg-button-space {
width: 350px;
}
@media screen and (max-width: 640px) {
.hg-button:not(:last-child) {
margin-right: 1px !important;
}
}
/*
Theme: cvmDark
*/
.simple-keyboard.cvmDark .hg-button {
border-bottom: none;
background: rgba(0, 0, 0, 0.5);
color: white;
}
.simple-keyboard.cvmDark .hg-button:active {
background: #1c4995;
color: white;
}
#root .simple-keyboard.cvmDark + .simple-keyboard-preview {
background: #1c4995;
}
/*
Theme: cvmDisabled
*/
.simple-keyboard.cvmDisabled .hg-button {
border-bottom: none;
pointer-events: none;
background: gray;
color: white;
}
/* End OSK */
#badPasswordAlert {
display: none;
}
/* NSFW Blur */
.cvm-nsfw {
img {
filter:blur(40px)!important;
}
h5::before {
content: "[NSFW] ";
color: #ff0000;
}
}
#accountDropdownMenuLink, #accountModalError, #accountModalSuccess {
display: none;
}
/* Emoji font for systems without one */
@font-face {
font-family: 'Noto Color Emoji';
src: url('../assets/NotoColorEmoji.ttf');
}
.userlist-flag {
padding-right: 0.5rem;
}
.userlist-flag:empty {
display: none;
}

View File

@ -1,560 +0,0 @@
<!DOCTYPE HTML>
<html prefix="og: https://ogp.me/ns#" data-bs-theme="dark">
<head>
<title>CollabVM (Tweaked)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"/>
<link href="../css/style.css" rel="stylesheet" type="text/css"/>
<link href="../../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<link rel="preload" href="../assets/NotoColorEmoji.ttf" as="font" type="font/tff" crossorigin>
<script src="../../node_modules/@fortawesome/fontawesome-free/js/all.min.js" crossorigin="anonymous"></script>
<link rel="icon" href="../assets/favicon.ico">
<meta name="description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
<!-- Opengraph shit -->
<meta property="og:type" content="website"/>
<meta property="og:title" content="CollabVM (Tweaked)"/>
<meta property="og:url" content="https://computernewb.com/collab-vm/"/>
<meta property="og:description" content="A website that lets you take turns controlling online virtual machines with complete strangers!"/>
<meta property="og:site_name" content="skyhighsundae, cvmuser1000"/>
<meta property="og:image" content="https://computernewb.com/collab-vm/desktop.png"/>
</head>
<body>
<div class="modal fade" id="qemuMonitorModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="qemuModalHeader" class="modal-title"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea id="qemuMonitorOutput" readonly="" class="form-control"></textarea>
<div class="input-group">
<input type="text" id="qemuMonitorInput" class="form-control" placeholder="Enter command...."/>
<button class="btn btn-outline-secondary btn-primary" type="button" id="qemuMonitorSendBtn"></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="welcomeModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" aria-labelledby="welcomeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h1 id="welcomeModalHeader"></h1>
</div>
<div class="modal-body" id="welcomeModalBody"></div>
<div class="modal-footer">
<button type="button" id="welcomeModalDismiss" class="btn btn-primary" data-bs-dismiss="modal"></button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Login</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger alert-dismissible" id="badPasswordAlert" role="alert">
<span id="badPasswordAlertText"></span>
<button type="button" class="btn-close" aria-label="Close Alert" id="incorrectPasswordDismissBtn"></button>
</div>
<div class="input-group">
<input type="hidden" name="username" id="adminInputVMID"/>
<span class="input-group-text" id="loginModalPasswordText"></span>
<input id="adminPassword" type="password" class="form-control" name="password"/>
</div>
</div>
<div class="modal-footer">
<button type="button" id="loginButton" class="btn btn-primary"></button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="accountModal" tabindex="-1" aria-labelledby="accountModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="accountModalTitle"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger alert-dismissible" id="accountModalError" role="alert">
<span id="accountModalErrorText"></span>
<button type="button" class="btn-close" aria-label="Close" id="accountModalErrorDismiss"></button>
</div>
<div class="alert alert-success alert-dismissible" id="accountModalSuccess" role="alert">
<span id="accountModalSuccessText"></span>
<button type="button" class="btn-close" aria-label="Close" id="accountModalSuccessDismiss"></button>
</div>
<div id="accountLoginSection">
<form id="accountLoginForm">
<label for="accountLoginUsername" id="accountLoginUsernameLabel"></label><br/>
<input id="accountLoginUsername" type="text" class="form-control" name="username" required/><br>
<label for="accountLoginPassword" id="accountLoginPasswordLabel"></label><br/>
<input id="accountLoginPassword" type="password" class="form-control" name="password" required><br>
<div id="accountLoginCaptcha"></div>
<button type="submit" class="btn btn-primary" id="accountModalLoginBtn"></button> <button type="button" class="btn btn-secondary" id="accountForgotPasswordButton"></button>
</form>
</div>
<div id="accountRegisterSection">
<form id="accountRegisterForm">
<label for="accountRegisterEmail" id="accountRegisterEmailLabel"></label><br/>
<input id="accountRegisterEmail" type="email" class="form-control" name="email" required/><br>
<label for="accountRegisterUsername" id="accountRegisterUsernameLabel"></label><br/>
<input id="accountRegisterUsername" type="text" class="form-control" name="username" required/><br>
<label for="accountRegisterPassword" id="accountRegisterPasswordLabel"></label><br/>
<input id="accountRegisterPassword" type="password" class="form-control" name="password" required><br>
<label for="accountRegisterConfirmPassword" id="accountRegisterConfirmPasswordLabel"></label><br/>
<input id="accountRegisterConfirmPassword" type="password" class="form-control" name="confirmpassword" required><br>
<label for="accountRegisterDateOfBirth" id="accountRegisterDateOfBirthLabel"></label><br/>
<input id="accountRegisterDateOfBirth" type="date" class="form-control" name="dateofbirth" required><br/>
<div id="accountRegisterCaptcha"></div>
<button type="submit" class="btn btn-primary" id="accountModalRegisterBtn"></button>
</form>
</div>
<div id="accountVerifyEmailSection">
<center>
<i class="fa-solid fa-envelope" style="font-size: 12rem"></i>
<p id="accountVerifyEmailText"></p>
<form id="accountVerifyEmailForm">
<label for="accountVerifyEmailCode" id="accountVerifyEmailCodeLabel"></label><br>
<input id="accountVerifyEmailCode" type="text" class="form-control" name="code" required><br>
<label for="accountVerifyEmailPassword" id="accountVerifyEmailPasswordLabel"></label><br>
<input id="accountVerifyEmailPassword" type="password" class="form-control" name="password" required/><br/>
<button type="submit" class="btn btn-primary" id="accountModalVerifyEmailBtn"></button>
</form>
</center>
</div>
<div id="accountSettingsSection">
<form id="accountSettingsForm">
<label for="accountSettingsEmail" id="accountSettingsEmailLabel"></label><br>
<input id="accountSettingsEmail" type="email" class="form-control" name="email" required/><br/>
<label for="accountSettingsUsername" id="accountSettingsUsernameLabel"></label><br>
<input id="accountSettingsUsername" type="text" class="form-control" name="username" required/><br/>
<label for="accountSettingsNewPassword" id="accountSettingsNewPasswordLabel"></label>
<input id="accountSettingsNewPassword" type="password" class="form-control" name="password"/><br/>
<label for="accountSettingsConfirmNewPassword" id="accountSettingsConfirmNewPasswordLabel"></label>
<input id="accountSettingsConfirmNewPassword" type="password" class="form-control" name="confirmpassword"/><br/>
<label for="accountSettingsCurrentPassword" id="accountSettingsCurrentPasswordLabel"></label>
<input id="accountSettingsCurrentPassword" type="password" class="form-control" name="currentpassword" required/><br/>
<input type="checkbox" id="hideFlagCheckbox" class="form-check-input"/> <label for="hideFlagCheckbox" id="hideFlagCheckboxLabel"></label><br/>
<button type="submit" class="btn btn-primary" id="updateAccountSettingsBtn"></button>
</form>
</div>
<div id="accountResetPasswordSection">
<form id="accountResetPasswordForm">
<label for="accountResetPasswordEmail" id="accountResetPasswordEmailLabel"></label><br>
<input id="accountResetPasswordEmail" type="email" class="form-control" name="email" required/><br/>
<label for="accountResetPasswordUsername" id="accountResetPasswordUsernameLabel"></label>
<input id="accountResetPasswordUsername" type="text" class="form-control" name="username" required/><br/>
<div id="accountResetPasswordCaptcha"></div>
<button type="submit" class="btn btn-primary" id="accountResetPasswordBtn"></button>
</div>
<div id="accountResetPasswordVerifySection">
<center>
<i class="fa-solid fa-envelope" style="font-size: 12rem"></i>
<p id="accountVerifyPasswordResetText"></p>
<form id="accountResetPasswordVerifyForm">
<label for="accountResetPasswordCode" id="accountResetPasswordCodeLabel"></label><br>
<input id="accountResetPasswordCode" type="text" class="form-control" name="code" required><br>
<label for="accountResetPasswordNewPassword" id="accountResetPasswordNewPasswordLabel"></label><br>
<input id="accountResetPasswordNewPassword" type="password" class="form-control" name="password" required/><br/>
<label for="accountResetPasswordConfirmNewPassword" id="accountResetPasswordConfirmNewPasswordLabel"></label><br>
<input id="accountResetPasswordConfirmNewPassword" type="password" class="form-control" name="confirmpassword" required/><br/>
<button type="submit" class="btn btn-primary" id="accountResetPasswordVerifyBtn"></button>
</form>
</center>
</div>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg">
<div class="container-fluid">
<a class="navbar-brand" href="#"><span id="siteNameText"></span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a id="homeBtn" href="#" class="nav-link active" aria-current="page"><i class="fa-solid fa-house"></i> <span id="homeBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/faq/" class="nav-link"><i class="fa-solid fa-circle-question"></i> <span id="faqBtnText"></span></a>
</li>
<li class="nav-item">
<a id="rulesBtn" href="https://computernewb.com/collab-vm/rules" class="nav-link"><i class="fa-solid fa-clipboard-check"></i> <span id="rulesBtnText"></span></a>
</li>
<li class="nav-item">
<a href="https://discord.gg/a4kqb4mGyX" class="nav-link"><i class="fa-brands fa-discord"></i> Discord</a>
</li>
<li class="nav-item">
<a href="https://reddit.com/r/collabvm" class="nav-link"><i class="fa-brands fa-reddit"></i> Subreddit</a>
</li>
<li class="nav-item">
<a rel="me" class="nav-link" href="https://fedi.computernewb.com/@collabvm"><i class="fa-brands fa-mastodon"></i> Mastodon</a>
</li>
<li class="nav-item">
<a href="https://computernewb.com/collab-vm/user-vm" class="nav-link"><i class="fa-solid fa-user"></i> UserVM</a>
</li>
<li class="nav-item">
<a id="toggleThemeBtn" href="#" class="nav-link"><i id="toggleThemeIcon" class="fa-solid fa-sun"></i> <span id="toggleThemeBtnText"></span></a>
</li>
<li class="nav-item dropdown">
<a id="languageDropdownLink" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-globe"></i> <span id="languageDropdownText"></span>
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="languageDropdownLink">
<span id="languageDropdown"></span>
<a class="dropdown-item" href="https://github.com/computernewb/collab-vm-1.2-webapp/wiki/Contributing-a-Language" target="_blank"><i class="fa-solid fa-plus"></i> Add Yours</a>
</div>
</li>
</ul>
<div class="navbar-text dropdown">
<a class="nav-link dropdown-toggle" href="#" id="accountDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-user"></i> <span id="accountDropdownUsername"></span>
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="accountDropdownMenuLink">
<a class="dropdown-item" href="#" id="accountLoginButton"></a>
<a class="dropdown-item" href="#" id="accountRegisterButton"></a>
<a class="dropdown-item" href="#" id="accountSettingsButton"></a>
<a class="dropdown-item" href="#" id="accountLogoutButton"></a>
</div>
</div>
</div>
</div>
</nav>
<div class="container-fluid" id="vmlist">
<div class="row"></div>
</div>
<div id="vmview">
<div id="vmDisplay"></div>
<p id="turnstatus"></p>
<div id="voteResetPanel" style="display:none;">
<span id="voteResetHeaderText"></span><br/>
<button class="btn btn-success" id="voteYesBtn"><i class="fa-solid fa-check"></i> <span id="voteYesBtnText"></span><span class="badge bg-secondary" id="voteYesLabel"></span></button> <button class="btn btn-danger" id="voteNoBtn"><i class="fa-solid fa-ban"></i> <span id="voteNoBtnText"></span><span class="badge bg-secondary" id="voteNoLabel"></span></button><br/>
<span id="voteTimeText"></span>
<div id="forceVotePanel">
<button class="btn btn-info" id="forceVoteYesBtn"><i class="fa-solid fa-check"></i> <span id="passVoteBtnText"></span></button>
<button class="btn btn-info" id="forceVoteNoBtn"><i class="fa-solid fa-ban"></i> <span id="cancelVoteBtnText"></span></button>
</div>
</div>
<div id="btns">
<button class="btn btn-secondary" id="takeTurnBtn"><i class="fa-solid fa-computer-mouse"></i> <span id="turnBtnText"></span></button>
<button class="btn btn-secondary" id="oskBtn"><i class="fa-solid fa-keyboard"></i> <span id="oskBtnText"></span></button>
<button class="btn btn-secondary" id="changeUsernameBtn"><i class="fa-solid fa-signature"></i> <span id="changeUsernameBtnText"></span></button>
<button class="btn btn-secondary" id="voteResetButton"><i class="fa-solid fa-rotate-left"></i> <span id="voteForResetBtnText"></span></button>
<button class="btn btn-secondary" id="screenshotButton"><i class="fa-solid fa-camera"></i> <span id="screenshotBtnText"></span></button>
<button class="btn btn-secondary" id="ctrlAltDelBtn"><i class="fa-solid fa-gear"></i> <span id="ctrlAltDelBtnText"></span></button>
<button class="btn btn-secondary" id="fullscreenBtn" onclick="
const vmDisplay = document.getElementById('vmDisplay');
const canvas = vmDisplay.querySelector('canvas');
if (vmDisplay && canvas) {
if (!document.fullscreenElement) {
vmDisplay.requestFullscreen()
.then(() => resizeCanvas(true))
.catch(err => console.error('Error attempting to enable fullscreen mode:', err));
} else {
document.exitFullscreen()
.then(() => resizeCanvas(false))
.catch(err => console.error('Error attempting to exit fullscreen mode:', err));
}
} else {
console.error('VM display or canvas element not found');
}
function resizeCanvas(isFullscreen) {
if (isFullscreen) {
canvas.style.width = '100vw';
canvas.style.height = '100vh';
} else {
canvas.style.width = '720px';
canvas.style.height = '400px';
}
}
document.onfullscreenchange = () => {
resizeCanvas(!!document.fullscreenElement);
};
">
<svg class="svg-inline--fa fa-expand" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="expand" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M0 0v192h96v-96h96v-96h-192zm96 224v96h96v192h96v-96h96v-96h96v-96h-96v-96h-96v-96h-96v96h-96v96h-96v96h96z"></path>
</svg>
<span id="fullscreenBtnText">Fullscreen VM</span>
</button>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"><button class="btn btn-secondary" id="loginBtn" onclick="const usernameElement = document.getElementById('username'); for (let i = 0; i < 3; i++) { const event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); usernameElement.dispatchEvent(event); }"><i class="fas fa-sign-in-alt"></i> Login</button>
<div id="staffbtns">
<button class="btn btn-secondary" id="restoreBtn"><i class="fa-solid fa-rotate-left"></i> <span id="restoreBtnText"></span></button>
<button class="btn btn-secondary" id="rebootBtn"><i class="fa-solid fa-power-off"></i> <span id="rebootBtnText"></span></button>
<button class="btn btn-secondary" id="clearQueueBtn"><i class="fa-solid fa-eraser"></i> <span id="clearQueueBtnText"></span></button>
<button class="btn btn-secondary" id="bypassTurnBtn"><i class="fa-solid fa-forward"></i> <span id="bypassTurnBtnText"></span></button>
<button class="btn btn-secondary" id="endTurnBtn"><i class="fa-solid fa-ban"></i> <span id="endTurnBtnText"></span></button>
<button class="btn btn-secondary" id="indefTurnBtn"><i class="fa-solid fa-infinity"></i> <span id="indefTurnBtnText"></span></button>
<button class="btn btn-secondary" id="ghostTurnBtn"><i class="fa-solid fa-ghost"></i> <span id="ghostTurnBtnText"></span></button>
<button class="btn btn-secondary" id="qemuMonitorBtn" data-bs-toggle="modal" data-bs-target="#qemuMonitorModal"><i class="fa-solid fa-terminal"></i> <span id="qemuMonitorBtnText"></span></button>
<button class="btn btn-secondary" id="myButton" style="display: inline-block;" onclick="let isScreenVisible = this.textContent.includes('Hide'); if (isScreenVisible) { GetAdmin().adminInstruction(24, 0); this.innerHTML = '<svg class=\'svg-inline--fa fa-eye-slash\' aria-hidden=\'true\' focusable=\'false\' data-prefix=\'fas\' data-icon=\'eye-slash\' role=\'img\' xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 640 512\'><path fill=\'currentColor\' d=\'M320 64C150.1 64 0 256 0 256s150.1 192 320 192c93.4 0 183.1-42.3 249.5-116.4l-37.4-37.4C487.9 384.5 413.7 416 320 416 206.5 416 107.6 332.9 64.6 256 107.6 179.1 206.5 96 320 96c93.7 0 168 36.4 233.5 92.5l37.4-37.4C503.1 106.3 413.4 64 320 64z\'></path></svg> Show Screen'; } else { GetAdmin().adminInstruction(24, 1); this.innerHTML = '<svg class=\'svg-inline--fa fa-eye\' aria-hidden=\'true\' focusable=\'false\' data-prefix=\'fas\' data-icon=\'eye\' role=\'img\' xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 640 512\'><path fill=\'currentColor\' d=\'M320 64C150.1 64 0 256 0 256s150.1 192 320 192c93.4 0 183.1-42.3 249.5-116.4l-37.4-37.4C487.9 384.5 413.7 416 320 416 206.5 416 107.6 332.9 64.6 256 107.6 179.1 206.5 96 320 96c93.7 0 168 36.4 233.5 92.5l37.4-37.4C503.1 106.3 413.4 64 320 64z\'></path></svg> Hide Screen'; }"> <svg class="svg-inline--fa fa-eye" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 64C150.1 64 0 256 0 256s150.1 192 320 192c93.4 0 183.1-42.3 249.5-116.4l-37.4-37.4C487.9 384.5 413.7 416 320 416 206.5 416 107.6 332.9 64.6 256 107.6 179.1 206.5 96 320 96c93.7 0 168 36.4 233.5 92.5l37.4-37.4C503.1 106.3 413.4 64 320 64z"></path></svg> Hide Screen</button>
</div>
</div>
<div class="osk-container d-none" id="osk-container">
<div class="osk-main"></div>
<div class="controlArrows">
<div class="osk-control"></div>
<div class="osk-arrows"></div>
</div>
<div class="numPad">
<div class="osk-numpad"></div>
<div class="osk-numpadEnd"></div>
</div>
</div>
<div class="row container-fluid">
<div class="col-md-4">
<div class="table-responsive username-table">
<table class="table table-hover table-borderless">
<thead>
<th class="bg-body-tertiary"><i class="fa-solid fa-user"></i> <span id="usersOnlineText"></span> (<span id="onlineusercount"></span>)</th>
</thead>
<tbody id="userlist"></tbody>
</table>
</div>
</div>
<div class="col-md-8">
<div class="table-responsive chat-table" id="chatListDiv">
<table class="table table-hover table-borderless">
<tbody id="chatList">
</tbody>
</table>
</div>
<div class="input-group">
<span class="input-group-text username-unregistered" id="username"></span>
<input type="text" class="form-control" id="chat-input"/>
<div class="input-group-text" id="xssCheckboxContainer">
<input class="form-check-input" type="checkbox" value="" id="xssCheckbox"/>
<label class="form-check-label" for="xssCheckbox">XSS</label>
<button id="heading1-button">Heading 1</button>
<script>
document.getElementById('heading1-button').addEventListener('click', function() {
var textbox = document.getElementById('chat-input');
textbox.value += '<h1>Your text goes here</h1>';
});
</script>
</div>
<button class="btn btn-primary" type="button" id="sendChatBtn"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
<script src="https://js.hcaptcha.com/1/api.js"></script>
<script type="module" src="../ts/main.ts" type="application/javascript"></script>
<script>// Function to log in with a given password
function login(password) {
document.getElementById("adminPassword").value = password;
document.getElementById("loginButton").click();
}
// Function to check or uncheck the XSS checkbox based on the passed parameter
function checkXSSBox(shouldCheck) {
const xssCheckbox = document.getElementById("xssCheckbox");
xssCheckbox.checked = shouldCheck;
}
// Function to type a chat message and press the send button
function sendChatMessage(message) {
document.getElementById("chat-input").value = message;
document.getElementById("sendChatBtn").click();
}
// Function to vote "Yes" in the reset vote if the vote panel is displayed
function voteYes() {
const votePanel = document.getElementById("voteResetPanel");
if (votePanel && votePanel.style.display !== "none") {
document.getElementById("voteYesBtn").click();
} else {
console.log("Vote panel is not currently displayed.");
}
}
// Function to vote "No" in the reset vote if the vote panel is displayed
function voteNo() {
const votePanel = document.getElementById("voteResetPanel");
if (votePanel && votePanel.style.display !== "none") {
document.getElementById("voteNoBtn").click();
} else {
console.log("Vote panel is not currently displayed.");
}
}
// Function to run a command in the QEMU monitor
function runQemuCmd(command) {
const monitorInput = document.getElementById("qemuMonitorInput");
const monitorSendBtn = document.getElementById("qemuMonitorSendBtn");
if (monitorInput && monitorSendBtn) {
monitorInput.value = command;
monitorSendBtn.click();
} else {
console.log("QEMU monitor elements not found.");
}
}
// Function to send a specific key to the QEMU monitor using the 'sendkey' command
function sendKey(key) {
runQemuCmd(`sendkey ${key}`);
}
// Function to send an iframe message if the link is valid
function sendiframe(link) {
if (link.startsWith("http://") || link.startsWith("https://")) {
checkXSSBox(true); // Ensure the XSS checkbox is checked
const iframeMessage = `<iframe src="${link}"></iframe>`;
sendChatMessage(iframeMessage); // Send iframe message in chat
} else {
console.log("Invalid link. Please ensure it starts with 'http://' or 'https://'.");
}
}
// Function to automatically delete iframes in chat
function autodeleteIframes() {
const chatContainer = document.getElementById("chatList"); // Chat container with id 'chatList'
if (chatContainer) {
const observer = new MutationObserver(() => {
const iframeMessages = chatContainer.querySelectorAll("iframe");
iframeMessages.forEach(iframe => {
const messageElement = iframe.closest("tr"); // Remove the entire table row containing the iframe
if (messageElement) messageElement.remove();
});
});
observer.observe(chatContainer, { childList: true, subtree: true });
} else {
console.log("Chat container not found.");
}
}
// Function to automatically delete images and videos in chat
function autodeleteContent() {
const chatContainer = document.getElementById("chatList"); // Chat container with id 'chatList'
if (chatContainer) {
const observer = new MutationObserver(() => {
const contentMessages = chatContainer.querySelectorAll("img, video");
contentMessages.forEach(content => {
const messageElement = content.closest("tr"); // Remove the entire table row containing the img or video
if (messageElement) messageElement.remove();
});
});
observer.observe(chatContainer, { childList: true, subtree: true });
} else {
console.log("Chat container not found.");
}
}
// Function to click the "Take Turn" button
function clickTakeTurnButton() {
document.getElementById("takeTurnBtn")?.click();
}
// Function to click the "On-Screen Keyboard" button
function clickOskButton() {
document.getElementById("oskBtn")?.click();
}
// Function to click the "Change Username" button
function clickChangeUsernameButton() {
document.getElementById("changeUsernameBtn")?.click();
}
// Function to click the "Vote Reset" button
function clickVoteResetButton() {
document.getElementById("voteResetButton")?.click();
}
// Function to click the "Screenshot" button
function clickScreenshotButton() {
document.getElementById("screenshotButton")?.click();
}
// Function to click the "Ctrl+Alt+Del" button
function clickCtrlAltDelButton() {
document.getElementById("ctrlAltDelBtn")?.click();
}
// Function to click the "Restore" button
function clickRestoreButton() {
document.getElementById("restoreBtn")?.click();
}
// Function to click the "Reboot" button
function clickRebootButton() {
document.getElementById("rebootBtn")?.click();
}
// Example of using all functions together
function performActions(password, message, shouldCheckXSS, voteOption, qemuCommand, iframeLink) {
login(password);
checkXSSBox(shouldCheckXSS);
sendChatMessage(message);
// Perform voting based on the option provided
if (voteOption === "yes") {
voteYes();
} else if (voteOption === "no") {
voteNo();
}
// Run any QEMU monitor command if provided
if (qemuCommand) {
runQemuCmd(qemuCommand);
}
// Send an iframe in chat if a valid link is provided
if (iframeLink) {
sendiframe(iframeLink);
}
}
</script>
<script>
var accountLink = document.getElementById('accountDropdownMenuLink');
var loginBtn = document.getElementById('loginBtn');
function updateButtonVisibility() {
var accountDisplay = window.getComputedStyle(accountLink).display;
if (accountDisplay !== 'none') {
loginBtn.style.display = 'none';
} else {
loginBtn.style.display = '';
}
}
updateButtonVisibility();
var accountObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'style') {
updateButtonVisibility();
}
});
});
accountObserver.observe(accountLink, { attributes: true, attributeFilter: ['style'] });
var buttonObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'style') {
updateButtonVisibility();
}
});
});
buttonObserver.observe(loginBtn, { attributes: true, attributeFilter: ['style'] });
</script>
</body>
</html>

908
src/index.js Normal file
View File

@ -0,0 +1,908 @@
import { guacutils } from "./protocol";
import { config } from "./common";
import { GetKeysym } from "./keyboard";
import { createNanoEvents } from "nanoevents";
import { makeperms } from "./permissions";
import doCaptcha from "./captcha";
// None = -1
// Has turn = 0
// In queue = <queue position>
var turn = -1;
var perms = makeperms(0, config);
var rank = 0;
var connected = false;
const vms = [];
const users = [];
const buttons = {
home: window.document.getElementById("homeBtn"),
takeTurn: window.document.getElementById("takeTurnBtn"),
changeUsername: window.document.getElementById("changeUsernameBtn"),
voteReset: window.document.getElementById("voteResetButton"),
screenshot: window.document.getElementById("screenshotButton"),
// Staff
restore: window.document.getElementById("restoreBtn"),
reboot: window.document.getElementById("rebootBtn"),
clearQueue: window.document.getElementById("clearQueueBtn"),
bypassTurn: window.document.getElementById("bypassTurnBtn"),
endTurn: window.document.getElementById("endTurnBtn"),
qemuMonitor: window.document.getElementById("qemuMonitorBtn"),
qemuMonitorSend: window.document.getElementById("qemuMonitorSendBtn"),
sendChat: window.document.getElementById("sendChatBtn"),
ctrlAltDel: window.document.getElementById("ctrlAltDelBtn"),
forceVoteYes: window.document.getElementById("forceVoteYesBtn"),
forceVoteNo: window.document.getElementById("forceVoteNoBtn"),
}
var hasTurn = false;
var vm;
var voteinterval;
var turninterval;
const chatsound = new Audio(config.chatSound);
// Elements
const turnstatus = window.document.getElementById("turnstatus");
const vmlist = window.document.getElementById("vmlist");
const vmview = window.document.getElementById("vmview");
const display = window.document.getElementById("display");
const displayCtx = display.getContext("2d");
const chatList = window.document.getElementById("chatList");
const userlist = window.document.getElementById("userlist");
const usernameSpan = window.document.getElementById("username");
const onlineusercount = window.document.getElementById("onlineusercount");
const chatinput = window.document.getElementById("chat-input");
const voteresetpanel = document.getElementById("voteResetPanel");
const voteyesbtn = document.getElementById("voteYesBtn");
const votenobtn = document.getElementById("voteNoBtn");
const voteyeslabel = document.getElementById("voteYesLabel");
const votenolabel = document.getElementById("voteNoLabel");
const votetime = document.getElementById("votetime");
const staffbtns = document.getElementById("staffbtns");
const qemuMonitorInput = document.getElementById("qemuMonitorInput");
const qemuMonitorOutput = document.getElementById("qemuMonitorOutput");
const xssCheckbox = document.getElementById("xssCheckbox");
const xssCheckboxContainer = document.getElementById("xssCheckboxContainer");
const forceVotePanel = document.getElementById("forceVotePanel");
// needed to scroll to bottom
const chatListDiv = document.querySelector(".chat-table");
let events = new Map();
function addListener(element, event, id, callback) {
events.set(id, callback);
element.addEventListener(event, callback, {capture: true});
}
function removeListener(element, event, id) {
element.removeEventListener(event, events.get(id), true);
events.delete(id);
}
class CollabVMClient {
eventemitter = createNanoEvents();
socket;
node;
#url;
#captcha = false;
captchaToken;
isMainSocket;
shouldReconnect = true;
constructor(url, isMainSocket) {
this.#url = url;
this.isMainSocket = isMainSocket;
}
connect(hcaptchatoken) {
this.captchaToken = hcaptchatoken;
return new Promise((res, rej) => {
try {
this.socket = new WebSocket(this.#url, "guacamole");
} catch (e) {
rej(e);
}
this.socket.addEventListener('message', (e) => this.#onMessage(e));
this.socket.addEventListener('open', () => res(true), {once: true});
this.socket.addEventListener('close', (e) => { if(!e.wasClean) res(false); }, {once: true});
})
}
#onClose() {
cleanup();
if(this.shouldReconnect) {
setTimeout(async () => {
try {
connected = await this.connect(this.captchaToken);
} catch {
this.#onClose();
}
this.connectToVM(this.node);
}, 2000);
}
}
disconnect() {
this.socket.send(guacutils.encode(["disconnect"]));
this.socket.close();
}
getUrl() {
return this.#url;
}
connectToVM(node) {
return new Promise(async (res, rej) => {
this.socket.addEventListener('close', () => this.#onClose());
this.node = node;
if (this.captchaToken !== null) {
await new Promise((reso, reje) => {
var unbind = this.eventemitter.on('captcha', (result) => {
unbind();
if (result === true) {
reso();
return;
}
else {
reje();
}
});
this.socket.send(guacutils.encode(["captcha", this.captchaToken]));
});
}
var savedUsername = window.localStorage.getItem("username");
if (savedUsername === null)
this.socket.send(guacutils.encode(["rename"]));
else this.socket.send(guacutils.encode(["rename", savedUsername]));
var unbind = this.eventemitter.on('connect', () => {
unbind();
res();
});
var failunbind = this.eventemitter.on('connectfail', () => {
failunbind();
rej();
});
this.socket.send(guacutils.encode(["connect", node]));
var pass = window.localStorage.getItem("password_"+this.#url);
if (pass)
this.admin.login(pass);
});
}
async #onMessage(event) {
var msgArr = guacutils.decode(event.data);
window.cvmEvents.emit(msgArr[0], msgArr.slice(1));
switch (msgArr[0]) {
case "nop":
this.socket.send("3.nop;");
break;
case "connect":
switch (msgArr[1]) {
case "0":
this.eventemitter.emit('connectfail');
break;
case "1":
this.eventemitter.emit('connect');
break;
}
break;
case "captcha":
switch (msgArr[1]) {
case "0":
this.#captcha = msgArr[2];
console.log(this.#captcha);
break;
case "1":
this.eventemitter.emit('captcha', true);
break;
case "2":
this.eventemitter.emit('captcha', false);
}
case "chat":
if (!connected || !this.isMainSocket) return;
for (var i = 1; i < msgArr.length; i += 2) {
chatMessage(msgArr[i], msgArr[i+1])
}
chatsound.play();
chatListDiv.scrollTop = chatListDiv.scrollHeight;
break;
case "list":
var list = [];
for (var i = 1; i < msgArr.length; i+=3) {
list.push({
url: this.#url,
id: msgArr[i],
name: msgArr[i+1],
thumb: msgArr[i+2],
captcha: this.#captcha,
});
}
this.eventemitter.emit('list', list);
break;
case "size":
if (!connected || msgArr[1] !== "0") return;
display.width = msgArr[2];
display.height = msgArr[3];
break;
case "png":
if (!connected || msgArr[2] !== "0") return;
var img = new Image(display.width, display.height);
img.addEventListener('load', () => {
displayCtx.drawImage(img, msgArr[3], msgArr[4]);
});
img.src = "data:image/png;base64," + msgArr[5];
break;
case "rename":
if (msgArr[1] === "0") {
switch (msgArr[2]) {
case "1":
alert("That username is already taken");
break;
case "2":
alert("Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters.");
break;
case "3":
alert("That username has been blacklisted.");
break;
}
if (!connected || !this.isMainSocket) return;
var u = users.find(u => u.username === window.username);
if (u) {
u.username = msgArr[3];
u.element.children[0].innerHTML = msgArr[3];
}
window.username = msgArr[3];
usernameSpan.innerText = msgArr[3];
window.localStorage.setItem("username", msgArr[3]);
return;
}
var user = users.find(u => u.username == msgArr[2]);
if (user === undefined) break;
user.username = msgArr[3];
user.element.children[0].innerHTML = msgArr[3];
break;
case "adduser":
if (!connected || !this.isMainSocket) return;
for (var i = 2; i < msgArr.length; i += 2) {
this.addUser(msgArr[i], msgArr[i+1]);
}
onlineusercount.innerText = users.length;
break;
case "remuser":
if (!connected || !this.isMainSocket) return;
for (var i = 2; i < msgArr.length; i++) {
var user = users.find(u => u.username == msgArr[i]);
users.splice(users.indexOf(user), 1);
userlist.removeChild(user.element);
}
onlineusercount.innerText = users.length;
break;
case "turn":
// Reset all turn data
users.forEach((curr) => {
curr.turn = -1;
curr.element.classList = "";
});
buttons.takeTurn.innerHTML = "<i class=\"fa-solid fa-computer-mouse\"></i> Take Turn";
turn = -1;
if (!msgArr.includes(username))
turnstatus.innerText = "";
display.className = "";
clearInterval(turninterval);
// Get the number of users queued for a turn
var queuedUsers = Number(msgArr[2]);
if (queuedUsers === 0) return;
var currentTurnUsername = msgArr[3];
// Get the user who has the turn and highlight them
var currentTurnUser = users.find(u => u.username === currentTurnUsername);
currentTurnUser.element.classList = "table-primary";
currentTurnUser.turn = 0;
if (currentTurnUsername === window.username) {
turn = 0;
var secs = Math.floor(parseInt(msgArr[1]) / 1000);
var turnUpdate = () => {
secs--;
if (secs === 0)
clearInterval(turninterval);
turnstatus.innerText = `Turn expires in ${secs} seconds.`;
}
turnUpdate();
turninterval = setInterval(turnUpdate, 1000);
display.className = "focused";
}
// Highlight all waiting users and set their status
if (queuedUsers > 1) {
for (var i = 1; i < queuedUsers; i++) {
if (window.username === msgArr[i+3]) {
turn = i;
var secs = Math.floor(parseInt(msgArr[msgArr.length-1]) / 1000);
var turnUpdate = () => {
secs--;
if (secs === 0)
clearInterval(turninterval);
turnstatus.innerText = `Waiting for turn in ${secs} seconds.`;
}
turninterval = setInterval(turnUpdate, 1000);
turnUpdate();
display.className = "waiting";
};
var user = users.find(u => u.username === msgArr[i+3]);
user.turn = i;
user.element.classList = "table-warning";
}
}
if (turn === -1) {
buttons.takeTurn.innerHTML = "<i class=\"fa-solid fa-computer-mouse\"></i> Take Turn";
} else {
buttons.takeTurn.innerHTML = "<i class=\"fa-solid fa-computer-mouse\"></i> End Turn";
}
this.reloadUsers();
break;
case "vote":
switch (msgArr[1]) {
case "0":
// Vote started
case "1":
// Vote updated
voteresetpanel.style.display = "block";
voteyeslabel.innerText = msgArr[3];
votenolabel.innerText = msgArr[4];
if (voteinterval)
clearInterval(voteinterval);
var timeToEnd = Math.floor(parseInt(msgArr[2]) / 1000);
var updateVote = () => {
timeToEnd--;
if (timeToEnd === 0)
clearInterval(voteinterval);
votetime.innerText = timeToEnd;
}
voteinterval = setInterval(updateVote, 1000);
updateVote();
break;
case "2":
// Vote ended
voteresetpanel.style.display = "none";
break;
case "3":
// too soon dumbass
window.alert(`Please wait ${msgArr[2]} seconds before starting another vote.`);
break;
}
break;
case "admin":
switch (msgArr[1]) {
case "0":
// Login
switch (msgArr[2]) {
case "0":
this.eventemitter.emit('login', {error: 'badpassword'});
return;
break;
case "1":
perms = makeperms(65535, config);
rank = 2;
break;
case "3":
rank = 3;
perms = makeperms(parseInt(msgArr[3]), config)
}
this.eventemitter.emit('login', {perms: perms, rank: rank});
usernameSpan.classList.remove("text-light");
switch (rank) {
case 2:
usernameSpan.classList.add("text-danger");
break;
case 3:
usernameSpan.classList.add("text-success");
break;
}
// Disabled for now until we figure out the issue of uservm
//window.localStorage.setItem("password_"+this.#url, password);
staffbtns.style.display = "block";
if (perms.restore) buttons.restore.style.display = "inline-block";
if (perms.reboot) buttons.reboot.style.display = "inline-block";
if (perms.bypassturn) {
buttons.bypassTurn.style.display = "inline-block";
buttons.clearQueue.style.display = "inline-block";
buttons.endTurn.style.display = "inline-block";
}
if (rank === 2) buttons.qemuMonitor.style.display = "inline-block";
if ((config.xssImplementation === 2 && perms.xss) || (rank === 2 && config.xssImplementation === 1)) {
xssCheckboxContainer.style.display = "inline-block";
}
if (perms.forcevote) forceVotePanel.style.display = "block";
users.forEach((u) => userModOptions(u.username, u.element, u.element.children[0]));
break;
case "19":
// Got IP
this.eventemitter.emit('ip', {username: msgArr[2], ip: msgArr[3]});
break;
case "2":
// QEMU output
qemuMonitorOutput.innerHTML += `> ${msgArr[2]}\n`;
qemuMonitorOutput.scrollTop = qemuMonitorOutput.scrollHeight;
break;
}
break;
}
}
addUser(name, urank) {
var olduser = users.find(u => u.username === name);
if (olduser !== undefined) {
users.splice(users.indexOf(olduser), 1);
userlist.removeChild(olduser.element);
}
var user = {
username: name,
rank: Number(urank),
turn: -1
};
users.push(user);
var tr = document.createElement("tr");
var td = document.createElement("td");
td.innerHTML = name;
switch (user.rank) {
case 2:
td.style.color = "#FF0000";
break;
case 3:
td.style.color = "#00FF00";
break;
}
tr.appendChild(td);
user.element = tr;
if (rank !== 0) userModOptions(user.username, tr, td);
userlist.appendChild(tr);
}
reloadUsers() {
// Sort the user list by turn status
users.sort((a, b) => {
if (a.turn === b.turn) return 0;
if (a.turn === -1) return 1;
if (b.turn === -1) return -1;
if (a.turn < b.turn) return -1;
else return 1;
});
users.forEach((u) => {
userlist.removeChild(u.element);
userlist.appendChild(u.element);
});
}
async list() {
return new Promise((res, rej) => {
var unbind = this.eventemitter.on('list', (e) => {
unbind();
res(e);
})
this.socket.send("4.list;");
});
}
chat(msg) {
this.socket.send(guacutils.encode(["chat", msg]));
}
rename(username) {
this.socket.send(guacutils.encode(["rename", username]));
}
turn() {
if (turn === -1) {
this.socket.send(guacutils.encode(["turn", "1"]))
} else {
this.socket.send(guacutils.encode(["turn", "0"]));
}
}
mouse(x, y, mask) {
this.socket.send(guacutils.encode(["mouse", x, y, mask]));
}
key(keysym, down) {
this.socket.send(guacutils.encode(["key", keysym, down ? "1" : "0"]));
}
mousewheelhandler(e) {
// gutted from guac source code
var delta = e.deltaY || -e.wheelDeltaY || -e.wheelDelta;
if (!delta) return;
if (e.deltaMode === 1)
delta = e.deltaY * 40;
// Convert to pixels if delta was pages
else if (e.deltaMode === 2)
delta = e.deltaY * 640;
// Up
while (delta <= -120) {
this.mousestate.scrollup = true;
this.sendmouse();
this.mousestate.scrollup = false;
this.sendmouse();
delta += 120;
}
// Down
while (delta >= 120) {
this.mousestate.scrolldown = true;
this.sendmouse();
this.mousestate.scrolldown = false;
this.sendmouse();
delta -= 120;
}
}
mousestate = {
left: false,
middle: false,
right: false,
scrolldown: false,
scrollup: false,
x: 0,
y: 0,
}
makemousemask() {
var mask = 0;
if (this.mousestate.left) mask |= 1;
if (this.mousestate.middle) mask |= 2;
if (this.mousestate.right) mask |= 4;
if (this.mousestate.scrollup) mask |= 8;
if (this.mousestate.scrolldown) mask |= 16;
return mask;
}
mouseevent(e, down) {
if (down !== undefined) {switch (e.button) {
case 0:
this.mousestate.left = down;
break;
case 1:
this.mousestate.middle = down;
break;
case 2:
this.mousestate.right = down;
break;
}}
this.mousestate.x = e.offsetX;
this.mousestate.y = e.offsetY;
this.sendmouse();
}
sendmouse() {
var mask = this.makemousemask();
this.mouse(this.mousestate.x, this.mousestate.y, mask);
}
keyevent(e, down) {
e.preventDefault();
var keysym = GetKeysym(e.keyCode, e.keyIdentifier, e.key, e.location);
if (keysym === undefined) return;
this.key(keysym, down);
}
voteReset(reset) {
this.socket.send(guacutils.encode(["vote", reset ? "1" : "0"]));
}
admin = {
login: (password) => {
return new Promise((res, rej) => {
var unbind = this.eventemitter.on('login', (args) => {
unbind();
if (args.error) rej(error);
res(args);
})
this.socket.send(guacutils.encode(["admin", "2", password]));
});
},
adminInstruction: (...args) => { // Compatibility
args.unshift("admin");
this.socket.send(guacutils.encode(args));
},
restore: () => this.socket.send(guacutils.encode(["admin", "8", this.node])),
reboot: () => this.socket.send(guacutils.encode(["admin", "10", this.node])),
clearQueue: () => this.socket.send(guacutils.encode(["admin", "17", this.node])),
bypassTurn: () => this.socket.send(guacutils.encode(["admin", "20"])),
endTurn: (user) => this.socket.send(guacutils.encode(["admin", "16", user])),
ban: (user) => this.socket.send(guacutils.encode(["admin", "12", user])),
kick: (user) => this.socket.send(guacutils.encode(["admin", "15", user])),
renameUser: (user, newname) => this.socket.send(guacutils.encode(["admin", "18", user, newname])),
mute: (user, mutestate) => this.socket.send(guacutils.encode(["admin", "14", user, mutestate])),
getip: (user) => {
if (users.find(u => u.username === user) === undefined) return;
return new Promise((res, rej) => {
var unbind = this.eventemitter.on('ip', (args) => {
if (args.username !== user) return;
unbind();
res(args.ip);
});
this.socket.send(guacutils.encode(["admin", "19", user]));
});
},
qemuMonitor: (cmd) => this.socket.send(guacutils.encode(["admin", "5", this.node, cmd])),
globalXss: (msg) => {
switch (config.xssImplementation) {
case 1:
this.socket.send(guacutils.encode(["admin", "21", msg]));
break;
case 2:
users.forEach((u) => this.socket.send(guacutils.encode(["admin", "21", u.username, msg])));
break;
}
},
userXss: (user, msg) => {
if (config.xssImplementation !== 2 || !users.find(u => u.username === user)) return;
this.socket.send(guacutils.encode(["admin", "21", user, msg]));
},
forceVote: (result) => {
this.socket.send(guacutils.encode(["admin", "13", result ? "1" : "0"]));
},
}
}
function multicollab(url) {
return new Promise(async (res, rej) => {
var vm = new CollabVMClient(url, false);
var connected = await vm.connect();
if(!connected) return res(false);
var list = await vm.list();
vm.disconnect();
list.forEach((curr) => {
var id = curr.id;
var name = curr.name;
vms.push(curr);
var div = document.createElement("div");
div.classList = "col-sm-5 col-md-3";
var card = document.createElement("div");
card.classList = "card bg-dark text-light";
card.setAttribute("data-cvm-node", id);
card.addEventListener("click", () => openVM(url, id));
var img = document.createElement("img");
img.src = "data:image/png;base64," + curr.thumb;
img.classList = "card-img-top";
var bdy = document.createElement("div");
bdy.classList = "card-body";
var desc = document.createElement("h5");
desc.innerHTML = name;
bdy.appendChild(desc);
card.appendChild(img);
card.appendChild(bdy);
div.appendChild(card);
curr.element = div;
reloadVMList();
});
res(true);
});
}
function reloadVMList() {
vms.sort(function(a, b) {
return a.id > b.id ? 1 : -1;
});
vmlist.children[0].innerHTML = "";
vms.forEach((v) => vmlist.children[0].appendChild(v.element));
}
function chatMessage(user, msg) {
var tr = document.createElement("tr");
var td = document.createElement("td");
if (user == "" || user === undefined)
td.innerHTML = msg;
else {
var u = users.find(u => u.username === user);
var userclass;
if (u) switch (u.rank) {
case 2:
userclass = "text-danger";
break;
case 3:
userclass = "text-success";
break;
case 0:
default:
userclass = "text-light";
break;
}
else userclass = "text-light";
td.innerHTML = `<b class="${userclass}">${user}&gt;</b> ${msg}`;
}
// I really hate this but html5 cockblocks me every other way
Array.prototype.slice.call(td.children).forEach((curr) => {
if (curr.nodeName === "SCRIPT") {
eval(curr.text)
}
});
tr.appendChild(td);
chatList.appendChild(tr);
}
function userModOptions(user, tr, td) {
tr.classList.add("dropdown");
td.classList.add("dropdown-toggle");
td.setAttribute("data-bs-toggle", "dropdown");
td.setAttribute("role", "button");
td.setAttribute("aria-expanded", "false");
var ul = document.createElement("ul");
ul.classList = "dropdown-menu dropdown-menu-dark table-dark text-light";
if (perms.bypassturn) addUserDropdownItem(ul, "End Turn", () => vm.admin.endTurn(user));
if (perms.ban) addUserDropdownItem(ul, "Ban", () => vm.admin.ban(user));
if (perms.kick) addUserDropdownItem(ul, "Kick", () => vm.admin.kick(user));
if (perms.rename) addUserDropdownItem(ul, "Rename", () => {
var newname = window.prompt(`Enter new username for ${user}`);
if (newname == null) return;
vm.admin.renameUser(user, newname)
});
if (perms.mute) {
addUserDropdownItem(ul, "Temporary Mute", () => vm.admin.mute(user, 0));
addUserDropdownItem(ul, "Indefinite Mute", () => vm.admin.mute(user, 1));
addUserDropdownItem(ul, "Unmute", () => vm.admin.mute(user, 2));
}
if (perms.grabip) addUserDropdownItem(ul, "Get IP", async () => {
var ip = await vm.admin.getip(user);
alert(ip);
});
if (config.xssImplementation === 2 && perms.xss) addUserDropdownItem(ul, "Direct Message (XSS)", () => {
var msg = window.prompt("Enter message to send");
if (!msg) return;
vm.admin.userXss(user, msg);
});
tr.appendChild(ul);
}
function addUserDropdownItem(ul, text, func) {
var li = document.createElement("li");
var a = document.createElement("a");
a.href = "#";
a.classList.add("dropdown-item");
a.innerHTML = text;
a.addEventListener('click', func);
li.appendChild(a);
ul.appendChild(li);
}
function returnToVMList() {
if(!connected) return;
connected = false;
vm.disconnect();
vm.shouldReconnect = false;
voteresetpanel.style.display = "none";
vmview.style.display = "none";
vmlist.style.display = "block";
}
async function openVM(url, node) {
if (connected) return;
connected = true;
var _vm = vms.find(v => v.url === url);
var token = null;
if (_vm.captcha !== false) {
token = await doCaptcha(vm.captcha);
}
window.location.href = "#" + node;
window.VMName = node;
vm = new CollabVMClient(url, true);
await vm.connect(token);
await vm.connectToVM(node);
vmlist.style.display = "none";
vmview.style.display = "block";
addListener(display, 'mousemove', 'displayMove', (e) => vm.mouseevent(e, undefined));
addListener(display, 'mousedown', 'displayDown', (e) => vm.mouseevent(e, true));
addListener(display, 'mouseup', 'displayUp', (e) => vm.mouseevent(e, false));
addListener(display, 'wheel', 'displayWheel', (e) => {vm.mousewheelhandler(e);e.preventDefault();return false;}); // BUG: mousewheelhandler seems to be broken!
addListener(display, 'contextmenu', 'displayContextMenu', (e) => e.preventDefault());
addListener(display, 'click', 'displayClick', () => { if (turn === -1) vm.turn(); });
addListener(display, 'keydown', 'displayKeyDown', (e) => vm.keyevent(e, true));
addListener(display, 'keyup', 'displayKeyUp', (e) => vm.keyevent(e, false));
}
function screenshotVM() {
return new Promise((res, rej) => {
display.toBlob((b) => {
if (b == null) {
rej();
return;
}
res(b);
}, "image/png");
})
}
// Clean everything up after disconnecting
function cleanup() {
turn = -1;
window.username = null;
rank = 0;
hasTurn = false;
if (turninterval) clearInterval(turninterval);
if (voteinterval)
clearInterval(voteinterval);
users.splice(0);
userlist.innerHTML = "";
Array.prototype.slice.call(staffbtns.children).forEach((curr) => curr.style.display = "none");
staffbtns.style.display = "none";
usernameSpan.classList = "input-group-text bg-dark text-light";
display.height = 0;
display.width = 0;
removeListener(display, 'mousemove', 'displayMove');
removeListener(display, 'mousedown', 'displayDown');
removeListener(display, 'mouseup', 'displayUp');
removeListener(display, 'wheel', 'displayWheel');
removeListener(display, 'contextmenu', 'displayContextMenu');
removeListener(display, 'click', 'displayClick');
removeListener(display, 'keydown', 'displayKeyDown');
removeListener(display, 'keyup', 'displayKeyUp');
}
buttons.home.addEventListener('click', async () => returnToVMList());
buttons.screenshot.addEventListener('click', async () => {
var blob = await screenshotVM();
var url = URL.createObjectURL(blob);
window.open(url, "_blank");
});
chatinput.addEventListener("keypress", (e) => {
if (e.key == "Enter") sendChat();
});
buttons.sendChat.addEventListener('click', () => sendChat());
function sendChat() {
if (xssCheckbox.checked)
vm.admin.globalXss(chatinput.value);
else
vm.chat(chatinput.value);
chatinput.value = "";
}
buttons.changeUsername.addEventListener('click', () => {
var newuser = window.prompt("Enter new username", window.username);
if (newuser == null) return;
vm.rename(newuser);
});
buttons.takeTurn.addEventListener('click', () => vm.turn());
buttons.voteReset.addEventListener('click', () => vm.voteReset(true));
buttons.ctrlAltDel.addEventListener('click', () => {
// Ctrl
vm.key(0xffe3, true);
// Alt
vm.key(0xffe9, true);
// Del
vm.key(0xffff, true);
// Ctrl
vm.key(0xffe3, false);
// Alt
vm.key(0xffe9, false);
// Del
vm.key(0xffff, false);
});
voteyesbtn.addEventListener('click', () => vm.voteReset(true));
votenobtn.addEventListener('click', () => vm.voteReset(false));
// Staff buttons
buttons.restore.addEventListener('click', () => {if (window.confirm("Do you really want to restore the VM?")) vm.admin.restore()});
buttons.reboot.addEventListener('click', () => vm.admin.reboot());
buttons.clearQueue.addEventListener('click', () => vm.admin.clearQueue());
buttons.bypassTurn.addEventListener('click', () => vm.admin.bypassTurn());
buttons.endTurn.addEventListener('click', () => vm.admin.endTurn(users[0]));
buttons.forceVoteYes.addEventListener('click', () => vm.admin.forceVote(true));
buttons.forceVoteNo.addEventListener('click', () => vm.admin.forceVote(false));
// QEMU Monitor Shit
function sendQEMUCommand() {
if (!qemuMonitorInput.value) return;
vm.admin.qemuMonitor(qemuMonitorInput.value);
qemuMonitorInput.value = "";
}
qemuMonitorInput.addEventListener('keypress', (e) => {
if (e.key === "Enter") sendQEMUCommand();
});
buttons.qemuMonitorSend.addEventListener('click', () => sendQEMUCommand());
// Login
var usernameClick = false;
usernameSpan.addEventListener('click', () => {
if (!usernameClick) {
usernameClick = true;
setInterval(() => {usernameClick = false;}, 1000);
return;
}
var pass = window.prompt("🔑");
if (!pass) return;
vm.admin.login(pass);
});
// Load all vms
(async () => {
var p = [];
config.serverAddresses.forEach(v => p.push(multicollab(v)));
await Promise.all(p);
var vm = vms.find(v => v.id === window.location.hash.substring(1));
if (vm)
openVM(vm.url, vm.id);
})();
// Export some stuff
window.screenshotVM = screenshotVM;
window.multicollab = multicollab;
window.getPerms = () => perms;
window.getRank = () => rank;
window.GetAdmin = () => vm.admin;
window.cvmEvents = createNanoEvents();
window.VMName = null;
// Welcome modal
var noWelcomeModal = window.localStorage.getItem("no-welcome-modal");
if (noWelcomeModal !== "1") {
var welcomeModalDismissBtn = document.getElementById("welcomeModalDismiss");
var welcomeModal = new bootstrap.Modal(document.getElementById("welcomeModal"));
welcomeModalDismissBtn.addEventListener("click", () => {
window.localStorage.setItem("no-welcome-modal", 1);
});
welcomeModalDismissBtn.disabled = true;
welcomeModal.show();
setTimeout(() => {
welcomeModalDismissBtn.disabled = false;
}, 5000);
}

280
src/keyboard.js Normal file
View File

@ -0,0 +1,280 @@
// Pulled a bunch of functions out of the guac source code to get a keysym
// and then a wrapper
// shitty but it works so /shrug
export function GetKeysym(keyCode, keyIdentifier, key, location) {
var keysym = keysym_from_key_identifier(key, location)
|| keysym_from_keycode(keyCode, location);
if (!keysym && key_identifier_sane(keyCode, keyIdentifier))
keysym = keysym_from_key_identifier(keyIdentifier, location);
return keysym;
}
function keysym_from_key_identifier(identifier, location) {
if (!identifier)
return null;
var typedCharacter;
// If identifier is U+xxxx, decode Unicode character
var unicodePrefixLocation = identifier.indexOf("U+");
if (unicodePrefixLocation >= 0) {
var hex = identifier.substring(unicodePrefixLocation+2);
typedCharacter = String.fromCharCode(parseInt(hex, 16));
}
// If single character, use that as typed character
else if (identifier.length === 1)
typedCharacter = identifier;
// Otherwise, look up corresponding keysym
else
return get_keysym(keyidentifier_keysym[identifier], location);
// Get codepoint
var codepoint = typedCharacter.charCodeAt(0);
return keysym_from_charcode(codepoint);
}
function get_keysym(keysyms, location) {
if (!keysyms)
return null;
return keysyms[location] || keysyms[0];
}
function keysym_from_charcode(codepoint) {
// Keysyms for control characters
if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;
// Keysyms for ASCII chars
if (codepoint >= 0x0000 && codepoint <= 0x00FF)
return codepoint;
// Keysyms for Unicode
if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)
return 0x01000000 | codepoint;
return null;
}
function isControlCharacter(codepoint) {
return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);
}
function keysym_from_keycode(keyCode, location) {
return get_keysym(keycodeKeysyms[keyCode], location);
}
function key_identifier_sane(keyCode, keyIdentifier) {
// Missing identifier is not sane
if (!keyIdentifier)
return false;
// Assume non-Unicode keyIdentifier values are sane
var unicodePrefixLocation = keyIdentifier.indexOf("U+");
if (unicodePrefixLocation === -1)
return true;
// If the Unicode codepoint isn't identical to the keyCode,
// then the identifier is likely correct
var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);
if (keyCode !== codepoint)
return true;
// The keyCodes for A-Z and 0-9 are actually identical to their
// Unicode codepoints
if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))
return true;
// The keyIdentifier does NOT appear sane
return false;
}
var keycodeKeysyms = {
8: [0xFF08], // backspace
9: [0xFF09], // tab
12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5
13: [0xFF0D], // enter
16: [0xFFE1, 0xFFE1, 0xFFE2], // shift
17: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl
18: [0xFFE9, 0xFFE9, 0xFE03], // alt
19: [0xFF13], // pause/break
20: [0xFFE5], // caps lock
27: [0xFF1B], // escape
32: [0x0020], // space
33: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 9
34: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 3
35: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 1
36: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 7
37: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 4
38: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 8
39: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6
40: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 2
45: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 0
46: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal
91: [0xFFEB], // left window key (hyper_l)
92: [0xFF67], // right window key (menu key?)
93: null, // select key
96: [0xFFB0], // KP 0
97: [0xFFB1], // KP 1
98: [0xFFB2], // KP 2
99: [0xFFB3], // KP 3
100: [0xFFB4], // KP 4
101: [0xFFB5], // KP 5
102: [0xFFB6], // KP 6
103: [0xFFB7], // KP 7
104: [0xFFB8], // KP 8
105: [0xFFB9], // KP 9
106: [0xFFAA], // KP multiply
107: [0xFFAB], // KP add
109: [0xFFAD], // KP subtract
110: [0xFFAE], // KP decimal
111: [0xFFAF], // KP divide
112: [0xFFBE], // f1
113: [0xFFBF], // f2
114: [0xFFC0], // f3
115: [0xFFC1], // f4
116: [0xFFC2], // f5
117: [0xFFC3], // f6
118: [0xFFC4], // f7
119: [0xFFC5], // f8
120: [0xFFC6], // f9
121: [0xFFC7], // f10
122: [0xFFC8], // f11
123: [0xFFC9], // f12
144: [0xFF7F], // num lock
145: [0xFF14], // scroll lock
225: [0xFE03] // altgraph (iso_level3_shift)
};
var keyidentifier_keysym = {
"Again": [0xFF66],
"AllCandidates": [0xFF3D],
"Alphanumeric": [0xFF30],
"Alt": [0xFFE9, 0xFFE9, 0xFE03],
"Attn": [0xFD0E],
"AltGraph": [0xFE03],
"ArrowDown": [0xFF54],
"ArrowLeft": [0xFF51],
"ArrowRight": [0xFF53],
"ArrowUp": [0xFF52],
"Backspace": [0xFF08],
"CapsLock": [0xFFE5],
"Cancel": [0xFF69],
"Clear": [0xFF0B],
"Convert": [0xFF21],
"Copy": [0xFD15],
"Crsel": [0xFD1C],
"CrSel": [0xFD1C],
"CodeInput": [0xFF37],
"Compose": [0xFF20],
"Control": [0xFFE3, 0xFFE3, 0xFFE4],
"ContextMenu": [0xFF67],
"DeadGrave": [0xFE50],
"DeadAcute": [0xFE51],
"DeadCircumflex": [0xFE52],
"DeadTilde": [0xFE53],
"DeadMacron": [0xFE54],
"DeadBreve": [0xFE55],
"DeadAboveDot": [0xFE56],
"DeadUmlaut": [0xFE57],
"DeadAboveRing": [0xFE58],
"DeadDoubleacute": [0xFE59],
"DeadCaron": [0xFE5A],
"DeadCedilla": [0xFE5B],
"DeadOgonek": [0xFE5C],
"DeadIota": [0xFE5D],
"DeadVoicedSound": [0xFE5E],
"DeadSemivoicedSound": [0xFE5F],
"Delete": [0xFFFF],
"Down": [0xFF54],
"End": [0xFF57],
"Enter": [0xFF0D],
"EraseEof": [0xFD06],
"Escape": [0xFF1B],
"Execute": [0xFF62],
"Exsel": [0xFD1D],
"ExSel": [0xFD1D],
"F1": [0xFFBE],
"F2": [0xFFBF],
"F3": [0xFFC0],
"F4": [0xFFC1],
"F5": [0xFFC2],
"F6": [0xFFC3],
"F7": [0xFFC4],
"F8": [0xFFC5],
"F9": [0xFFC6],
"F10": [0xFFC7],
"F11": [0xFFC8],
"F12": [0xFFC9],
"F13": [0xFFCA],
"F14": [0xFFCB],
"F15": [0xFFCC],
"F16": [0xFFCD],
"F17": [0xFFCE],
"F18": [0xFFCF],
"F19": [0xFFD0],
"F20": [0xFFD1],
"F21": [0xFFD2],
"F22": [0xFFD3],
"F23": [0xFFD4],
"F24": [0xFFD5],
"Find": [0xFF68],
"GroupFirst": [0xFE0C],
"GroupLast": [0xFE0E],
"GroupNext": [0xFE08],
"GroupPrevious": [0xFE0A],
"FullWidth": null,
"HalfWidth": null,
"HangulMode": [0xFF31],
"Hankaku": [0xFF29],
"HanjaMode": [0xFF34],
"Help": [0xFF6A],
"Hiragana": [0xFF25],
"HiraganaKatakana": [0xFF27],
"Home": [0xFF50],
"Hyper": [0xFFED, 0xFFED, 0xFFEE],
"Insert": [0xFF63],
"JapaneseHiragana": [0xFF25],
"JapaneseKatakana": [0xFF26],
"JapaneseRomaji": [0xFF24],
"JunjaMode": [0xFF38],
"KanaMode": [0xFF2D],
"KanjiMode": [0xFF21],
"Katakana": [0xFF26],
"Left": [0xFF51],
"Meta": [0xFFE7, 0xFFE7, 0xFFE8],
"ModeChange": [0xFF7E],
"NumLock": [0xFF7F],
"PageDown": [0xFF56],
"PageUp": [0xFF55],
"Pause": [0xFF13],
"Play": [0xFD16],
"PreviousCandidate": [0xFF3E],
"PrintScreen": [0xFD1D],
"Redo": [0xFF66],
"Right": [0xFF53],
"RomanCharacters": null,
"Scroll": [0xFF14],
"Select": [0xFF60],
"Separator": [0xFFAC],
"Shift": [0xFFE1, 0xFFE1, 0xFFE2],
"SingleCandidate": [0xFF3C],
"Super": [0xFFEB, 0xFFEB, 0xFFEC],
"Tab": [0xFF09],
"Up": [0xFF52],
"Undo": [0xFF65],
"Win": [0xFFEB],
"Zenkaku": [0xFF28],
"ZenkakuHankaku": [0xFF2A]
};

25
src/permissions.js Normal file
View File

@ -0,0 +1,25 @@
export function makeperms(mask, config) {
const perms = {
restore: false,
reboot: false,
ban: false,
forcevote: false,
mute: false,
kick: false,
bypassturn: false,
rename: false,
grabip: false,
xss: false
};
if ((mask & 1) !== 0) perms.restore = true;
if ((mask & 2) !== 0) perms.reboot = true;
if ((mask & 4) !== 0) perms.ban = true;
if ((mask & 8) !== 0) perms.forcevote = true;
if ((mask & 16) !== 0) perms.mute = true;
if ((mask & 32) !== 0) perms.kick = true;
if ((mask & 64) !== 0) perms.bypassturn = true;
if ((mask & 128) !== 0) perms.rename = true;
if ((mask & 256) !== 0) perms.grabip = true;
if (config.xssImplementation === 2 && (mask & 512) !== 0) perms.xss = true;
return perms;
}

45
src/protocol.js Normal file
View File

@ -0,0 +1,45 @@
export const guacutils = {
decode: (string) => {
let pos = -1;
let sections = [];
for(;;) {
let len = string.indexOf('.', pos + 1);
if(len === -1)
break;
pos = parseInt(string.slice(pos + 1, len)) + len + 1;
// don't allow funky protocol length
if(pos > string.length)
return [];
sections.push(string.slice(len + 1, pos));
const sep = string.slice(pos, pos + 1);
if(sep === ',')
continue;
else if(sep === ';')
break;
else
// Invalid data.
return [];
}
return sections;
},
encode: (string) => {
let command = '';
for(var i = 0; i < string.length; i++) {
let current = string[i];
command += current.toString().length + '.' + current;
command += ( i < string.length - 1 ? ',' : ';');
}
return command;
}
};

View File

@ -1,256 +0,0 @@
import * as dayjs from 'dayjs';
export default class AuthManager {
apiEndpoint : string;
info : AuthServerInformation | null;
account : Account | null;
constructor(apiEndpoint : string) {
this.apiEndpoint = apiEndpoint;
this.info = null;
this.account = null;
}
getAPIInformation() : Promise<AuthServerInformation> {
return new Promise(async res => {
var data = await fetch(this.apiEndpoint + "/api/v1/info");
this.info = await data.json();
res(this.info!);
})
}
login(username : string, password : string, captchaToken : string | undefined) : Promise<AccountLoginResult> {
return new Promise(async (res,rej) => {
if (!this.info) throw new Error("Cannot login before fetching API information.");
if (!captchaToken && this.info.hcaptcha.required) throw new Error("This API requires a valid hCaptcha token.");
var data = await fetch(this.apiEndpoint + "/api/v1/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
password: password,
captchaToken: captchaToken
})
});
var json = await data.json() as AccountLoginResult;
if (!json) throw new Error("data.json() gave null or undefined result");
if (json.success && !json.verificationRequired) {
this.account = {
username: json.username!,
email: json.email!,
sessionToken: json.token!
}
}
res(json);
})
}
loadSession(token : string) {
return new Promise<SessionResult>(async (res, rej) => {
var data = await fetch(this.apiEndpoint + "/api/v1/session", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
token: token,
})
});
var json = await data.json() as SessionResult;
if (json.success) {
this.account = {
sessionToken: token,
username: json.username!,
email: json.email!,
};
}
res(json);
})
}
register(username : string, password : string, email : string, dateOfBirth : dayjs.Dayjs, captchaToken : string | undefined) : Promise<AccountRegisterResult> {
return new Promise(async (res, rej) => {
if (!this.info) throw new Error("Cannot login before fetching API information.");
if (!captchaToken && this.info.hcaptcha.required) throw new Error("This API requires a valid hCaptcha token.");
var data = await fetch(this.apiEndpoint + "/api/v1/register", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
password: password,
email: email,
dateOfBirth: dateOfBirth.format("YYYY-MM-DD"),
captchatoken: captchaToken
})
});
res(await data.json() as AccountRegisterResult);
});
}
logout() {
return new Promise<LogoutResult>(async res => {
if (!this.account) throw new Error("Cannot log out without logging in first");
var data = await fetch(this.apiEndpoint + "/api/v1/logout", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
token: this.account.sessionToken
})
});
var json = await data.json() as LogoutResult;
this.account = null;
res(json);
})
}
verifyEmail(username : string, password : string, code : string) {
return new Promise<VerifyEmailResult>(async res => {
var data = await fetch(this.apiEndpoint + "/api/v1/verify", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
password: password,
code: code,
})
});
res(await data.json() as VerifyEmailResult);
});
}
updateAccount(currentPassword : string, newEmail : string | undefined, newUsername : string | undefined, newPassword : string | undefined) {
return new Promise<UpdateAccountResult>(async res => {
if (!this.account) throw new Error("Cannot update account without being logged in.");
if (!newEmail && !newUsername && !newPassword) throw new Error("Cannot update account without any new information.");
var data = await fetch(this.apiEndpoint + "/api/v1/update", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
token: this.account!.sessionToken,
currentPassword: currentPassword,
newPassword: newPassword,
username: newUsername,
email: newEmail,
})
});
var json = await data.json() as UpdateAccountResult;
if (json.success) {
if (this.account!.email !== newEmail) this.account!.email = newEmail!;
if (this.account!.username !== newUsername) this.account!.username = newUsername!;
if (json.sessionExpired || json.verificationRequired) {
this.account = null;
}
}
res(json);
});
}
sendPasswordResetEmail(username : string, email : string, captchaToken : string | undefined) {
return new Promise<PasswordResetResult>(async res => {
if (!this.info) throw new Error("Cannot send password reset email without fetching API information.");
if (!captchaToken && this.info.hcaptcha.required) throw new Error("This API requires a valid hCaptcha token.");
var data = await fetch(this.apiEndpoint + "/api/v1/sendreset", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
email: email,
captchaToken: captchaToken
})
});
res(await data.json() as PasswordResetResult);
});
}
resetPassword(username : string, email : string, code : string, newPassword : string) {
return new Promise<PasswordResetResult>(async res => {
var data = await fetch(this.apiEndpoint + "/api/v1/reset", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
email: email,
code: code,
newPassword: newPassword
})
});
res(await data.json() as PasswordResetResult);
});
}
}
export interface AuthServerInformation {
registrationOpen : boolean;
hcaptcha : {
required : boolean;
siteKey : string | undefined;
};
}
export interface AccountRegisterResult {
success : boolean;
error : string | undefined;
verificationRequired : boolean | undefined;
username : string | undefined;
email : string | undefined;
sessionToken : string | undefined;
}
export interface AccountLoginResult {
success : boolean;
token : string | undefined;
error : string | undefined;
verificationRequired : boolean | undefined;
email : string | undefined;
username : string | undefined;
}
export interface SessionResult {
success : boolean;
error : string | undefined;
banned : boolean;
username : string | undefined;
email : string | undefined;
}
export interface VerifyEmailResult {
success : boolean;
error : string | undefined;
sessionToken : string | undefined;
}
export interface LogoutResult {
success : boolean;
error : string | undefined;
}
export interface Account {
username : string;
email : string;
sessionToken : string;
}
export interface UpdateAccountResult {
success : boolean;
error : string | undefined;
verificationRequired : boolean | undefined;
sessionExpired : boolean | undefined;
}
export interface PasswordResetResult {
success : boolean;
error : string | undefined;
}

View File

@ -1,8 +0,0 @@
// TODO: `Object` has a toString(), but we should probably gate that off
/// Interface for things that can be turned into strings
export interface ToStringable {
toString(): string;
}
/// A type for strings, or things that can (in a valid manner) be turned into strings
export type StringLike = string | ToStringable;

View File

@ -1,104 +0,0 @@
import { Language } from "./i18n.js";
const fallbackLanguage : Language = {
"languageName": "English (US)",
"translatedLanguageName": "English (US)",
"flag": "🇺🇸",
"author": "Computernewb",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Yes",
"kGeneric_No": "No",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Cancel",
"kGeneric_Send": "Send",
"kGeneric_Understood": "Understood",
"kGeneric_Username": "Username",
"kGeneric_Password": "Password",
"kGeneric_Login": "Log in",
"kGeneric_Register": "Register",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Date of Birth",
"kGeneric_VerificationCode": "Verification Code",
"kGeneric_Verify": "Verify",
"kGeneric_Update": "Update",
"kGeneric_Logout": "Log out",
"kWelcomeModal_Header": "Welcome to CollabVM",
"kWelcomeModal_Body": "<p>Before continuing, please familiarize yourself with our rules:</p> <h3>R1. Don't break the law.</h3> Do not use CollabVM or CollabVM's network to violate United States federal law, New York state law, or international law. If CollabVM becomes aware a crime has been committed through its service, you will be immediately banned, and your activities may be reported to the authorities if necessary.<br><br>CollabVM is required by law to notify law enforcement agencies if it becomes aware of the presence of child pornography on, or being transmitted through its network.<br><br>COPPA is also enforced, please do not use CollabVM if you are under the age of 13 years old. <h3>R2. No running DoS/DDoS tools.</h3> Do not use CollabVM to DoS/DDoS an indivdiual, business, company, or anyone else. <h3>R3. No spam distribution.</h3> Do not spam any emails using this service or push spam in general. <h3>R4. Do not abuse any exploits.</h3> Do not abuse any exploits, additionally if you see someone abusing exploits or you need to report one, please contact me at: computernewbab@gmail.com <h3>R5. Don't impersonate other users.</h3> Do not impersonate other members of CollabVM. If caught, you'll be temporarily disconnected, and banned if necessary. <h3>R6. One vote per person.</h3> Do not use any methods or tools to bypass the vote restriction. Only one vote per person is allowed, no matter what. Anybody who is caught doing this will be banned. <h3>R7. No Remote Administration Tools.</h3> Do not use any remote administration tools (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.) <h3>R8. No bypassing CollabNet.</h3> Do not attempt to bypass the blocking provided by CollabNet, especially if it is being used to break Rule 1, Rule 2, or Rule 7 (or run stupid over-used things). <h3>R9. No performing destructive actions constantly.</h3> Any user may not destroy the VM (rendering it unusable constantly), install/reinstall the operating system (except on VM7 or VM8), or run bots that do such. This includes bots that spam massive amounts of keyboard/mouse input (\"kitting\"). <h3>R10. No Cryptomining</h3> Attempting to mine cryptocurrency on the VMs will result in a kick, and then a permanent ban if you keep attempting. Besides, it's not like you're gonna make any money off it. <h3>NSFW Warning</h3> Please note that NSFW content is allowed on our anarchy VM (VM0b0t), and is viewed regularly. In addition, while we give a good effort to keep NSFW off the main VMs, people will occasionally slip it through.",
"kSiteButtons_Home": "Home",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Rules",
"kSiteButtons_DarkMode": "Dark Mode",
"kSiteButtons_LightMode": "Light Mode",
"kVM_UsersOnlineText": "Users Online:",
"kVM_TurnTimeTimer": "Turn expires in {0} seconds.",
"kVM_WaitingTurnTimer": "Waiting for turn in {0} seconds.",
"kVM_VoteCooldownTimer": "Please wait {0} seconds before starting another vote.",
"kVM_VoteForResetTitle": "Do you want to reset the VM?",
"kVM_VoteForResetTimer": "Vote ends in {0} seconds",
"kVMButtons_TakeTurn": "Take Turn",
"kVMButtons_EndTurn": "End Turn",
"kVMButtons_ChangeUsername": "Change Username",
"kVMButtons_Keyboard": "Keyboard",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Vote For Reset",
"kVMButtons_Screenshot": "Screenshot",
"kQEMUMonitor": "QEMU Monitor",
"kAdminVMButtons_PassVote": "Pass Vote",
"kAdminVMButtons_CancelVote": "Cancel Vote",
"kAdminVMButtons_Restore": "Restore",
"kAdminVMButtons_Reboot": "Reboot",
"kAdminVMButtons_ClearTurnQueue": "Clear Turn Queue",
"kAdminVMButtons_BypassTurn": "Bypass Turn",
"kAdminVMButtons_IndefiniteTurn": "Indefinite Turn",
"kAdminVMButtons_GhostTurnOn": "Ghost Turn (On)",
"kAdminVMButtons_GhostTurnOff": "Ghost Turn (Off)",
"kAdminVMButtons_Ban": "Ban",
"kAdminVMButtons_Kick": "Kick",
"kAdminVMButtons_TempMute": "Temporary Mute",
"kAdminVMButtons_IndefMute": "Indefinite Mute",
"kAdminVMButtons_Unmute": "Unmute",
"kAdminVMButtons_GetIP": "Get IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Enter new username for {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Are you sure you want to restore the VM?",
"kVMPrompts_EnterNewUsernamePrompt": "Enter a new username, or leave the field blank to be assigned a guest username",
"kError_UnexpectedDisconnection": "You have been disconnected from the server.",
"kError_UsernameTaken": "That username is already taken",
"kError_UsernameInvalid": "Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters.",
"kError_UsernameBlacklisted": "That username has been blacklisted.",
"kError_IncorrectPassword": "Incorrect password.",
"kAccountModal_Verify": "Verify E-Mail",
"kAccountModal_AccountSettings": "Account Settings",
"kAccountModal_ResetPassword": "Reset Password",
"kAccountModal_NewPassword": "New Password",
"kAccountModal_ConfirmNewPassword": "Confirm New Password",
"kAccountModal_CurrentPassword": "Current Password",
"kAccountModal_ConfirmPassword": "Confirm Password",
"kAccountModal_HideFlag": "Hide my Country Flag",
"kMissingCaptcha": "Please fill out the captcha.",
"kPasswordsMustMatch": "Passwords must match.",
"kAccountModal_VerifyText": "We sent an E-Mail to {0}. To verify your account, please enter the 8-digit code from the E-Mail below.",
"kAccountModal_VerifyPasswordResetText": "We sent an E-Mail to {0}. To reset your password, please enter the 8-digit code from the E-Mail below.",
"kAccountModal_PasswordResetSuccess": "Your password has been changed successfully. You can now log in with your new password.",
"kNotLoggedIn": "Not Logged in"
}
}
export default fallbackLanguage;

View File

@ -1,77 +0,0 @@
import { StringLike } from './StringLike';
function isalpha(char: number) {
return RegExp(/^\p{L}/, 'u').test(String.fromCharCode(char));
}
/// A simple function for formatting strings in a more expressive manner.
/// While JavaScript *does* have string interpolation, it's not a total replacement
/// for just formatting strings, and a method like this is better for data independent formatting.
///
/// ## Example usage
///
/// ```typescript
/// let hello = Format("Hello, {0}!", "World");
/// ```
export function Format(pattern: string, ...args: Array<StringLike>) {
let argumentsAsStrings: Array<string> = [...args].map((el) => {
// This catches cases where the thing already is a string
if (typeof el == 'string') return el as string;
return el.toString();
});
let pat = pattern;
// Handle pattern ("{0} {1} {2} {3} {4} {5}") syntax if found
for (let i = 0; i < pat.length; ++i) {
if (pat[i] == '{') {
let replacementStart = i;
let foundSpecifierEnd = false;
// Make sure the specifier is not cut off (the last character of the string)
if (i + 3 > pat.length) {
throw new Error(`Error in format pattern "${pat}": Cutoff/invalid format specifier`);
}
// Try and find the specifier end ('}').
// Whitespace and a '{' are considered errors.
for (let j = i + 1; j < pat.length; ++j) {
switch (pat[j]) {
case '}':
foundSpecifierEnd = true;
i = j;
break;
case '{':
throw new Error(`Error in format pattern "${pat}": Cannot start a format specifier in an existing replacement`);
case ' ':
throw new Error(`Error in format pattern "${pat}": Whitespace inside format specifier`);
case '-':
throw new Error(`Error in format pattern "${pat}": Malformed format specifier`);
default:
if (isalpha(pat.charCodeAt(j))) throw new Error(`Error in format pattern "${pat}": Malformed format specifier`);
break;
}
if (foundSpecifierEnd) break;
}
if (!foundSpecifierEnd) throw new Error(`Error in format pattern "${pat}": No terminating "}" character found`);
// Get the beginning and trailer
let beginning = pat.substring(0, replacementStart);
let trailer = pat.substring(replacementStart + 3);
let argumentIndex = parseInt(pat.substring(replacementStart + 1, i));
if (Number.isNaN(argumentIndex) || argumentIndex > argumentsAsStrings.length) throw new Error(`Error in format pattern "${pat}": Argument index out of bounds`);
// This is seriously the only decent way to do this in javascript
// thanks brendan eich (replace this thanking with more choice words in your head)
pat = beginning + argumentsAsStrings[argumentIndex] + trailer;
}
}
return pat;
}

View File

@ -1,470 +0,0 @@
import { StringLike } from './StringLike';
import { Format } from './format';
import { Emitter, Unsubscribe, createNanoEvents } from 'nanoevents';
import Config from '../../config.json';
/// All string keys.
export enum I18nStringKey {
// Generic things
kGeneric_CollabVM = 'kGeneric_CollabVM',
kGeneric_Yes = 'kGeneric_Yes',
kGeneric_No = 'kGeneric_No',
kGeneric_Ok = 'kGeneric_Ok',
kGeneric_Cancel = 'kGeneric_Cancel',
kGeneric_Send = 'kGeneric_Send',
kGeneric_Understood = 'kGeneric_Understood',
kGeneric_Username = 'kGeneric_Username',
kGeneric_Password = 'kGeneric_Password',
kGeneric_Login = 'kGeneric_Login',
kGeneric_Register = 'kGeneric_Register',
kGeneric_EMail = 'kGeneric_EMail',
kGeneric_DateOfBirth = 'kGeneric_DateOfBirth',
kGeneric_VerificationCode = 'kGeneric_VerificationCode',
kGeneric_Verify = 'kGeneric_Verify',
kGeneric_Update = 'kGeneric_Update',
kGeneric_Logout = 'kGeneric_Logout',
kWelcomeModal_Header = 'kWelcomeModal_Header',
kWelcomeModal_Body = 'kWelcomeModal_Body',
kSiteButtons_Home = 'kSiteButtons_Home',
kSiteButtons_FAQ = 'kSiteButtons_FAQ',
kSiteButtons_Rules = 'kSiteButtons_Rules',
kSiteButtons_DarkMode = 'kSiteButtons_DarkMode',
kSiteButtons_LightMode = 'kSiteButtons_LightMode',
kSiteButtons_Languages = 'kSiteButtons_Languages',
kVM_UsersOnlineText = 'kVM_UsersOnlineText',
kVM_TurnTimeTimer = 'kVM_TurnTimeTimer',
kVM_WaitingTurnTimer = 'kVM_WaitingTurnTimer',
kVM_VoteCooldownTimer = 'kVM_VoteCooldownTimer',
kVM_VoteForResetTitle = 'kVM_VoteForResetTitle',
kVM_VoteForResetTimer = 'kVM_VoteForResetTimer',
kVMButtons_TakeTurn = 'kVMButtons_TakeTurn',
kVMButtons_EndTurn = 'kVMButtons_EndTurn',
kVMButtons_ChangeUsername = 'kVMButtons_ChangeUsername',
kVMButtons_Keyboard = 'kVMButtons_Keyboard',
KVMButtons_CtrlAltDel = 'KVMButtons_CtrlAltDel',
kVMButtons_VoteForReset = 'kVMButtons_VoteForReset',
kVMButtons_Screenshot = 'kVMButtons_Screenshot',
// Admin VM buttons
kQEMUMonitor = 'kQEMUMonitor',
kAdminVMButtons_PassVote = 'kAdminVMButtons_PassVote',
kAdminVMButtons_CancelVote = 'kAdminVMButtons_CancelVote',
kAdminVMButtons_Restore = 'kAdminVMButtons_Restore',
kAdminVMButtons_Reboot = 'kAdminVMButtons_Reboot',
kAdminVMButtons_ClearTurnQueue = 'kAdminVMButtons_ClearTurnQueue',
kAdminVMButtons_BypassTurn = 'kAdminVMButtons_BypassTurn',
kAdminVMButtons_IndefiniteTurn = 'kAdminVMButtons_IndefiniteTurn',
kAdminVMButtons_GhostTurnOn = 'kAdminVMButtons_GhostTurnOn',
kAdminVMButtons_GhostTurnOff = 'kAdminVMButtons_GhostTurnOff',
kAdminVMButtons_Ban = 'kAdminVMButtons_Ban',
kAdminVMButtons_Kick = 'kAdminVMButtons_Kick',
kAdminVMButtons_TempMute = 'kAdminVMButtons_TempMute',
kAdminVMButtons_IndefMute = 'kAdminVMButtons_IndefMute',
kAdminVMButtons_Unmute = 'kAdminVMButtons_Unmute',
kAdminVMButtons_GetIP = 'kAdminVMButtons_GetIP',
// prompts
kVMPrompts_AdminChangeUsernamePrompt = 'kVMPrompts_AdminChangeUsernamePrompt',
kVMPrompts_AdminRestoreVMPrompt = 'kVMPrompts_AdminRestoreVMPrompt',
kVMPrompts_EnterNewUsernamePrompt = 'kVMPrompts_EnterNewUsernamePrompt',
// error messages
kError_UnexpectedDisconnection = 'kError_UnexpectedDisconnection',
kError_UsernameTaken = 'kError_UsernameTaken',
kError_UsernameInvalid = 'kError_UsernameInvalid',
kError_UsernameBlacklisted = 'kError_UsernameBlacklisted',
kError_IncorrectPassword = 'kError_IncorrectPassword',
// Auth
kAccountModal_Verify = 'kAccountModal_Verify',
kAccountModal_AccountSettings = 'kAccountModal_AccountSettings',
kAccountModal_ResetPassword = 'kAccountModal_ResetPassword',
kAccountModal_NewPassword = 'kAccountModal_NewPassword',
kAccountModal_ConfirmNewPassword = 'kAccountModal_ConfirmNewPassword',
kAccountModal_CurrentPassword = 'kAccountModal_CurrentPassword',
kAccountModal_ConfirmPassword = 'kAccountModal_ConfirmPassword',
kAccountModal_HideFlag = 'kAccountModal_HideFlag',
kAccountModal_VerifyText = 'kAccountModal_VerifyText',
kAccountModal_VerifyPasswordResetText = 'kAccountModal_VerifyPasswordResetText',
kAccountModal_PasswordResetSuccess = 'kAccountModal_PasswordResetSuccess',
kMissingCaptcha = 'kMissingCaptcha',
kPasswordsMustMatch = 'kPasswordsMustMatch',
kNotLoggedIn = 'kNotLoggedIn',
}
export interface I18nEvents {
// Called when the language is changed
languageChanged: (lang: string) => void;
}
// This models the JSON structure.
export type Language = {
languageName: string;
translatedLanguageName: string;
flag: string; // country flag, can be blank if not applicable. will be displayed in language dropdown
author: string;
stringKeys: {
// This is fancy typescript speak for
// "any string index returns a string",
// which is our expectation.
// See https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures if this is confusing.
[key: string]: string;
};
};
export type LanguageMetadata = {
languageName: string;
flag: string; // country flag, can be blank if not applicable. will be displayed in language dropdown
};
// `languages.json`
export type LanguagesJson = {
// Array of language IDs to allow loading
languages: {[key: string]: LanguageMetadata};
// The default language (set if a invalid language not in the languages array is set, or no language is set)
defaultLanguage: string;
};
// ID for fallback language
const fallbackId = '!!fallback';
// This language is provided in the webapp itself just in case language stuff fails
import fallbackLanguage from './fallbackLanguage.js';
interface StringKeyMap {
[k: string]: I18nStringKey;
}
/// our fancy internationalization helper.
export class I18n {
// The language data itself
private langs : Map<string, LanguageMetadata> = new Map<string, Language>();
private lang: Language = fallbackLanguage;
private languageDropdown: HTMLSpanElement = document.getElementById('languageDropdown') as HTMLSpanElement;
private emitter: Emitter<I18nEvents> = createNanoEvents();
CurrentLanguage = () => this.langId;
// the ID of the language
private langId: string = fallbackId;
private regionNameRenderer = new Intl.DisplayNames(['en-US'], {type: 'region'});
async Init() {
// Load language list
var res = await fetch("lang/languages.json");
if (!res.ok) {
alert("Failed to load languages.json: " + res.statusText);
await this.SetLanguage(fallbackId);
this.ReplaceStaticStrings();
return;
}
var langData = await res.json() as LanguagesJson;
for (const langId in langData.languages) {
this.langs.set(langId, langData.languages[langId]);
}
this.langs.forEach((_lang, langId) => {
// Add to language dropdown
var a = document.createElement('a');
a.classList.add('dropdown-item');
a.href = '#';
a.innerText = `${_lang.flag} ${_lang.languageName}`;
a.addEventListener('click', async e => {
e.preventDefault();
await this.SetLanguage(langId);
this.ReplaceStaticStrings();
});
this.languageDropdown.appendChild(a);
});
let lang = null;
let lsLang = window.localStorage.getItem('i18n-lang');
var browserLang = navigator.language.toLowerCase();
// If the language is set in localstorage, use that
if (lsLang !== null && this.langs.has(lsLang)) lang = lsLang;
// If the browser language is in the list, use that
else if (this.langs.has(browserLang)) lang = browserLang;
else {
// If the exact browser language isn't in the list, try to find a language with the same prefix
for (let langId in langData.languages) {
if (langId.split('-')[0] === browserLang.split('-')[0]) {
lang = langId;
break;
}
}
}
// If all else fails, use the default language
if (lang === null) lang = langData.defaultLanguage;
await this.SetLanguage(lang);
this.ReplaceStaticStrings();
}
getCountryName(code: string) : string {
return this.regionNameRenderer.of(code) || code;
}
private async SetLanguage(id: string) {
let lastId = this.langId;
this.langId = id;
let lang;
if (id === fallbackId) lang = fallbackLanguage;
else {
let path = `./lang/${id}.json`;
let res = await fetch(path);
if (!res.ok) {
console.error(`Failed to load lang/${id}.json: ${res.statusText}`);
await this.SetLanguage(fallbackId);
return;
}
lang = await res.json() as Language;
}
this.lang = lang;
if (this.langId != lastId) {
// Replace static strings
this.ReplaceStaticStrings();
// Update region name renderer target language
this.regionNameRenderer = new Intl.DisplayNames([this.langId], {type: 'region'});
};
// Set the language ID localstorage entry
if (this.langId !== fallbackId) {
window.localStorage.setItem('i18n-lang', this.langId);
}
this.emitter.emit('languageChanged', this.langId);
console.log('i18n initalized for', id, 'sucessfully!');
}
// Replaces static strings that we don't recompute
private ReplaceStaticStrings() {
const kDomIdtoStringMap: StringKeyMap = {
siteNameText: I18nStringKey.kGeneric_CollabVM,
homeBtnText: I18nStringKey.kSiteButtons_Home,
faqBtnText: I18nStringKey.kSiteButtons_FAQ,
rulesBtnText: I18nStringKey.kSiteButtons_Rules,
accountLoginButton: I18nStringKey.kGeneric_Login,
accountRegisterButton: I18nStringKey.kGeneric_Register,
accountSettingsButton: I18nStringKey.kAccountModal_AccountSettings,
accountLogoutButton: I18nStringKey.kGeneric_Logout,
languageDropdownText: I18nStringKey.kSiteButtons_Languages,
welcomeModalHeader: I18nStringKey.kWelcomeModal_Header,
welcomeModalBody: I18nStringKey.kWelcomeModal_Body,
welcomeModalDismiss: I18nStringKey.kGeneric_Understood,
usersOnlineText: I18nStringKey.kVM_UsersOnlineText,
voteResetHeaderText: I18nStringKey.kVM_VoteForResetTitle,
voteYesBtnText: I18nStringKey.kGeneric_Yes,
voteNoBtnText: I18nStringKey.kGeneric_No,
changeUsernameBtnText: I18nStringKey.kVMButtons_ChangeUsername,
oskBtnText: I18nStringKey.kVMButtons_Keyboard,
ctrlAltDelBtnText: I18nStringKey.KVMButtons_CtrlAltDel,
voteForResetBtnText: I18nStringKey.kVMButtons_VoteForReset,
screenshotBtnText: I18nStringKey.kVMButtons_Screenshot,
// admin stuff
badPasswordAlertText: I18nStringKey.kError_IncorrectPassword,
loginModalPasswordText: I18nStringKey.kGeneric_Password,
loginButton: I18nStringKey.kGeneric_Login,
passVoteBtnText: I18nStringKey.kAdminVMButtons_PassVote,
cancelVoteBtnText: I18nStringKey.kAdminVMButtons_CancelVote,
endTurnBtnText: I18nStringKey.kVMButtons_EndTurn,
qemuMonitorBtnText: I18nStringKey.kQEMUMonitor,
qemuModalHeader: I18nStringKey.kQEMUMonitor,
qemuMonitorSendBtn: I18nStringKey.kGeneric_Send,
restoreBtnText: I18nStringKey.kAdminVMButtons_Restore,
rebootBtnText: I18nStringKey.kAdminVMButtons_Reboot,
clearQueueBtnText: I18nStringKey.kAdminVMButtons_ClearTurnQueue,
bypassTurnBtnText: I18nStringKey.kAdminVMButtons_BypassTurn,
indefTurnBtnText: I18nStringKey.kAdminVMButtons_IndefiniteTurn,
ghostTurnBtnText: I18nStringKey.kAdminVMButtons_GhostTurnOff,
// Account modal
accountLoginUsernameLabel: I18nStringKey.kGeneric_Username,
accountLoginPasswordLabel: I18nStringKey.kGeneric_Password,
accountModalLoginBtn: I18nStringKey.kGeneric_Login,
accountForgotPasswordButton: I18nStringKey.kAccountModal_ResetPassword,
accountRegisterEmailLabel: I18nStringKey.kGeneric_EMail,
accountRegisterUsernameLabel: I18nStringKey.kGeneric_Username,
accountRegisterPasswordLabel: I18nStringKey.kGeneric_Password,
accountRegisterConfirmPasswordLabel: I18nStringKey.kAccountModal_ConfirmPassword,
accountRegisterDateOfBirthLabel: I18nStringKey.kGeneric_DateOfBirth,
accountModalRegisterBtn: I18nStringKey.kGeneric_Register,
accountVerifyEmailCodeLabel: I18nStringKey.kGeneric_VerificationCode,
accountVerifyEmailPasswordLabel: I18nStringKey.kGeneric_Password,
accountModalVerifyEmailBtn: I18nStringKey.kGeneric_Verify,
accountSettingsEmailLabel: I18nStringKey.kGeneric_EMail,
accountSettingsUsernameLabel: I18nStringKey.kGeneric_Username,
accountSettingsNewPasswordLabel: I18nStringKey.kAccountModal_NewPassword,
accountSettingsConfirmNewPasswordLabel: I18nStringKey.kAccountModal_ConfirmNewPassword,
accountSettingsCurrentPasswordLabel: I18nStringKey.kAccountModal_CurrentPassword,
hideFlagCheckboxLabel: I18nStringKey.kAccountModal_HideFlag,
updateAccountSettingsBtn: I18nStringKey.kGeneric_Update,
accountResetPasswordEmailLabel: I18nStringKey.kGeneric_EMail,
accountResetPasswordUsernameLabel: I18nStringKey.kGeneric_Username,
accountResetPasswordBtn: I18nStringKey.kAccountModal_ResetPassword,
accountResetPasswordCodeLabel: I18nStringKey.kGeneric_VerificationCode,
accountResetPasswordNewPasswordLabel: I18nStringKey.kAccountModal_NewPassword,
accountResetPasswordConfirmNewPasswordLabel: I18nStringKey.kAccountModal_ConfirmNewPassword,
accountResetPasswordVerifyBtn: I18nStringKey.kAccountModal_ResetPassword,
};
const kDomAttributeToStringMap = {
adminPassword: {
placeholder: I18nStringKey.kGeneric_Password,
},
accountLoginUsername: {
placeholder: I18nStringKey.kGeneric_Username,
},
accountLoginPassword: {
placeholder: I18nStringKey.kGeneric_Password,
},
accountRegisterEmail: {
placeholder: I18nStringKey.kGeneric_EMail,
},
accountRegisterUsername: {
placeholder: I18nStringKey.kGeneric_Username,
},
accountRegisterPassword: {
placeholder: I18nStringKey.kGeneric_Password,
},
accountRegisterConfirmPassword: {
placeholder: I18nStringKey.kAccountModal_ConfirmPassword,
},
accountVerifyEmailCode: {
placeholder: I18nStringKey.kGeneric_VerificationCode,
},
accountVerifyEmailPassword: {
placeholder: I18nStringKey.kGeneric_Password,
},
accountSettingsEmail: {
placeholder: I18nStringKey.kGeneric_EMail,
},
accountSettingsUsername: {
placeholder: I18nStringKey.kGeneric_Username,
},
accountSettingsNewPassword: {
placeholder: I18nStringKey.kAccountModal_NewPassword,
},
accountSettingsConfirmNewPassword: {
placeholder: I18nStringKey.kAccountModal_ConfirmNewPassword,
},
accountSettingsCurrentPassword: {
placeholder: I18nStringKey.kAccountModal_CurrentPassword,
},
accountResetPasswordEmail: {
placeholder: I18nStringKey.kGeneric_EMail,
},
accountResetPasswordUsername: {
placeholder: I18nStringKey.kGeneric_Username,
},
accountResetPasswordCode: {
placeholder: I18nStringKey.kGeneric_VerificationCode,
},
accountResetPasswordNewPassword: {
placeholder: I18nStringKey.kAccountModal_NewPassword,
},
accountResetPasswordConfirmNewPassword: {
placeholder: I18nStringKey.kAccountModal_ConfirmNewPassword,
},
};
const kDomClassToStringMap: StringKeyMap = {
"mod-end-turn-btn": I18nStringKey.kVMButtons_EndTurn,
"mod-ban-btn": I18nStringKey.kAdminVMButtons_Ban,
"mod-kick-btn": I18nStringKey.kAdminVMButtons_Kick,
"mod-change-username-btn": I18nStringKey.kVMButtons_ChangeUsername,
"mod-temp-mute-btn": I18nStringKey.kAdminVMButtons_TempMute,
"mod-indef-mute-btn": I18nStringKey.kAdminVMButtons_IndefMute,
"mod-unmute-btn": I18nStringKey.kAdminVMButtons_Unmute,
"mod-get-ip-btn": I18nStringKey.kAdminVMButtons_GetIP,
}
for (let domId of Object.keys(kDomIdtoStringMap)) {
let element = document.getElementById(domId);
if (element == null) {
alert(`Error: Could not find element with ID ${domId} in the DOM! Please tell a site admin this happened.`);
return;
}
// Do the magic.
// N.B: For now, we assume all strings in this map are not formatted.
// If this assumption changes, then we should just use GetString() again
// and maybe include arguments, but for now this is okay
element.innerHTML = this.GetStringRaw(kDomIdtoStringMap[domId]);
}
for (let domId of Object.keys(kDomAttributeToStringMap)) {
let element = document.getElementById(domId);
if (element == null) {
alert(`Error: Could not find element with ID ${domId} in the DOM! Please tell a site admin this happened.`);
return;
}
// TODO: Figure out if we can get rid of this ts-ignore
// @ts-ignore
let attributes = kDomAttributeToStringMap[domId];
for (let attr of Object.keys(attributes)) {
element.setAttribute(attr, this.GetStringRaw(attributes[attr] as I18nStringKey));
}
}
for (let domClass of Object.keys(kDomClassToStringMap)) {
let elements = document.getElementsByClassName(domClass);
for (let element of elements) {
element.innerHTML = this.GetStringRaw(kDomClassToStringMap[domClass]);
}
}
}
// Returns a (raw, unformatted) string. Currently only used if we don't need formatting.
GetStringRaw(key: I18nStringKey): string {
if (key === I18nStringKey.kGeneric_CollabVM && Config.SiteNameOverride) return Config.SiteNameOverride;
if (key === I18nStringKey.kWelcomeModal_Header && Config.WelcomeModalTitleOverride) return Config.WelcomeModalTitleOverride;
if (key === I18nStringKey.kWelcomeModal_Body && Config.WelcomeModalBodyOverride) return Config.WelcomeModalBodyOverride;
let val = this.lang.stringKeys[key];
// Look up the fallback language by default if the language doesn't
// have that string key yet; if the fallback doesn't have it either,
// then just return the string key and a bit of a notice things have gone wrong
if (val == undefined) {
let fallback = fallbackLanguage.stringKeys[key];
if (fallback !== undefined) val = fallback;
else return `${key} (ERROR LOOKING UP TRANSLATION!!!)`;
}
return val;
}
// Returns a formatted localized string.
GetString(key: I18nStringKey, ...replacements: StringLike[]): string {
return Format(this.GetStringRaw(key), ...replacements);
}
on<e extends keyof I18nEvents>(event: e, cb: I18nEvents[e]): Unsubscribe {
return this.emitter.on(event, cb);
}
}
export let TheI18n = new I18n();

View File

@ -1,408 +0,0 @@
// Pulled a bunch of functions out of the guac source code to get a keysym
// and then a wrapper
// shitty but it works so /shrug
// THIS SUCKS SO BAD AND I HATE IT PLEASE REWRITE ALL OF THIS
export default function GetKeysym(keyCode: number, key: string, location: number): number | null {
let keysym = keysym_from_key_identifier(key, location) || keysym_from_keycode(keyCode, location);
return keysym;
}
function keysym_from_key_identifier(identifier: string, location: number): number | null {
if (!identifier) return null;
let typedCharacter: string | undefined;
// If identifier is U+xxxx, decode Unicode character
const unicodePrefixLocation = identifier.indexOf('U+');
if (unicodePrefixLocation >= 0) {
const hex = identifier.substring(unicodePrefixLocation + 2);
typedCharacter = String.fromCharCode(parseInt(hex, 16));
} else if (identifier.length === 1) typedCharacter = identifier;
else return get_keysym(keyidentifier_keysym[identifier], location);
if (!typedCharacter) return null;
const codepoint = typedCharacter.charCodeAt(0);
return keysym_from_charcode(codepoint);
}
function get_keysym(keysyms: number[] | null, location: number): number | null {
if (!keysyms) return null;
return keysyms[location] || keysyms[0];
}
function keysym_from_charcode(codepoint: number): number | null {
if (isControlCharacter(codepoint)) return 0xff00 | codepoint;
if (codepoint >= 0x0000 && codepoint <= 0x00ff) return codepoint;
if (codepoint >= 0x0100 && codepoint <= 0x10ffff) return 0x01000000 | codepoint;
return null;
}
function isControlCharacter(codepoint: number): boolean {
return codepoint <= 0x1f || (codepoint >= 0x7f && codepoint <= 0x9f);
}
function keysym_from_keycode(keyCode: number, location: number): number | null {
return get_keysym(keycodeKeysyms[keyCode], location);
}
function key_identifier_sane(keyCode: number, keyIdentifier: string): boolean {
if (!keyIdentifier) return false;
const unicodePrefixLocation = keyIdentifier.indexOf('U+');
if (unicodePrefixLocation === -1) return true;
const codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation + 2), 16);
if (keyCode !== codepoint) return true;
if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57)) return true;
return false;
}
export function OSK_buttonToKeysym(button: string): number | null {
const keyMapping = OSK_keyMappings.find((mapping) => mapping.includes(button));
if (keyMapping) {
const [, keyCode, keyIdentifier, key, location] = keyMapping;
return GetKeysym(keyCode, key, location);
}
return null;
}
interface KeyIdentifierKeysym {
[key: string]: number[] | null;
}
interface KeyCodeKeysyms {
[key: number]: number[] | null;
}
const keycodeKeysyms: KeyCodeKeysyms = {
8: [0xff08], // backspace
9: [0xff09], // tab
12: [0xff0b, 0xff0b, 0xff0b, 0xffb5], // clear / KP 5
13: [0xff0d], // enter
16: [0xffe1, 0xffe1, 0xffe2], // shift
17: [0xffe3, 0xffe3, 0xffe4], // ctrl
18: [0xffe9, 0xffe9, 0xfe03], // alt
19: [0xff13], // pause/break
20: [0xffe5], // caps lock
27: [0xff1b], // escape
32: [0x0020], // space
33: [0xff55, 0xff55, 0xff55, 0xffb9], // page up / KP 9
34: [0xff56, 0xff56, 0xff56, 0xffb3], // page down / KP 3
35: [0xff57, 0xff57, 0xff57, 0xffb1], // end / KP 1
36: [0xff50, 0xff50, 0xff50, 0xffb7], // home / KP 7
37: [0xff51, 0xff51, 0xff51, 0xffb4], // left arrow / KP 4
38: [0xff52, 0xff52, 0xff52, 0xffb8], // up arrow / KP 8
39: [0xff53, 0xff53, 0xff53, 0xffb6], // right arrow / KP 6
40: [0xff54, 0xff54, 0xff54, 0xffb2], // down arrow / KP 2
45: [0xff63, 0xff63, 0xff63, 0xffb0], // insert / KP 0
46: [0xffff, 0xffff, 0xffff, 0xffae], // delete / KP decimal
91: [0xffeb], // left window key (hyper_l)
92: [0xff67], // right window key (menu key?)
93: null, // select key
96: [0xffb0], // KP 0
97: [0xffb1], // KP 1
98: [0xffb2], // KP 2
99: [0xffb3], // KP 3
100: [0xffb4], // KP 4
101: [0xffb5], // KP 5
102: [0xffb6], // KP 6
103: [0xffb7], // KP 7
104: [0xffb8], // KP 8
105: [0xffb9], // KP 9
106: [0xffaa], // KP multiply
107: [0xffab], // KP add
109: [0xffad], // KP subtract
110: [0xffae], // KP decimal
111: [0xffaf], // KP divide
112: [0xffbe], // f1
113: [0xffbf], // f2
114: [0xffc0], // f3
115: [0xffc1], // f4
116: [0xffc2], // f5
117: [0xffc3], // f6
118: [0xffc4], // f7
119: [0xffc5], // f8
120: [0xffc6], // f9
121: [0xffc7], // f10
122: [0xffc8], // f11
123: [0xffc9], // f12
144: [0xff7f], // num lock
145: [0xff14], // scroll lock
225: [0xfe03] // altgraph (iso_level3_shift)
};
const keyidentifier_keysym: KeyIdentifierKeysym = {
Again: [0xff66],
AllCandidates: [0xff3d],
Alphanumeric: [0xff30],
Alt: [0xffe9, 0xffe9, 0xfe03],
Attn: [0xfd0e],
AltGraph: [0xfe03],
ArrowDown: [0xff54],
ArrowLeft: [0xff51],
ArrowRight: [0xff53],
ArrowUp: [0xff52],
Backspace: [0xff08],
CapsLock: [0xffe5],
Cancel: [0xff69],
Clear: [0xff0b],
Convert: [0xff21],
Copy: [0xfd15],
Crsel: [0xfd1c],
CrSel: [0xfd1c],
CodeInput: [0xff37],
Compose: [0xff20],
Control: [0xffe3, 0xffe3, 0xffe4],
ContextMenu: [0xff67],
DeadGrave: [0xfe50],
DeadAcute: [0xfe51],
DeadCircumflex: [0xfe52],
DeadTilde: [0xfe53],
DeadMacron: [0xfe54],
DeadBreve: [0xfe55],
DeadAboveDot: [0xfe56],
DeadUmlaut: [0xfe57],
DeadAboveRing: [0xfe58],
DeadDoubleacute: [0xfe59],
DeadCaron: [0xfe5a],
DeadCedilla: [0xfe5b],
DeadOgonek: [0xfe5c],
DeadIota: [0xfe5d],
DeadVoicedSound: [0xfe5e],
DeadSemivoicedSound: [0xfe5f],
Delete: [0xffff],
Down: [0xff54],
End: [0xff57],
Enter: [0xff0d],
EraseEof: [0xfd06],
Escape: [0xff1b],
Execute: [0xff62],
Exsel: [0xfd1d],
ExSel: [0xfd1d],
F1: [0xffbe],
F2: [0xffbf],
F3: [0xffc0],
F4: [0xffc1],
F5: [0xffc2],
F6: [0xffc3],
F7: [0xffc4],
F8: [0xffc5],
F9: [0xffc6],
F10: [0xffc7],
F11: [0xffc8],
F12: [0xffc9],
F13: [0xffca],
F14: [0xffcb],
F15: [0xffcc],
F16: [0xffcd],
F17: [0xffce],
F18: [0xffcf],
F19: [0xffd0],
F20: [0xffd1],
F21: [0xffd2],
F22: [0xffd3],
F23: [0xffd4],
F24: [0xffd5],
Find: [0xff68],
GroupFirst: [0xfe0c],
GroupLast: [0xfe0e],
GroupNext: [0xfe08],
GroupPrevious: [0xfe0a],
FullWidth: null,
HalfWidth: null,
HangulMode: [0xff31],
Hankaku: [0xff29],
HanjaMode: [0xff34],
Help: [0xff6a],
Hiragana: [0xff25],
HiraganaKatakana: [0xff27],
Home: [0xff50],
Hyper: [0xffed, 0xffed, 0xffee],
Insert: [0xff63],
JapaneseHiragana: [0xff25],
JapaneseKatakana: [0xff26],
JapaneseRomaji: [0xff24],
JunjaMode: [0xff38],
KanaMode: [0xff2d],
KanjiMode: [0xff21],
Katakana: [0xff26],
Left: [0xff51],
Meta: [0xffe7, 0xffe7, 0xffe8],
ModeChange: [0xff7e],
NumLock: [0xff7f],
PageDown: [0xff56],
PageUp: [0xff55],
Pause: [0xff13],
Play: [0xfd16],
PreviousCandidate: [0xff3e],
PrintScreen: [0xfd1d],
Redo: [0xff66],
Right: [0xff53],
RomanCharacters: null,
Scroll: [0xff14],
Select: [0xff60],
Separator: [0xffac],
Shift: [0xffe1, 0xffe1, 0xffe2],
SingleCandidate: [0xff3c],
Super: [0xffeb, 0xffeb, 0xffec],
Tab: [0xff09],
Up: [0xff52],
Undo: [0xff65],
Win: [0xffeb],
Zenkaku: [0xff28],
ZenkakuHankaku: [0xff2a]
};
const OSK_keyMappings: [string, number, string, string, number][] = [
['!', 49, 'Digit1', '!', 0],
['#', 51, 'Digit3', '#', 0],
['$', 52, 'Digit4', '$', 0],
['%', 53, 'Digit5', '%', 0],
['&', 55, 'Digit7', '&', 0],
["'", 222, 'Quote', "'", 0],
['(', 57, 'Digit9', '(', 0],
[')', 48, 'Digit0', ')', 0],
['*', 56, 'Digit8', '*', 0],
['+', 187, 'Equal', '+', 0],
[',', 188, 'Comma', ',', 0],
['-', 189, 'Minus', '-', 0],
['.', 190, 'Period', '.', 0],
['/', 191, 'Slash', '/', 0],
['0', 48, 'Digit0', '0', 0],
['1', 49, 'Digit1', '1', 0],
['2', 50, 'Digit2', '2', 0],
['3', 51, 'Digit3', '3', 0],
['4', 52, 'Digit4', '4', 0],
['5', 53, 'Digit5', '5', 0],
['6', 54, 'Digit6', '6', 0],
['7', 55, 'Digit7', '7', 0],
['8', 56, 'Digit8', '8', 0],
['9', 57, 'Digit9', '9', 0],
[':', 186, 'Semicolon', ':', 0],
[';', 186, 'Semicolon', ';', 0],
['<', 188, 'Comma', '<', 0],
['=', 187, 'Equal', '=', 0],
['>', 190, 'Period', '>', 0],
['?', 191, 'Slash', '?', 0],
['@', 50, 'Digit2', '@', 0],
['A', 65, 'KeyA', 'A', 0],
['B', 66, 'KeyB', 'B', 0],
['C', 67, 'KeyC', 'C', 0],
['D', 68, 'KeyD', 'D', 0],
['E', 69, 'KeyE', 'E', 0],
['F', 70, 'KeyF', 'F', 0],
['G', 71, 'KeyG', 'G', 0],
['H', 72, 'KeyH', 'H', 0],
['I', 73, 'KeyI', 'I', 0],
['J', 74, 'KeyJ', 'J', 0],
['K', 75, 'KeyK', 'K', 0],
['L', 76, 'KeyL', 'L', 0],
['M', 77, 'KeyM', 'M', 0],
['N', 78, 'KeyN', 'N', 0],
['O', 79, 'KeyO', 'O', 0],
['P', 80, 'KeyP', 'P', 0],
['Q', 81, 'KeyQ', 'Q', 0],
['R', 82, 'KeyR', 'R', 0],
['S', 83, 'KeyS', 'S', 0],
['T', 84, 'KeyT', 'T', 0],
['U', 85, 'KeyU', 'U', 0],
['V', 86, 'KeyV', 'V', 0],
['W', 87, 'KeyW', 'W', 0],
['X', 88, 'KeyX', 'X', 0],
['Y', 89, 'KeyY', 'Y', 0],
['Z', 90, 'KeyZ', 'Z', 0],
['[', 219, 'BracketLeft', '[', 0],
['\\', 220, 'Backslash', '\\', 0],
[']', 221, 'BracketRight', ']', 0],
['^', 54, 'Digit6', '^', 0],
['_', 189, 'Minus', '_', 0],
['`', 192, 'Backquote', '`', 0],
['a', 65, 'KeyA', 'a', 0],
['b', 66, 'KeyB', 'b', 0],
['c', 67, 'KeyC', 'c', 0],
['d', 68, 'KeyD', 'd', 0],
['e', 69, 'KeyE', 'e', 0],
['f', 70, 'KeyF', 'f', 0],
['g', 71, 'KeyG', 'g', 0],
['h', 72, 'KeyH', 'h', 0],
['i', 73, 'KeyI', 'i', 0],
['j', 74, 'KeyJ', 'j', 0],
['k', 75, 'KeyK', 'k', 0],
['l', 76, 'KeyL', 'l', 0],
['m', 77, 'KeyM', 'm', 0],
['n', 78, 'KeyN', 'n', 0],
['o', 79, 'KeyO', 'o', 0],
['p', 80, 'KeyP', 'p', 0],
['q', 81, 'KeyQ', 'q', 0],
['r', 82, 'KeyR', 'r', 0],
['s', 83, 'KeyS', 's', 0],
['t', 84, 'KeyT', 't', 0],
['u', 85, 'KeyU', 'u', 0],
['v', 86, 'KeyV', 'v', 0],
['w', 87, 'KeyW', 'w', 0],
['x', 88, 'KeyX', 'x', 0],
['y', 89, 'KeyY', 'y', 0],
['z', 90, 'KeyZ', 'z', 0],
['{', 219, 'BracketLeft', '{', 0],
['{altleft}', 18, 'AltLeft', 'AltLeft', 1],
['{altright}', 18, 'AltRight', 'AltRight', 2],
['{arrowdown}', 40, 'ArrowDown', 'ArrowDown', 0],
['{arrowleft}', 37, 'ArrowLeft', 'ArrowLeft', 0],
['{arrowright}', 39, 'ArrowRight', 'ArrowRight', 0],
['{arrowup}', 38, 'ArrowUp', 'ArrowUp', 0],
['{backspace}', 8, 'Backspace', 'Backspace', 0],
['{capslock}', 20, 'CapsLock', 'CapsLock', 0],
['{controlleft}', 17, 'ControlLeft', 'ControlLeft', 1],
['{controlright}', 17, 'ControlRight', 'ControlRight', 2],
['{delete}', 46, 'Delete', 'Delete', 0],
['{end}', 35, 'End', 'End', 0],
['{enter}', 13, 'Enter', 'Enter', 0],
['{escape}', 27, 'Escape', 'Escape', 0],
['{f10}', 121, 'F10', 'F10', 0],
['{f11}', 122, 'F11', 'F11', 0],
['{f12}', 123, 'F12', 'F12', 0],
['{f1}', 112, 'F1', 'F1', 0],
['{f2}', 113, 'F2', 'F2', 0],
['{f3}', 114, 'F3', 'F3', 0],
['{f4}', 115, 'F4', 'F4', 0],
['{f5}', 116, 'F5', 'F5', 0],
['{f6}', 117, 'F6', 'F6', 0],
['{f7}', 118, 'F7', 'F7', 0],
['{f8}', 119, 'F8', 'F8', 0],
['{f9}', 120, 'F9', 'F9', 0],
['{home}', 36, 'Home', 'Home', 0],
['{insert}', 45, 'Insert', 'Insert', 0],
['{metaleft}', 91, 'OSLeft', 'OSLeft', 1],
['{metaright}', 92, 'OSRight', 'OSRight', 2],
['{numlock}', 144, 'NumLock', 'NumLock', 0],
['{numpad0}', 96, 'Numpad0', 'Numpad0', 3],
['{numpad1}', 97, 'Numpad1', 'Numpad1', 3],
['{numpad2}', 98, 'Numpad2', 'Numpad2', 3],
['{numpad3}', 99, 'Numpad3', 'Numpad3', 3],
['{numpad4}', 100, 'Numpad4', 'Numpad4', 3],
['{numpad5}', 101, 'Numpad5', 'Numpad5', 3],
['{numpad6}', 102, 'Numpad6', 'Numpad6', 3],
['{numpad7}', 103, 'Numpad7', 'Numpad7', 3],
['{numpad8}', 104, 'Numpad8', 'Numpad8', 3],
['{numpad9}', 105, 'Numpad9', 'Numpad9', 3],
['{numpadadd}', 107, 'NumpadAdd', 'NumpadAdd', 3],
['{numpaddecimal}', 110, 'NumpadDecimal', 'NumpadDecimal', 3],
['{numpaddivide}', 111, 'NumpadDivide', 'NumpadDivide', 3],
['{numpadenter}', 13, 'NumpadEnter', 'NumpadEnter', 3],
['{numpadmultiply}', 106, 'NumpadMultiply', 'NumpadMultiply', 3],
['{numpadsubtract}', 109, 'NumpadSubtract', 'NumpadSubtract', 3],
['{pagedown}', 34, 'PageDown', 'PageDown', 0],
['{pageup}', 33, 'PageUp', 'PageUp', 0],
['{pause}', 19, 'Pause', 'Pause', 0],
['{prtscr}', 44, 'PrintScreen', 'PrintScreen', 0],
['{scrolllock}', 145, 'ScrollLock', 'ScrollLock', 0],
['{shiftleft}', 16, 'ShiftLeft', 'ShiftLeft', 1],
['{shiftright}', 16, 'ShiftRight', 'ShiftRight', 2],
['{space}', 32, 'Space', 'Space', 0],
['{tab}', 9, 'Tab', 'Tab', 0],
['|', 220, 'Backslash', '|', 0],
['}', 221, 'BracketRight', '}', 0],
['~', 192, 'Backquote', '~', 0],
['"', 222, 'Quote', '"', 0]
];

File diff suppressed because it is too large Load Diff

View File

@ -1,741 +0,0 @@
import { createNanoEvents, Emitter, DefaultEvents, Unsubscribe } from 'nanoevents';
import * as Guacutils from './Guacutils.js';
import VM from './VM.js';
import { User } from './User.js';
import { AdminOpcode, Permissions, Rank } from './Permissions.js';
import TurnStatus from './TurnStatus.js';
import Mouse from './mouse.js';
import GetKeysym from '../keyboard.js';
import VoteStatus from './VoteStatus.js';
import MuteState from './MuteState.js';
import { StringLike } from '../StringLike.js';
import * as msgpack from 'msgpackr';
// TODO: Properly workspaceify this
import { CollabVMProtocolMessage, CollabVMProtocolMessageType } from '../../../collab-vm-1.2-binary-protocol/src/index.js';
const w = window as any;
export interface CollabVMClientEvents {
//open: () => void;
close: () => void;
message: (...args: string[]) => void;
// Protocol stuff
chat: (username: string, message: string) => void;
adduser: (user: User) => void;
remuser: (user: User) => void;
renamestatus: (status: 'taken' | 'invalid' | 'blacklisted') => void;
turn: (status: TurnStatus) => void;
rename: (oldUsername: string, newUsername: string, selfRename: boolean) => void;
vote: (status: VoteStatus) => void;
voteend: () => void;
votecd: (coolDownTime: number) => void;
badpw: () => void;
login: (rank: Rank, perms: Permissions) => void;
// Auth stuff
auth: (server: string) => void;
accountlogin: (success: boolean) => void;
flag: () => void;
}
// types for private emitter
interface CollabVMClientPrivateEvents {
open: () => void;
list: (listEntries: string[]) => void;
connect: (connectedToVM: boolean) => void;
ip: (username: string, ip: string) => void;
qemu: (qemuResponse: string) => void;
}
const DefaultCapabilities = [ "bin" ];
export default class CollabVMClient {
// Fields
private socket: WebSocket;
canvas: HTMLCanvasElement;
// A secondary canvas that is not scaled
unscaledCanvas: HTMLCanvasElement;
canvasScale : { width : number, height : number } = { width: 0, height: 0 };
actualScreenSize : { width : number, height : number } = { width: 0, height: 0 };
private unscaledCtx: CanvasRenderingContext2D;
private ctx: CanvasRenderingContext2D;
private url: string;
private connectedToVM: boolean = false;
private users: User[] = [];
private username: string | null = null;
private mouse: Mouse = new Mouse();
private rank: Rank = Rank.Unregistered;
private perms: Permissions = new Permissions(0);
private voteStatus: VoteStatus | null = null;
private node: string | null = null;
private auth: boolean = false;
// events that are used internally and not exposed
private internalEmitter: Emitter<CollabVMClientPrivateEvents>;
// public events
private publicEmitter: Emitter<CollabVMClientEvents>;
private unsubscribeCallbacks: Array<Unsubscribe> = [];
constructor(url: string) {
// Save the URL
this.url = url;
// Create the events
this.internalEmitter = createNanoEvents();
this.publicEmitter = createNanoEvents();
// Create the canvas
this.canvas = document.createElement('canvas');
this.unscaledCanvas = document.createElement('canvas');
// Set tab index so it can be focused
this.canvas.tabIndex = -1;
// Get the 2D context
this.ctx = this.canvas.getContext('2d')!;
this.unscaledCtx = this.unscaledCanvas.getContext('2d')!;
// Bind canvas click
this.canvas.addEventListener('click', (e) => {
if (this.users.find((u) => u.username === this.username)?.turn === -1) this.turn(true);
});
// Bind keyboard and mouse
this.canvas.addEventListener(
'mousedown',
(e: MouseEvent) => {
if (!this.shouldSendInput()) return;
this.mouse.initFromMouseEvent(e);
this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask());
},
{
capture: true
}
);
this.canvas.addEventListener(
'mouseup',
(e: MouseEvent) => {
if (!this.shouldSendInput()) return;
this.mouse.initFromMouseEvent(e);
this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask());
},
{
capture: true
}
);
this.canvas.addEventListener(
'mousemove',
(e: MouseEvent) => {
if (!this.shouldSendInput()) return;
this.mouse.initFromMouseEvent(e);
this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask());
},
{
capture: true
}
);
this.canvas.addEventListener(
'keydown',
(e: KeyboardEvent) => {
e.preventDefault();
if (!this.shouldSendInput()) return;
let keysym = GetKeysym(e.keyCode, e.key, e.location);
if (keysym === null) return;
this.key(keysym, true);
},
{
capture: true
}
);
this.canvas.addEventListener(
'keyup',
(e: KeyboardEvent) => {
e.preventDefault();
if (!this.shouldSendInput()) return;
let keysym = GetKeysym(e.keyCode, e.key, e.location);
if (keysym === null) return;
this.key(keysym, false);
},
{
capture: true
}
);
this.canvas.addEventListener(
'wheel',
(ev: WheelEvent) => {
ev.preventDefault();
if (!this.shouldSendInput()) return;
this.mouse.initFromWheelEvent(ev);
this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask());
// this is a very, very ugly hack but it seems to work so /shrug
if (this.mouse.scrollUp) this.mouse.scrollUp = false;
else if (this.mouse.scrollDown) this.mouse.scrollDown = false;
this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask());
},
{
capture: true
}
);
window.addEventListener('resize', (e) => this.onWindowResize(e));
this.canvas.addEventListener('contextmenu', (e) => e.preventDefault());
// Create the WebSocket
this.socket = new WebSocket(url, 'guacamole');
this.socket.binaryType = 'arraybuffer';
// Add the event listeners
this.socket.addEventListener('open', () => this.onOpen());
this.socket.addEventListener('message', (event) => this.onMessage(event));
this.socket.addEventListener('close', () => this.publicEmitter.emit('close'));
}
// Fires when the WebSocket connection is opened
private onOpen() {
this.internalEmitter.emit('open');
}
private onBinaryMessage(data: ArrayBuffer) {
let msg: CollabVMProtocolMessage;
try {
msg = msgpack.decode(data);
} catch {
console.error("Server sent invalid binary message");
return;
}
if (msg.type === undefined) return;
switch (msg.type) {
case CollabVMProtocolMessageType.rect: {
if (!msg.rect || msg.rect.x === undefined || msg.rect.y === undefined || msg.rect.data === undefined) return;
let blob = new Blob( [ new Uint8Array(msg.rect.data) ], {type: "image/jpeg"});
let url = URL.createObjectURL(blob);
let img = new Image();
img.addEventListener('load', () => {
this.loadRectangle(img, msg.rect!.x, msg.rect!.y);
URL.revokeObjectURL(url);
});
img.src = url;
break;
}
}
}
// Fires on WebSocket message
private onMessage(event: MessageEvent) {
if (event.data instanceof ArrayBuffer) {
this.onBinaryMessage(event.data);
return;
}
let msgArr: string[];
try {
msgArr = Guacutils.decode(event.data);
} catch (e) {
console.error(`Server sent invalid message (${e})`);
return;
}
this.publicEmitter.emit('message', ...msgArr);
switch (msgArr[0]) {
case 'nop': {
// Send a NOP back
this.send('nop');
break;
}
case 'list': {
// pass msgarr to the emitter for processing by list()
this.internalEmitter.emit('list', msgArr.slice(1));
break;
}
case 'connect': {
this.connectedToVM = msgArr[1] === '1';
this.internalEmitter.emit('connect', this.connectedToVM);
break;
}
case 'size': {
if (msgArr[1] !== '0') return;
this.recalculateCanvasScale(parseInt(msgArr[2]), parseInt(msgArr[3]));
this.unscaledCanvas.width = this.actualScreenSize.width;
this.unscaledCanvas.height = this.actualScreenSize.height;
this.canvas.width = this.canvasScale.width;
this.canvas.height = this.canvasScale.height;
break;
}
case 'png': {
// Despite the opcode name, this is actually JPEG, because old versions of the server used PNG and yknow backwards compatibility
let img = new Image();
var x = parseInt(msgArr[3]);
var y = parseInt(msgArr[4]);
img.addEventListener('load', () => {
this.loadRectangle(img, x, y);
});
img.src = 'data:image/jpeg;base64,' + msgArr[5];
break;
}
case 'chat': {
for (let i = 1; i < msgArr.length; i += 2) {
this.publicEmitter.emit('chat', msgArr[i], msgArr[i + 1]);
}
break;
}
case 'adduser': {
for (let i = 2; i < msgArr.length; i += 2) {
let _user = this.users.find((u) => u.username === msgArr[i]);
if (_user !== undefined) {
_user.rank = parseInt(msgArr[i + 1]);
} else {
_user = new User(msgArr[i], parseInt(msgArr[i + 1]));
this.users.push(_user);
}
this.publicEmitter.emit('adduser', _user);
}
break;
}
case 'remuser': {
for (let i = 2; i < msgArr.length; i++) {
let _user = this.users.find((u) => u.username === msgArr[i]);
if (_user === undefined) continue;
this.users.splice(this.users.indexOf(_user), 1);
this.publicEmitter.emit('remuser', _user);
}
}
case 'rename': {
let selfrename = false;
let oldusername: string | null = null;
// We've been renamed
if (msgArr[1] === '0') {
selfrename = true;
oldusername = this.username;
// msgArr[2] is the status of the rename
// Anything other than 0 is an error, however the server will still rename us to a guest name
switch (msgArr[2]) {
case '1':
// The username we wanted was taken
this.publicEmitter.emit('renamestatus', 'taken');
break;
case '2':
// The username we wanted was invalid
this.publicEmitter.emit('renamestatus', 'invalid');
break;
case '3':
// The username we wanted is blacklisted
this.publicEmitter.emit('renamestatus', 'blacklisted');
break;
}
this.username = msgArr[3];
} else oldusername = msgArr[2];
let _user = this.users.find((u) => u.username === oldusername);
if (_user) {
_user.username = msgArr[3];
}
this.publicEmitter.emit('rename', oldusername!, msgArr[3], selfrename);
break;
}
case 'turn': {
// Reset all turn data
for (let user of this.users) user.turn = -1;
let queuedUsers = parseInt(msgArr[2]);
if (queuedUsers === 0) {
this.publicEmitter.emit('turn', {
user: null,
queue: [],
turnTime: null,
queueTime: null
});
return;
}
let currentTurn = this.users.find((u) => u.username === msgArr[3])!;
currentTurn.turn = 0;
let queue: User[] = [];
if (queuedUsers > 1) {
for (let i = 1; i < queuedUsers; i++) {
let user = this.users.find((u) => u.username === msgArr[i + 3])!;
queue.push(user);
user.turn = i;
}
}
this.publicEmitter.emit('turn', {
user: currentTurn,
queue: queue,
turnTime: currentTurn.username === this.username ? parseInt(msgArr[1]) : null,
queueTime: queue.some((u) => u.username === this.username) ? parseInt(msgArr[msgArr.length - 1]) : null
});
break;
}
case 'vote': {
switch (msgArr[1]) {
case '0':
// Vote started
case '1':
// Vote updated
let timeToEnd = parseInt(msgArr[2]);
let yesVotes = parseInt(msgArr[3]);
let noVotes = parseInt(msgArr[4]);
// Some server implementations dont send data for status 0, and some do
if (Number.isNaN(timeToEnd) || Number.isNaN(yesVotes) || Number.isNaN(noVotes)) return;
this.voteStatus = {
timeToEnd: timeToEnd,
yesVotes: yesVotes,
noVotes: noVotes
};
this.publicEmitter.emit('vote', this.voteStatus);
break;
case '2':
// Vote ended
this.voteStatus = null;
this.publicEmitter.emit('voteend');
break;
case '3':
// Cooldown
this.publicEmitter.emit('votecd', parseInt(msgArr[2]));
break;
}
}
// auth stuff
case 'auth': {
this.publicEmitter.emit('auth', msgArr[1]);
this.auth = true;
break;
}
case 'login': {
if (msgArr[1] === "1") {
this.rank = Rank.Registered;
this.publicEmitter.emit('login', Rank.Registered, new Permissions(0));
}
this.publicEmitter.emit('accountlogin', msgArr[1] === "1");
break;
}
case 'admin': {
switch (msgArr[1]) {
case '0': {
// Login
switch (msgArr[2]) {
case '0':
this.publicEmitter.emit('badpw');
return;
case '1':
this.perms.set(65535);
this.rank = Rank.Admin;
break;
case '3':
this.perms.set(parseInt(msgArr[3]));
this.rank = Rank.Moderator;
break;
}
this.publicEmitter.emit('login', this.rank, this.perms);
break;
}
case '19': {
// IP
this.internalEmitter.emit('ip', msgArr[2], msgArr[3]);
break;
}
case '2': {
// QEMU
this.internalEmitter.emit('qemu', msgArr[2]);
break;
}
}
}
case 'flag': {
for (let i = 1; i < msgArr.length; i += 2) {
let user = this.users.find((u) => u.username === msgArr[i]);
if (user) user.countryCode = msgArr[i + 1];
}
this.publicEmitter.emit('flag');
break;
}
}
}
private loadRectangle(img: HTMLImageElement, x: number, y: number) {
if (this.actualScreenSize.width !== this.canvasScale.width || this.actualScreenSize.height !== this.canvasScale.height)
this.unscaledCtx.drawImage(img, x, y);
// Scale the image to the canvas
this.ctx.drawImage(img, 0, 0, img.width, img.height,
(x / this.actualScreenSize.width) * this.canvas.width,
(y / this.actualScreenSize.height) * this.canvas.height,
(img.width / this.actualScreenSize.width) * this.canvas.width,
(img.height / this.actualScreenSize.height) * this.canvas.height
);
}
private onWindowResize(e: Event) {
if (!this.connectedToVM) return;
// If the canvas is the same size as the screen, don't bother redrawing
if (window.innerWidth >= this.actualScreenSize.width && this.canvas.width === this.actualScreenSize.width) return;
if (this.actualScreenSize.width === this.canvasScale.width && this.actualScreenSize.height === this.canvasScale.height) {
this.unscaledCtx.drawImage(this.canvas, 0, 0);
}
this.recalculateCanvasScale(this.actualScreenSize.width, this.actualScreenSize.height);
this.canvas.width = this.canvasScale.width;
this.canvas.height = this.canvasScale.height;
this.ctx.drawImage(this.unscaledCanvas, 0, 0, this.actualScreenSize.width, this.actualScreenSize.height, 0, 0, this.canvas.width, this.canvas.height);
}
private recalculateCanvasScale(width: number, height: number) {
this.actualScreenSize.width = width;
this.actualScreenSize.height = height;
// If the screen is bigger than the canvas, don't downscale
if (window.innerWidth >= this.actualScreenSize.width) {
this.canvasScale.width = this.actualScreenSize.width;
this.canvasScale.height = this.actualScreenSize.height;
} else {
// If the canvas is bigger than the screen, downscale
this.canvasScale.width = window.innerWidth;
this.canvasScale.height = (window.innerWidth / this.actualScreenSize.width) * this.actualScreenSize.height;
}
}
async WaitForOpen() {
return new Promise<void>((res) => {
// TODO: should probably reject on close
let unsub = this.onInternal('open', () => {
unsub();
res();
});
});
}
// Sends a message to the server
send(...args: StringLike[]) {
let guacElements = [...args].map((el) => {
// This catches cases where the thing already is a string
if (typeof el == 'string') return el as string;
return el.toString();
});
this.socket.send(Guacutils.encode(...guacElements));
}
// Get a list of all VMs
list(): Promise<VM[]> {
return new Promise((res, rej) => {
let u = this.onInternal('list', (list: string[]) => {
u();
let vms: VM[] = [];
for (let i = 0; i < list.length; i += 3) {
let th = new Image();
th.src = 'data:image/jpeg;base64,' + list[i + 2];
vms.push({
url: this.url,
id: list[i],
displayName: list[i + 1],
thumbnail: th
});
}
res(vms);
});
this.send('list');
});
}
// Connect to a node
connect(id: string, username: string | null = null): Promise<boolean> {
return new Promise((res) => {
let u = this.onInternal('connect', (success: boolean) => {
u();
res(success);
});
if (localStorage.getItem('collabvm-hide-flag') === 'true') this.send('noflag');
if (username === null) this.send('rename');
else this.send('rename', username);
if (DefaultCapabilities.length > 0) this.send('cap', ...DefaultCapabilities);
this.send('connect', id);
this.node = id;
});
}
// Close the connection
close() {
this.connectedToVM = false;
// call all unsubscribe callbacks explicitly
for (let cb of this.unsubscribeCallbacks) {
cb();
}
this.unsubscribeCallbacks = [];
if (this.socket.readyState === WebSocket.OPEN) this.socket.close();
}
// Get users
getUsers(): User[] {
// Return a copy of the array
return this.users.slice();
}
// Send a chat message
chat(message: string) {
this.send('chat', message);
}
// Rename
rename(username: string | null = null) {
if (username) this.send('rename', username);
else this.send('rename');
}
// Take or drop turn
turn(taketurn: boolean) {
this.send('turn', taketurn ? '1' : '0');
}
// Send mouse instruction
sendmouse(_x: number, _y: number, mask: number) {
let x = Math.round((_x / this.canvas.width) * this.actualScreenSize.width);
let y = Math.round((_y / this.canvas.height) * this.actualScreenSize.height);
this.send('mouse', x, y, mask);
}
// Send key
key(keysym: number, down: boolean) {
this.send('key', keysym, down ? '1' : '0');
}
// Get vote status
getVoteStatus(): VoteStatus | null {
return this.voteStatus;
}
// Start a vote, or vote
vote(vote: boolean) {
this.send('vote', vote ? '1' : '0');
}
// Try to login using the specified password
login(password: string) {
this.send('admin', AdminOpcode.Login, password);
}
/* Admin commands */
// Restore
restore() {
if (!this.node) return;
this.send('admin', AdminOpcode.Restore, this.node!);
}
// Reboot
reboot() {
if (!this.node) return;
this.send('admin', AdminOpcode.Reboot, this.node!);
}
// Clear turn queue
clearQueue() {
if (!this.node) return;
this.send('admin', AdminOpcode.ClearTurns, this.node!);
}
// Bypass turn
bypassTurn() {
this.send('admin', AdminOpcode.BypassTurn);
}
// End turn
endTurn(user: string) {
this.send('admin', AdminOpcode.EndTurn, user);
}
// Ban
ban(user: string) {
this.send('admin', AdminOpcode.BanUser, user);
}
// Kick
kick(user: string) {
this.send('admin', AdminOpcode.KickUser, user);
}
// Rename user
renameUser(oldname: string, newname: string) {
this.send('admin', AdminOpcode.RenameUser, oldname, newname);
}
// Mute user
mute(user: string, state: MuteState) {
this.send('admin', AdminOpcode.MuteUser, user, state);
}
// Grab IP
getip(user: string) {
if (this.users.find((u) => u.username === user) === undefined) return false;
return new Promise<string>((res) => {
let unsubscribe = this.onInternal('ip', (username: string, ip: string) => {
if (username !== user) return;
unsubscribe();
res(ip);
});
this.send('admin', AdminOpcode.GetIP, user);
});
}
// QEMU Monitor
qemuMonitor(cmd: string) {
return new Promise<string>((res) => {
let unsubscribe = this.onInternal('qemu', (output) => {
unsubscribe();
res(output);
});
this.send('admin', AdminOpcode.MonitorCommand, this.node!, cmd);
});
}
// XSS
xss(msg: string) {
this.send('admin', AdminOpcode.ChatXSS, msg);
}
// Force vote
forceVote(result: boolean) {
this.send('admin', AdminOpcode.ForceVote, result ? '1' : '0');
}
// Toggle turns
turns(enabled: boolean) {
this.send('admin', AdminOpcode.ToggleTurns, enabled ? '1' : '0');
}
// Indefinite turn
indefiniteTurn() {
this.send('admin', AdminOpcode.IndefiniteTurn);
}
// Hide screen
hideScreen(hidden: boolean) {
this.send('admin', AdminOpcode.HideScreen, hidden ? '1' : '0');
}
// Login to account
loginAccount(token: string) {
this.send('login', token);
}
usesAccountAuth() {
return this.auth;
}
getNode() {
return this.node;
}
private onInternal<E extends keyof CollabVMClientPrivateEvents>(event: E, callback: CollabVMClientPrivateEvents[E]): Unsubscribe {
return this.internalEmitter.on(event, callback);
}
private shouldSendInput() {
return this.users.find(u => u.username === this.username)?.turn === 0 || (w.collabvm.ghostTurn && this.rank === Rank.Admin);
}
on<E extends keyof CollabVMClientEvents>(event: E, callback: CollabVMClientEvents[E]): Unsubscribe {
let unsub = this.publicEmitter.on(event, callback);
this.unsubscribeCallbacks.push(unsub);
return unsub;
}
}

View File

@ -1,37 +0,0 @@
export function decode(string: string): string[] {
let pos = -1;
let sections = [];
for (;;) {
let len = string.indexOf('.', pos + 1);
if (len === -1) break;
pos = parseInt(string.slice(pos + 1, len)) + len + 1;
// don't allow funky protocol length
if (pos > string.length) return [];
sections.push(string.slice(len + 1, pos));
const sep = string.slice(pos, pos + 1);
if (sep === ',') continue;
else if (sep === ';') break;
// Invalid data.
else return [];
}
return sections;
}
export function encode(...string: string[]): string {
let command = '';
for (let i = 0; i < string.length; i++) {
let current = string[i];
command += current.toString().length + '.' + current;
command += i < string.length - 1 ? ',' : ';';
}
return command;
}

View File

@ -1,7 +0,0 @@
enum MuteState {
Temp = 0,
Perma = 1,
Unmuted = 2
}
export default MuteState;

View File

@ -1,57 +0,0 @@
export class Permissions {
restore: boolean = false;
reboot: boolean = false;
ban: boolean = false;
forcevote: boolean = false;
mute: boolean = false;
kick: boolean = false;
bypassturn: boolean = false;
rename: boolean = false;
grabip: boolean = false;
xss: boolean = false;
constructor(mask: number) {
this.set(mask);
}
set(mask: number) {
this.restore = (mask & 1) !== 0;
this.reboot = (mask & 2) !== 0;
this.ban = (mask & 4) !== 0;
this.forcevote = (mask & 8) !== 0;
this.mute = (mask & 16) !== 0;
this.kick = (mask & 32) !== 0;
this.bypassturn = (mask & 64) !== 0;
this.rename = (mask & 128) !== 0;
this.grabip = (mask & 256) !== 0;
this.xss = (mask & 512) !== 0;
}
}
export enum Rank {
Unregistered = 0,
Registered = 1,
Admin = 2,
Moderator = 3
}
// All used admin opcodes as a enum
export enum AdminOpcode {
Login = 2,
MonitorCommand = 5,
Restore = 8,
Reboot = 10,
BanUser = 12,
ForceVote = 13,
MuteUser = 14,
KickUser = 15,
EndTurn = 16,
ClearTurns = 17,
RenameUser = 18,
GetIP = 19,
BypassTurn = 20,
ChatXSS = 21,
ToggleTurns = 22,
IndefiniteTurn = 23,
HideScreen = 24
}

View File

@ -1,12 +0,0 @@
import { User } from './User.js';
export default interface TurnStatus {
// The user currently taking their turn
user: User | null;
// The users in the turn queue
queue: User[];
// Amount of time left in the turn. Null unless the user is taking their turn
turnTime: number | null;
// Amount of time until the user gets their turn. Null unless the user is in the queue
queueTime: number | null;
}

View File

@ -1,15 +0,0 @@
import { Rank } from './Permissions.js';
export class User {
username: string;
rank: Rank;
// -1 means not in the turn queue, 0 means the current turn, anything else is the position in the queue
turn: number;
countryCode: string | null = null;
constructor(username: string, rank: Rank = Rank.Unregistered) {
this.username = username;
this.rank = rank;
this.turn = -1;
}
}

View File

@ -1,9 +0,0 @@
export default interface VM {
url: string;
id: string;
displayName: string;
thumbnail: HTMLImageElement;
}

View File

@ -1,5 +0,0 @@
export default interface VoteStatus {
timeToEnd: number;
yesVotes: number;
noVotes: number;
}

View File

@ -1,45 +0,0 @@
function maskContains(mask: number, bit: number): boolean {
return (mask & bit) == bit;
}
export default class Mouse {
left: boolean = false;
middle: boolean = false;
right: boolean = false;
scrollDown: boolean = false;
scrollUp: boolean = false;
x: number = 0;
y: number = 0;
constructor() {}
makeMask() {
var mask = 0;
if (this.left) mask |= 1;
if (this.middle) mask |= 2;
if (this.right) mask |= 4;
if (this.scrollUp) mask |= 8;
if (this.scrollDown) mask |= 16;
return mask;
}
initFromMouseEvent(e: MouseEvent) {
this.left = maskContains(e.buttons, 1);
this.right = maskContains(e.buttons, 2);
this.middle = maskContains(e.buttons, 4);
this.x = e.offsetX;
this.y = e.offsetY;
}
// don't think there's a good way of shoehorning this in processEvent so ..
// (I guess could union e to be MouseEvent|WheelEvent and put this in there, but it'd be a
// completely unnesscary runtime check for a one-event situation, so having it be seperate
// and even call the MouseEvent implementation is more than good enough)
initFromWheelEvent(ev: WheelEvent) {
this.initFromMouseEvent(ev as MouseEvent);
// Now do the actual wheel handling
if (ev.deltaY < 0) this.scrollUp = true;
else if (ev.deltaY > 0) this.scrollDown = true;
}
}

View File

@ -1,29 +0,0 @@
import { Format } from '../format';
test('a string without any format specifiers in it is unaltered', () => {
expect(Format('Hello World')).toBe('Hello World');
});
test('formatting a string works', () => {
expect(Format('Hello, {0}!', 'World')).toBe('Hello, World!');
});
test('a cut off format specifier throws', () => {
expect(() => Format('a{0', 1)).toThrow('Cutoff/invalid format specifier');
});
test('a malformed format specifier throws', () => {
expect(() => Format('a{-0}', 1)).toThrow('Malformed format specifier');
expect(() => Format('a{0-}', 1)).toThrow('Malformed format specifier');
expect(() => Format('a{0ab}', 1)).toThrow('Malformed format specifier');
expect(() => Format('a{ab0ab}', 1)).toThrow('Malformed format specifier');
// Whitespace is not permitted inside a format specifier
expect(() => Format('a{0 }', 1)).toThrow('Whitespace inside format specifier');
expect(() => Format('a{ 0}', 1)).toThrow('Whitespace inside format specifier');
expect(() => Format('a{ 0 }', 1)).toThrow('Whitespace inside format specifier');
});
test("a OOB format specifier doesn't work", () => {
expect(() => Format('a {37}', 1)).toThrow('Argument index out of bounds');
});

View File

@ -1,101 +0,0 @@
{
"languageName": "nonsense",
"translatedLanguageName": "nonsense",
"flag": "NS",
"author": "cvmuser1000, skyhighsundae",
"stringKeys": {
"kGeneric_CollabVM": "s09d8sa0d7asdasd708d7as0dßasd",
"kGeneric_Yes": "wq09d8sßad8as90deá98d8asda97287ßsa8d3a7d",
"kGeneric_No": "asdaqsd0asd78asd0asdasd",
"kGeneric_Ok": "DSADASDDUASDASD=",
"kGeneric_Cancel": "NDADASUASIDKAJDSKIUASDLWSUIADJHPAWUSAD",
"kGeneric_Send": "odsjadoiasudoasoidasidsaidasudasd",
"kGeneric_Understood": "odssiad09s8dsad",
"kGeneric_Username": "sad9asu8d30a2880e8weu2q",
"kGeneric_Password": "sa9dasd9802788ej3lklkkjgofopdüsaödlkiwigtrghzg",
"kGeneric_Login": "0129837465ruzfhjcnghjdskhgjfdksgjkds",
"kGeneric_Register": "aölkgjhtujekdksklejjjasddadasdasi",
"kGeneric_EMail": "EDisuad8as7d0asd",
"kGeneric_DateOfBirth": "D9098s9a80ßdsda",
"kGeneric_VerificationCode": "ad98as8d0asdß Code",
"kGeneric_Verify": "das0d9as8d8a0939asd",
"kGeneric_Update": "asd8as8d0sad7as90d",
"kGeneric_Logout": "9erwfeüookawerjioksdiuferjkzofer",
"kWelcomeModal_Header": "Welcome to s09d8sa0d7asdasd708d7as0dßasd (Tweaked)",
"kWelcomeModal_Body": "sdiuasd0ßYOudasd0a9sdas8d90OYußsadsad9asd8As8)(dsad0adas0d098WS8sda87saWds998sa9asd989",
"kSiteButtons_Home": "x0e08d9f9f93ks9s8ad989a9sd829038eqwde",
"kSiteButtons_FAQ": "=?D)S=A(D=)S(A=Eßasd7a08sd0828ßa0dsadjaspd8ß2",
"kSiteButtons_Rules": "9dasd07asd8728a0sd98a89sd=d8as0dadaOIDHASdjADHASOD",
"kSiteButtons_DarkMode": "IDÖOASDOA(DUASDAISDPIS",
"kSiteButtons_LightMode": "DISADISAD)AS)DAS)D)ADASUASIDJASKDASD",
"kSiteButtons_Languages": "s09d8sa0d7asdasd708d7as0dßasd ASD)(AS)D(ASDKASDKASD",
"kVM_UsersOnlineText": "DUSADIASDJUASODASD: ",
"kVM_TurnTimeTimer": "SADI SAODIAO IDOASDO {0} SAD()ASD(AS=DASDD",
"kVM_WaitingTurnTimer": "SDIKSODA SAIDSOAID IOSAD OOASD OI {0} SAODOASDÜ",
"kVM_VoteCooldownTimer": "FKJDJF PDSFPO {0} ASJDLS ASDKJAS ()(FK FDLKD KFO",
"kVM_VoteForResetTitle": "ASDIKOASDIPS )DSJDASIPD VM FDASISDIAPD",
"kVM_VoteForResetTimer": "VSLDKALSDK DLKSALD SAESAPD {0} SDIASDJ",
"kVMButtons_TakeTurn": "SADKOOASDOPDPOAS",
"kVMButtons_EndTurn": "IASUASD)SD",
"kVMButtons_ChangeUsername": "ADAS)(§)SKSODASKDSA",
"kVMButtons_Keyboard": "FDSPFSDOFISDIOPFUPÜS",
"KVMButtons_CtrlAltDel": "ASDOASD+SDOSD+DSI",
"kVMButtons_VoteForReset": "DKSI DAS( RESET",
"kVMButtons_Screenshot": "ASD=ASDASPDIASPDOIASDPIASD",
"kQEMUMonitor": "ADSIOADOASDIPAOSDIASPÜDSAD",
"kAdminVMButtons_PassVote": "SADI)AS(DASDJASD KASJDIASDI ISAI",
"kAdminVMButtons_CancelVote": "SDIASOD IOASDOI",
"kAdminVMButtons_Restore": "SADKASDIAPSODAPSÄDIASUIDPIUASDPASUIDPIASD",
"kAdminVMButtons_Reboot": "ASPODASUIOPDIASDUASPUIDASOIDASIDSAIDOASIDPA",
"kAdminVMButtons_ClearTurnQueue": "SADOPASOKIDSAOÜDIOASDÜSAD",
"kAdminVMButtons_BypassTurn": "ASDKOASDPOASD OASIDASOPD",
"kAdminVMButtons_IndefiniteTurn": "SADKASOÜDOÜ OSADOISÜ",
"kAdminVMButtons_GhostTurnOn": "SADASKDOASDOIASDIO DASIDUSAUID (SAODPAISD)",
"kAdminVMButtons_GhostTurnOff": "SADASKDOASDOIASDIO DASIDUSAUID (SAKDSOAD)",
"kAdminVMButtons_Ban": "ISADASPÜD",
"kAdminVMButtons_Kick": "SDIOASDISAD",
"kAdminVMButtons_TempMute": "SDLKASDPIOAIPD ASIDAS",
"kAdminVMButtons_IndefMute": "SADOASDIPÜASD ASIDAS",
"kAdminVMButtons_Unmute": "ASDIASIDAS",
"kAdminVMButtons_GetIP": "SSDPASDASDÜ)A=D",
"kVMPrompts_AdminChangeUsernamePrompt": "DISUADOASUDZ OASDd iSA DSd {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "ASOIAIPDSDUA sad9 asd9asd uiasd aosd",
"kVMPrompts_EnterNewUsernamePrompt": "ODUSPADIUASP asp9dasp9 asd9 uasid usadi",
"kError_UnexpectedDisconnection": "d9sa98d0asd08SDASDASD",
"kError_UsernameTaken": "saoidipasdpas uasdiuasuodiiouIDUIASDP1",
"kError_UsernameInvalid": "ASDPOASUDA )(ASDIUASD PIUSAIDU SAIDU IDISAUDUISAUISAiuaisa",
"kError_UsernameBlacklisted": "DOSADI OASD ISS=AD =SDJ PSAIDP SIDPJ",
"kError_IncorrectPassword": "DUIASDASD =SAD)A=",
"kAccountModal_Verify": "SADKOSADO OSAIDOISAI",
"kAccountModal_AccountSettings": "SAOSDIIPASD ISADSUAPD",
"kAccountModal_ResetPassword": "SDAOIDOASID IOODOASIO",
"kAccountModal_NewPassword": "XSAPDIASPD IASDOASI)SPAD",
"kAccountModal_ConfirmNewPassword": "DPASDKO ASPDPSD DPAS",
"kAccountModal_CurrentPassword": "DLSADOASD DAOIIO",
"kAccountModal_ConfirmPassword": "ASD=AI OWEJSDJLSD LIASIDI",
"kAccountModal_HideFlag": "DASOSAO OFOGP ÜAÜASDAPDSDASDI IDPSPOPD",
"kMissingCaptcha": "ASDOASIDPOASD SDOSAD OSDIO CAPTCHA",
"kPasswordsMustMatch": "ASODIO SAIOD OISADIO IO SADIOIO",
"kAccountModal_VerifyText": "DL SODSI ID SAIOD SOID JSADSIOADJASJID ASDOSA JDSAOJ",
"kAccountModal_VerifyPasswordResetText": "SADIO ASDIP ASD(I PSAIOD P)ASD( AS)D )ASD) A)SD )SAUJD IASD J JSA J ASODOSADOSADISAIPD",
"kAccountModal_PasswordResetSuccess": "SADSA)D )SAD(AS)D )SAD)()(A )(SAD())(S ()AS()D)(ASDASD",
"kNotLoggedIn": "DASKD KSAKJSA DSJ"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Deutsch",
"translatedLanguageName": "German",
"flag": "🇩🇪",
"author": "julias.zone, nephacks",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Ja",
"kGeneric_No": "Nein",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Abbrechen",
"kGeneric_Send": "Senden",
"kGeneric_Understood": "Verstanden",
"kGeneric_Username": "Benutzername",
"kGeneric_Password": "Passwort",
"kGeneric_Login": "Einloggen",
"kGeneric_Register": "Registrieren",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Geburtsdatum",
"kGeneric_VerificationCode": "Verifizierungscode",
"kGeneric_Verify": "Überprüfen",
"kGeneric_Update": "Aktualisieren",
"kGeneric_Logout": "Abmelden",
"kWelcomeModal_Header": "Willkommen zu CollabVM",
"kWelcomeModal_Body": "<p>Bevor Sie fortfahren, machen Sie sich bitte mit unseren Regeln vertraut:</p> <h3>R1. Verbrechen Sie nicht das Gesetz.</h3> Verwenden Sie CollabVM oder das CollabVM-Netzwerk nicht, um das Bundesgesetz der Vereinigten Staaten, das Gesetz des Staates New York oder internationales Recht zu verletzen. Wenn CollabVM erfährt, dass eine Straftat durch seinen Dienst begangen wurde, werden Sie sofort gesperrt und Ihre Aktivitäten können den Behörden gemeldet werden, falls erforderlich.<br/> CollabVM ist gesetzlich verpflichtet, die Strafverfolgungsbehörden zu benachrichtigen, wenn es Kenntnis vom Vorhandensein von Kinderpornographie in seinem Netzwerk oder deren Übertragung erhält.<br/> COPPA wird auch durchgesetzt, bitte benutzen Sie CollabVM nicht, wenn Sie unter 13 Jahre alt sind. <h3>R2. Keine DoS/DDoS-Tools.</h3> Benutzen Sie CollabVM nicht, um DoS/DDoS gegen eine Einzelperson, ein Geschäft, ein Unternehmen oder eine andere Person zu betreiben.<h3>R3. Kein Spam-Versand.</h3> Verwenden Sie diesen Dienst nicht zum Versenden von Spam-E-Mails oder zum Versenden von Spam im Allgemeinen.<h3>R4. Keinen Missbrauch von Exploits.</h3> Missbrauche keine Exploits. Wenn Sie jemanden sehen, der Exploits missbraucht, oder wenn Sie einen Exploit melden möchtest, kontaktiere mich bitte unter: computernewbab@gmail.com <h3>R5. Geben Sie sich nicht als andere Benutzer aus.</h3> Geben Sie sich nicht als andere Mitglieder von CollabVM aus. Wenn Sie erwischt werden, werden Sie vorübergehend von der Verbindung getrennt und gegebenenfalls gesperrt. <h3>R6. Eine Stimme pro Person.</h3> Verwenden Sie keine Methoden oder Hilfsmittel, um die Abstimmungsbeschränkung zu umgehen. Es ist nur eine Stimme pro Person erlaubt, egal wie. Jeder, der dabei erwischt wird, wird gebannt. <h3>R7. Keine Fernadministrations-Tools.</h3> Es dürfen keine Fernadministrations-Tools verwendet werden (z.B. DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, uzw.)<h3>R8. Kein Umgehen von CollabNet</h3> Versuchen Sie nicht, die von CollabNet bereitgestellten Sperren zu umgehen, insbesondere dann nicht, wenn sie dazu benutzt werden, Regel 1, Regel 2 oder Regel 7 zu brechen (oder dumme, übermäßige Dinge auszuführen).<h3>R9. Keine ständigen destruktiven Aktionen durchführen.</h3> Jeder Nutzer darf die VM nicht zerstören (was es ständig unbrauchbar macht), ein anderes Betriebssystem installieren oder das Betriebssystem neu installieren (außer auf VM7 oder VM8) oder Bots ausführen, die dies tun. Dies schließt Bots ein, die massive Mengen an Tastatur-/Mauseingaben spammen (so genanntes \"Kitting\"). <h3>R10. Kein Cryptomining</h3> Der Versuch, auf den VMs Kryptowährungen zu \"schürfen\", führt zu einem Kick und dann zu einem permanenten Bann, wenn Sie es weiter versuchen. Außerdem ist es ja nicht so, dass Sie damit Geld verdienen würden.<h3>NSFW-Warnung</h3> Bitte beachten Sie, dass NSFW-Inhalte auf unserer Anarcho-VM (VM0b0t) erlaubt sind und regelmäßig angesehen werden. Obwohl wir uns bemühen, NSFW von den Haupt-VMs fernzuhalten, kann es vorkommen, dass was durchrutscht. </div>",
"kSiteButtons_Home": "Hauptseite",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Regeln",
"kSiteButtons_DarkMode": "Dunkelmodus",
"kSiteButtons_LightMode": "Heller Modus",
"kSiteButtons_Languages": "Sprachen",
"kVM_UsersOnlineText": "Nutzer online:",
"kVM_TurnTimeTimer": "Ihre Zeit läuft in {0} Sekunden aus.",
"kVM_WaitingTurnTimer": "In der Reihe warten für {0} Sekunden.",
"kVM_VoteCooldownTimer": "Bitte warten Sie {0} Sekunden bevor Sie noch eine Abstimmung starten.",
"kVM_VoteForResetTitle": "Möchten Sie die VM zurücksetzen?",
"kVM_VoteForResetTimer": "Abstimmung endet in {0} Sekunden",
"kVMButtons_TakeTurn": "In Reihe stellen.",
"kVMButtons_EndTurn": "Aus der Reihe kommen",
"kVMButtons_ChangeUsername": "Nutzername ändern",
"kVMButtons_Keyboard": "Tastatur",
"KVMButtons_CtrlAltDel": "Strg+Alt+Entf",
"kVMButtons_VoteForReset": "Für Zurücksetzung abstimmen",
"kVMButtons_Screenshot": "Screenshot",
"kQEMUMonitor": "QEMU-Konsole",
"kAdminVMButtons_PassVote": "Abstimmung abschließen",
"kAdminVMButtons_CancelVote": "Abstimmung abbrechen",
"kAdminVMButtons_Restore": "Wiederherstellen",
"kAdminVMButtons_Reboot": "Neustarten",
"kAdminVMButtons_ClearTurnQueue": "Warteschlange leeren",
"kAdminVMButtons_BypassTurn": "Reihe überspringen",
"kAdminVMButtons_IndefiniteTurn": "Unbegrenzter Zeit",
"kAdminVMButtons_Ban": "Sperren",
"kAdminVMButtons_Kick": "Kick",
"kAdminVMButtons_TempMute": "Temporäre Stummschaltung",
"kAdminVMButtons_IndefMute": "Dauerhafte Stummschaltung",
"kAdminVMButtons_Unmute": "Entstummen",
"kAdminVMButtons_GetIP": "IP-Adresse abrufen",
"kVMPrompts_AdminChangeUsernamePrompt": "Geben Sie einen neuen Benutzernamen für {0} ein:",
"kVMPrompts_AdminRestoreVMPrompt": "Sind Sie sicher, dass Sie die virtuelle Maschine wiederherstellen möchten?",
"kVMPrompts_EnterNewUsernamePrompt": "Geben Sie Ihren neuen Nutzername ein.",
"kError_UnexpectedDisconnection": "Sie wurden vom Server getrennt.",
"kError_UsernameTaken": "Dieser Benutzername ist bereits vergeben.",
"kError_UsernameInvalid": "Benutzernamen dürfen nur Zahlen, Buchstaben, Leerzeichen, Bindestriche, Unterstriche und Punkte enthalten und müssen zwischen 3 und 20 Zeichen lang sein.",
"kError_UsernameBlacklisted": "Dieser Benutzername ist verboten.",
"kError_IncorrectPassword": "Falsches Passwort.",
"kAccountModal_Verify": "E-Mail Adresse verifizieren",
"kAccountModal_AccountSettings": "Kontoeinstellungen",
"kAccountModal_ResetPassword": "Passwort zurücksetzen",
"kAccountModal_NewPassword": "Neues Passwort",
"kAccountModal_ConfirmNewPassword": "Neues Passwort bestätigen",
"kAccountModal_CurrentPassword": "Aktuelles Passwort",
"kAccountModal_ConfirmPassword": "Passwort bestätigen",
"kMissingCaptcha": "Bitte füllen Sie das Captcha aus.",
"kPasswordsMustMatch": "Die Passwörter müssen übereinstimmen.",
"kAccountModal_VerifyText": "Wir haben eine E-Mail an {0} verschickt. Um Ihr Konto zu verifizieren, geben Sie bitte den 8-stelligen Code aus der E-Mail unten ein.",
"kAccountModal_VerifyPasswordResetText": "Wir haben eine E-Mail an {0} verschickt. Um Ihr Passwort zurückzusetzen, geben Sie bitte den 8-stelligen Code aus der E-Mail unten ein.",
"kAccountModal_PasswordResetSuccess": "Ihr Passwort wurde erfolgreich geändert. Sie können sich jetzt mit Ihrem neuen Passwort anmelden.",
"kNotLoggedIn": "Nicht angemeldet"
}
}

View File

@ -1,101 +0,0 @@
{
"languageName": "English (US)",
"translatedLanguageName": "English (US)",
"flag": "🇺🇸",
"author": "Computernewb",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Yes",
"kGeneric_No": "No",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Cancel",
"kGeneric_Send": "Send",
"kGeneric_Understood": "Understood",
"kGeneric_Username": "Username",
"kGeneric_Password": "Password",
"kGeneric_Login": "Log in",
"kGeneric_Register": "Register",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Date of Birth",
"kGeneric_VerificationCode": "Verification Code",
"kGeneric_Verify": "Verify",
"kGeneric_Update": "Update",
"kGeneric_Logout": "Log out",
"kWelcomeModal_Header": "Welcome to CollabVM (Tweaked)",
"kWelcomeModal_Body": "<p>Before continuing, please familiarize yourself with our rules:</p> <h3>R1. Don't break the law.</h3> Do not use CollabVM or CollabVM's network to violate United States federal law, New York state law, or international law. If CollabVM becomes aware a crime has been committed through its service, you will be immediately banned, and your activities may be reported to the authorities if necessary.<br><br>CollabVM is required by law to notify law enforcement agencies if it becomes aware of the presence of child pornography on, or being transmitted through its network.<br><br>COPPA is also enforced, please do not use CollabVM if you are under the age of 13 years old. <h3>R2. No running DoS/DDoS tools.</h3> Do not use CollabVM to DoS/DDoS an indivdiual, business, company, or anyone else. <h3>R3. No spam distribution.</h3> Do not spam any emails using this service or push spam in general. <h3>R4. Do not abuse any exploits.</h3> Do not abuse any exploits, additionally if you see someone abusing exploits or you need to report one, please contact me at: computernewbab@gmail.com <h3>R5. Don't impersonate other users.</h3> Do not impersonate other members of CollabVM. If caught, you'll be temporarily disconnected, and banned if necessary. <h3>R6. One vote per person.</h3> Do not use any methods or tools to bypass the vote restriction. Only one vote per person is allowed, no matter what. Anybody who is caught doing this will be banned. <h3>R7. No Remote Administration Tools.</h3> Do not use any remote administration tools (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.) <h3>R8. No bypassing CollabNet.</h3> Do not attempt to bypass the blocking provided by CollabNet, especially if it is being used to break Rule 1, Rule 2, or Rule 7 (or run stupid over-used things). <h3>R9. No performing destructive actions constantly.</h3> Any user may not destroy the VM (rendering it unusable constantly), install/reinstall the operating system (except on VM7 or VM8), or run bots that do such. This includes bots that spam massive amounts of keyboard/mouse input (\"kitting\"). <h3>R10. No Cryptomining</h3> Attempting to mine cryptocurrency on the VMs will result in a kick, and then a permanent ban if you keep attempting. Besides, it's not like you're gonna make any money off it. <h3>NSFW Warning</h3> Please note that NSFW content is allowed on our anarchy VM (VM0b0t), and is viewed regularly. In addition, while we give a good effort to keep NSFW off the main VMs, people will occasionally slip it through.",
"kSiteButtons_Home": "Home",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Rules",
"kSiteButtons_DarkMode": "Dark Mode",
"kSiteButtons_LightMode": "Light Mode",
"kSiteButtons_Languages": "CollabVM Languages",
"kVM_UsersOnlineText": "Users Online:",
"kVM_TurnTimeTimer": "Turn expires in {0} seconds.",
"kVM_WaitingTurnTimer": "Waiting for turn in {0} seconds.",
"kVM_VoteCooldownTimer": "Please wait {0} seconds before starting another vote.",
"kVM_VoteForResetTitle": "Do you want to reset the VM?",
"kVM_VoteForResetTimer": "Vote ends in {0} seconds",
"kVMButtons_TakeTurn": "Take Turn",
"kVMButtons_EndTurn": "End Turn",
"kVMButtons_ChangeUsername": "Username Settings",
"kVMButtons_Keyboard": "Show Keyboard",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Vote For Reset",
"kVMButtons_Screenshot": "Screenshot",
"kQEMUMonitor": "QEMU Monitor",
"kAdminVMButtons_PassVote": "Pass Vote",
"kAdminVMButtons_CancelVote": "Cancel Vote",
"kAdminVMButtons_Restore": "Restore",
"kAdminVMButtons_Reboot": "Reboot",
"kAdminVMButtons_ClearTurnQueue": "Clear Turn Queue",
"kAdminVMButtons_BypassTurn": "Bypass Turn",
"kAdminVMButtons_IndefiniteTurn": "Indefinite Turn",
"kAdminVMButtons_GhostTurnOn": "Ghost Turn (On)",
"kAdminVMButtons_GhostTurnOff": "Ghost Turn (Off)",
"kAdminVMButtons_Ban": "Ban",
"kAdminVMButtons_Kick": "Kick",
"kAdminVMButtons_TempMute": "Temporary Mute",
"kAdminVMButtons_IndefMute": "Permanent Mute",
"kAdminVMButtons_Unmute": "Unmute",
"kAdminVMButtons_GetIP": "Get IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Enter new username for {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Are you sure you want to restore the VM?",
"kVMPrompts_EnterNewUsernamePrompt": "Enter a new username, or leave the field blank to be assigned a guest username",
"kError_UnexpectedDisconnection": "You have been disconnected from the server.",
"kError_UsernameTaken": "That username is already taken",
"kError_UsernameInvalid": "Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters.",
"kError_UsernameBlacklisted": "That username has been blacklisted.",
"kError_IncorrectPassword": "Incorrect password.",
"kAccountModal_Verify": "Verify E-Mail",
"kAccountModal_AccountSettings": "Account Settings",
"kAccountModal_ResetPassword": "Reset Password",
"kAccountModal_NewPassword": "New Password",
"kAccountModal_ConfirmNewPassword": "Confirm New Password",
"kAccountModal_CurrentPassword": "Current Password",
"kAccountModal_ConfirmPassword": "Confirm Password",
"kAccountModal_HideFlag": "Hide my Country Flag from the Users Online section",
"kMissingCaptcha": "Please fill out the captcha.",
"kPasswordsMustMatch": "Passwords must match.",
"kAccountModal_VerifyText": "We sent an E-Mail to {0}. To verify your account, please enter the 8-digit code from the E-Mail below.",
"kAccountModal_VerifyPasswordResetText": "We sent an E-Mail to {0}. To reset your password, please enter the 8-digit code from the E-Mail below.",
"kAccountModal_PasswordResetSuccess": "Your password has been changed successfully. You can now log in with your new password.",
"kNotLoggedIn": "Not Logged in"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Español (España)",
"translatedLanguageName": "Spanish (Spain)",
"flag": "🇪🇸",
"author": "DXD Tech, IPTVman",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Si",
"kGeneric_No": "No",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Cancelar",
"kGeneric_Send": "Enviar",
"kGeneric_Understood": "Entendido",
"kGeneric_Username": "Nombre de usuario",
"kGeneric_Password": "Contraseña",
"kGeneric_Login": "Iniciar sesión",
"kGeneric_Register": "Registrar",
"kGeneric_EMail": "Correo",
"kGeneric_DateOfBirth": "Fecha de nacimiento",
"kGeneric_VerificationCode": "Código de verificación",
"kGeneric_Verify": "Verificar",
"kGeneric_Update": "Actualizar",
"kGeneric_Logout": "Cerrar sesión",
"kWelcomeModal_Header": "Bienvenido a CollabVM",
"kWelcomeModal_Body": "<p>Antes de continuar, por favor familiarízate con nuestras reglas:</p> <h3>R1. No rompas la ley.</h3> No uses CollabVM o la red de CollabVM para violar la ley federal de Estados Unidos, ley estatal de Nueva York, o ley internacional. Si CollabVM toma consciencia de que un crimen ha sido cometido a través de su servicio, serás baneado inmediatamente, y tus actividades serán reportadas a las autoridades si es necesario.<br><br>CollabVM está requerido por ley a informar a las agencias de cumplimiento de la ley si se toma consciencia de pornografía infantil en, o siendo transmitida a través de su red.<br><br>La Ley de Protección de la Privacidad en Línea para Niños (COPPA) también está impuesta, por favor no utilices CollabVM si tienes menos de 13 años. <h3>R2. No ejecutar herramientas DoS/DDoS.</h3> No utilices CollabVM para lanzar ataques DoS/DDoS (denegación de servicio) a un individuo, empresa, compañía, o cualquier otra persona. <h3>R3. No distribuir spam.</h3> No envíes spam a ningún email usando este servicio ni envíes spam en general. <h3>R4. No abuses de ninguna vulnerabilidad.</h3> No abuses de ninguna vulnerabilidad de seguridad. Además, si ves a alguien abusando de vulnerabilidades o necesitas informar de una, por favor contáctame en: computernewbab@gmail.com <h3>R5. No suplantes a otros usuarios.</h3> No te hagas pasar por otros miembros de CollabVM. Si se te encuentra haciéndolo, serás desconectado temporalmente y baneado si es necesario. <h3>R6. Un voto por persona.</h3> No uses otros métodos o herramientas para evitar la restricción de votos. Solo se permite un voto por persona, pase lo que pase. Cualquiera que se le encuentre haciéndolo será baneado. <h3>R7. No herramientas de administración remota.</h3> No utilices herramientas de administración remota (ej.: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.) <h3>R8. No saltarse CollabNet.</h3> No intentes saltarte el bloqueo provisto por CollabNet, especialmente si se está utilizando para romper la regla 1, regla 2, o regla 7 (o para ejecutar cosas estúpidas sobreutilizadas). <h3>R9. No realizar acciones destructivas constantemente.</h3> Ningún usuario debería destruir la máquina virtual (haciéndola inusable constantemente), instalar/reinstalar el sistema operativo (menos en VM7 o VM8), o ejecutar bots que lo hagan. Esto incluye bots que hagan spam de cantidades masivas de entradas de teclado/mouse (\"kitting\"). <h3>R10. No minería de criptomonedas.</h3> Intentar minar criptomonedas en las máquinas virtuales resultará en una expulsión, y después un bloqueo permanente si lo sigues intentando. Además, no es que vayas a ganar dinero de ello. <h3>Aviso por NSFW</h3> Por favor ten en cuenta que se permite el contenido NSFW (no seguro para el trabajo) en nuestra máquina virtual anárquica (VM0b0t), y se ve a menudo. Además, aunque nos esforzamos en mantener el contenido NSFW fuera de las máquinas virtuales principales, las personas ocasionalmente lo pasan por alto.",
"kSiteButtons_Home": "Inicio",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Reglas",
"kSiteButtons_DarkMode": "Modo oscuro",
"kSiteButtons_LightMode": "Modo claro",
"kSiteButtons_Languages": "Idiomas",
"kVM_UsersOnlineText": "Usuarios en línea:",
"kVM_TurnTimeTimer": "El turno expira en {0} segundos.",
"kVM_WaitingTurnTimer": "Esperando al turno en {0} segundos.",
"kVM_VoteCooldownTimer": "Por favor espera {0} segundos antes de empezar otro voto.",
"kVM_VoteForResetTitle": "¿Quieres reiniciar la máquina virtual?",
"kVM_VoteForResetTimer": "El voto acaba en {0} segundos",
"kVMButtons_TakeTurn": "Tomar turno",
"kVMButtons_EndTurn": "Terminar turno",
"kVMButtons_ChangeUsername": "Cambiar nombre de usuario",
"kVMButtons_Keyboard": "Teclado",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Votar por reiniciar",
"kVMButtons_Screenshot": "Captura de pantalla",
"kQEMUMonitor": "Monitor QEMU",
"kAdminVMButtons_PassVote": "Aprobar voto",
"kAdminVMButtons_CancelVote": "Cancelar voto",
"kAdminVMButtons_Restore": "Restaurar",
"kAdminVMButtons_Reboot": "Reiniciar",
"kAdminVMButtons_ClearTurnQueue": "Borrar la cola de turno",
"kAdminVMButtons_BypassTurn": "Pasar por alto el turno",
"kAdminVMButtons_IndefiniteTurn": "Turno indefinido",
"kAdminVMButtons_Ban": "Prohibir",
"kAdminVMButtons_Kick": "Expulsar",
"kAdminVMButtons_TempMute": "Silencio temporal",
"kAdminVMButtons_IndefMute": "Silencio indefinido",
"kAdminVMButtons_Unmute": "Eliminar silencio",
"kAdminVMButtons_GetIP": "Obtener IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Ingrese nuevo nombre de usuario para {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "¿Estás seguro de que quieres restaurar la Máquina Virtual?",
"kVMPrompts_EnterNewUsernamePrompt": "Ingrese un nuevo nombre de usuario o deje el campo en blanco para que se le asigne un nombre de usuario invitado",
"kError_UnexpectedDisconnection": "Has sido desconectado del servidor.",
"kError_UsernameTaken": "Ese nombre de usuario ya se encuentra en uso",
"kError_UsernameInvalid": "Los nombres de usuario solo pueden contener números, letras, espacios, guiones, subrayos y puntos, y debe ser entre 3 a 20 caracteres.",
"kError_UsernameBlacklisted": "Ese nombre de usuario ha sido prohibido.",
"kError_IncorrectPassword": "Contraseña incorrecta.",
"kAccountModal_Verify": "Verificar correo",
"kAccountModal_AccountSettings": "Configuraciones de la cuenta",
"kAccountModal_ResetPassword": "Restablecer la contraseña",
"kAccountModal_NewPassword": "Nueva contraseña",
"kAccountModal_ConfirmNewPassword": "Confirmar nueva contraseña",
"kAccountModal_CurrentPassword": "Contraseña actual",
"kAccountModal_ConfirmPassword": "Confirmar Contraseña",
"kMissingCaptcha": "Por favor, complete el captcha.",
"kPasswordsMustMatch": "Las contraseñas deben coincidir.",
"kAccountModal_VerifyText": "Enviamos un correo electrónico a {0}. Para verificar su cuenta, ingrese el código de 8 dígitos del correo electrónico a continuación.",
"kAccountModal_VerifyPasswordResetText": "Enviamos un correo electrónico a {0}. Para restablecer su contraseña, ingrese el código de 8 dígitos del correo electrónico a continuación.",
"kAccountModal_PasswordResetSuccess": "Tu contraseña ha sido cambiada exitosamente. Ahora puede iniciar sesión con su nueva contraseña.",
"kNotLoggedIn": "Sin iniciar sesión"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Français (FR)",
"translatedLanguageName": "French (FR)",
"flag": "🇫🇷",
"author": "pikterra",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Oui",
"kGeneric_No": "Non",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Annuler",
"kGeneric_Send": "Envoyer",
"kGeneric_Understood": "Compris",
"kGeneric_Username": "Nom d'utilisateur",
"kGeneric_Password": "Mot de passe",
"kGeneric_Login": "Connection",
"kGeneric_Register": "Inscription",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Date de naissance",
"kGeneric_VerificationCode": "Code de Vérification",
"kGeneric_Verify": "Verifier",
"kGeneric_Update": "Mise à jour",
"kGeneric_Logout": "Déconnection",
"kWelcomeModal_Header": "Bienvenue sur CollabVM",
"kWelcomeModal_Body": "<p>Avant de continuer, veuillez vous familiariser avec nos règles:</p> <h3>R1. Respecter la Loi.</h3> N'utilisez pas le réseau CollabVM pour violez les lois fédéral des Etats-Unis, lois de New-York, ou les lois International. Si CollabVM trouve qu'un crime a été commis vous allez etre immédiatement bannis, et vos activités peuvent être signaler aux authorités si nécessaire.<br><br>CollabVM se vera de notifier des agences d'enforcement des lois si il trouve de la présence de pédo-pornographie sur ou étant transmis par le réseau.<br><br>Les lois COPPA sont aussi misent en place, n'utilisez pas CollabVM si vous avez moins de 13 ans. <h3>R2. N'utilisez pas d'outils de DoS/DDoS.</h3> N'utilisez pas CollabVM pour Dos/DDoS un individu, un business, une entreprise ou n'importe qui d'autre. <h3>R3. Pas de spam.</h3> Ne spammez pas d'email à l'aide de ce service. <h3>R4. N'abusez pas d'exploits.</h3> N'abusez pas d'exploits, et si vous voyez quelq'un abusez d'exploits ou que vous avez besoin d'en signaler un, veuillez me contacter à : computernewbab@gmail.com <h3>R5. N'usurpez pas l'identité d'autres utilisateurs.</h3> N'usurpez pas d'autres membres de CollabVM. Si vous êtes pris sur le fait vous allez être temporairement déconnecté, et bannis si nécessaire. <h3>R6. Un seul vote par personne.</h3> N'utilisez pas de méthodes ou d'outils pour bypasser les restrictions du système de votes. Un seul vote par personne est autorisé, peu importe quoi. N'importe qui trouvé a faire cela sera bannis. <h3>R7. Pas d'outils d'accès a distance.</h3> N'utilisez pas d'outils d'accès a distance (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.) <h3>R8. Ne bypasser pas CollabNet.</h3> N'éssayer pas de bypasser le bloquage introduis par CollabNet, surtout si ces pour briser les régles 1, 2 et 7 (ou utiliser des choses trop utilisés). <h3>R9. Ne faites pas d'actions destructives constamment.</h3> Les utilisateurs ne doivent pas détruire un VM (le rendant inutilisable constamment), installer/réinstaller le système d'exploitation (a part sur VM7 et sur VM8), ou faire des bots qui le font. Cela inclus aussi les bots qui spam le clavier et la souris (\"kitting\"). <h3>R10. Pas de minage de cryptomonnaie</h3> Vous allez vous faire exclure si vous tentez de miner de la cryptomonnaie sur un des VMs, puis un ban permanent si vous persister. Mais de toute façon ces pas comme si vous allez vous faire de l'argent comme ça. <h3>Attention NSFW</h3> Attention veuillez noter que le contenu NSFW est autorisé sur le VM anarchie (VM0b0t), et est vue réguilièrement. De plus, nous faisons des efforts pour tenir le contenu NSFW hors des VMs général, mais des utilisateurs arrivent quand même parfois à en montrer.",
"kSiteButtons_Home": "Menu",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Règles",
"kSiteButtons_DarkMode": "Mode sombre",
"kSiteButtons_LightMode": "Mode clair",
"kSiteButtons_Languages": "Languages",
"kVM_UsersOnlineText": "Utilisateurs en ligne:",
"kVM_TurnTimeTimer": "Vôtre tour se termine dans {0} secondes.",
"kVM_WaitingTurnTimer": "Vôtre tour sera dans {0} secondes.",
"kVM_VoteCooldownTimer": "Veuillez attendre {0} secondes avant de commencer un autre vote.",
"kVM_VoteForResetTitle": "Voulez vous réinitialiser le VM ?",
"kVM_VoteForResetTimer": "Le vote se termine dans {0} secondes",
"kVMButtons_TakeTurn": "Commencer vôtre tour",
"kVMButtons_EndTurn": "Terminer vôtre tour",
"kVMButtons_ChangeUsername": "Changer de nom d'utilisateur",
"kVMButtons_Keyboard": "Clavier",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Commencer un vote pour reset",
"kVMButtons_Screenshot": "Capture d'écran",
"kQEMUMonitor": "Moniteur QEMU",
"kAdminVMButtons_PassVote": "Passer le vote",
"kAdminVMButtons_CancelVote": "Annuler le vote",
"kAdminVMButtons_Restore": "Rétablir",
"kAdminVMButtons_Reboot": "Redémarrer",
"kAdminVMButtons_ClearTurnQueue": "Supprimer la queue pour un tour",
"kAdminVMButtons_BypassTurn": "Bypasser vôtre tour",
"kAdminVMButtons_IndefiniteTurn": "Tour indéfinis",
"kAdminVMButtons_Ban": "Ban",
"kAdminVMButtons_Kick": "Exclusion",
"kAdminVMButtons_TempMute": "Mute temporaire",
"kAdminVMButtons_IndefMute": "Mute indéfinis",
"kAdminVMButtons_Unmute": "Unmute",
"kAdminVMButtons_GetIP": "Trouver l'IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Entrer un nouveau nom d'utilisateur pour {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Êtes-vous sûr de rétablir le VM?",
"kVMPrompts_EnterNewUsernamePrompt": "Entrer un nouveau nom d'utilisateur, ou laisser l'espace vide pour etre assigner un nom d'utilisateur invité",
"kError_UnexpectedDisconnection": "Vous avez été déconnecté du serveur.",
"kError_UsernameTaken": "Ce nom d'utilisateur est déjà pris",
"kError_UsernameInvalid": "Les noms d'utilisateurs ne peuvent que contenirs des chiffres, lettres, éspaces, tirets, tirets du bas, et des points, et ils doivent êtres d'une longueur entre 3 et 20.",
"kError_UsernameBlacklisted": "Ce nom d'utilisateur est interdit.",
"kError_IncorrectPassword": "Mot de passe incorrect.",
"kAccountModal_Verify": "Vérifier l'E-mail",
"kAccountModal_AccountSettings": "Paramètres de compte",
"kAccountModal_ResetPassword": "Réinitialiser le Mot de passe",
"kAccountModal_NewPassword": "Nouveau Mot de passe",
"kAccountModal_ConfirmNewPassword": "Confirmer le nouveau Mot de passe",
"kAccountModal_CurrentPassword": "Mot de passe actuel",
"kAccountModal_ConfirmPassword": "Confirmer le nouveau Mot de passe",
"kMissingCaptcha": "Veuillez compléter ce Captcha.",
"kPasswordsMustMatch": "Les Mot de passes doivent se corréspondre.",
"kAccountModal_VerifyText": "Nous avons envoyez un E-mail à {0}. Pour vérifier votre compte, veuillez entrer le mot de passe à 8 chiffre envoyé par E-mail.",
"kAccountModal_VerifyPasswordResetText": "Nous avons envoyez un E-mail à {0}. Pour réinitialiser votre Mot de passe, veuillez entrer le mot de passe à 8 chiffre envoyé par E-mail.",
"kAccountModal_PasswordResetSuccess": "Vôtre Mot de passe à été réinitialiser avec succès. Vous pouvez maintenant vous connecter avec votre nouveau Mot de passe.",
"kNotLoggedIn": "Non connecté"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Hrvatski",
"translatedLanguageName": "Croatian",
"flag": "🇭🇷",
"author": "xDLiveRBLX",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Da",
"kGeneric_No": "Ne",
"kGeneric_Ok": "U redu",
"kGeneric_Cancel": "Odkaži",
"kGeneric_Send": "Pošalji",
"kGeneric_Understood": "Razumljeno",
"kGeneric_Username": "Korisničko ime",
"kGeneric_Password": "Lozinka",
"kGeneric_Login": "Prijavi se",
"kGeneric_Register": "Registriraj se",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Datum rođenja",
"kGeneric_VerificationCode": "Verifikacijski kod",
"kGeneric_Verify": "Potvrdi",
"kGeneric_Update": "Ažuriraj",
"kGeneric_Logout": "Odjavi se",
"kWelcomeModal_Header": "Dobrodošli u CollabVM",
"kWelcomeModal_Body": "<p>Prije nastavka, upoznajte se sa našim pravilima:</p> <h3>R1. Nemojte kršiti zakon.</h3> Ne koristite CollabVM da biste kršili federalni zakon SAD-a, zakon savezne države New York ili međunarodni zakon. Ako CollabVM sazna da je preko usluge učinjen zakon, odmah ćete dobiti ban sa usluge a vaše aktivnosti mogu biti prijavljene policiji ako je potrebno.<br><br>CollabVM je dužan po zakonu obavijestiti agencije ako postane svjestan o prisutnosti dječje pornografije ili prijenosa kroz njihovu mrežu.<br><br>COPPA je također provođen, nemojte koristiti CollabVM ako ste mlađi od 13 godina. <h3>R2. Nema pokretanja/korištenja DOS/DDoS alata.</h3> Ne koristite CollabVM za provođenje DOS/DDoS napada na pojedince, tvrtke, kompanije ili bilo koga drugoga. <h3>R3. Ne šaljite neželjenu poštu.</h3> Ne šaljite neželjenu poštu kroz ovu uslugu i općenito ne šaljite neželjene stvari. <h3>R4. Nemojte zlorabiti exploite. </h3> Nemojte zlorabiti bilo kakve exploite, dodatno, ako vidite nekoga da zlorabljuje exploite ili trebate priajviti jedan, molim Vas kontaktirajte me na: computernewbab@gmail.com <h3>R5. Nemojte lažno predstavljati druge korisnike.</h3> Nemojte lažno predstavljati druge korisnike CollabVM-a. Ako ste ulovljeni, biti ćete privremeno odspojeni sa stranice i ban-ani ako potrebno. <h3>R6. Jedan glas po osobi.</h3> Nemojte koristiti nikakve metode ili alate za zaobilaženje ograničenja glasova. Samo jedan glas je dozvoljen po osobi, bez obzira na sve. Svatko tko bude ulovljen biti će ban-an. <h3>R7. Nema alata za daljinski pristup.</h3> Ne koristite nikakve alate za daljinski pristup (npr: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, itd.) <h3>R8. Nema zaobilaženja CollabNet-a.</h3> Ne pokušavajte zaobilaziti blokiranje koje nudi CollabNet, pogotovo ako se koristi za kršenje 1., 2., ili 7., pravila. (ili za pokretanje glupih previše korištenih stvari.). <h3>R9. Nema stalnog izvođenja uništavajućih radnji.</h3> Bilo koji korisnik ne smije uništiti VM (čineći ga stalno neupotrebljivim), instalirati/deinstalirati operativni sustav (osim na VM7 ili VM8), ili pokretati botove koje to čine. Ovo uključuje botove koji unose goleme količine unosa tipkovnice ili miša. (\"kitting\"). <h3>R10. Nema rudarenja kriptovaluta.</h3> Pokušavanje rudarenja kriptovaluta na ovim VM-ovima rezultirati će izbačenjem, a zatim trajnim banom ako nastavite. Osim toga, nije kao da ćete zaraditi ikakve novce na tome. <h3>NSFW upozorenje</h3> Imajte na umu da je NSFW sadržaj dopušten na anarchy VMu (VM0b0t) i da se redovito pregledava. Osim toga, dok se jako trudimo spriječiti NSFW od glavnih VMova, ponekad će se prošuljati.",
"kSiteButtons_Home": "Glavna stranica",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Pravila",
"kSiteButtons_DarkMode": "Tamni način prikaza",
"kSiteButtons_LightMode": "Svjetli način prikaza",
"kSiteButtons_Languages": "Jezici",
"kVM_UsersOnlineText": "Korisnici na mreži:",
"kVM_TurnTimeTimer": "Red isteće za {0} sekundi.",
"kVM_WaitingTurnTimer": "Čekanje za red u {0} sekundi.",
"kVM_VoteCooldownTimer": "Molim Vas pričekajte {0} sekundi prije početka novog glasanja.",
"kVM_VoteForResetTitle": "Želite li resetirati VM?",
"kVM_VoteForResetTimer": "Glasanje završava za {0} sekundi",
"kVMButtons_TakeTurn": "Uzmi red",
"kVMButtons_EndTurn": "Završi red",
"kVMButtons_ChangeUsername": "Promijeni korisničko ime",
"kVMButtons_Keyboard": "Tipkovnica",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Glasaj za reset",
"kVMButtons_Screenshot": "Screenshot",
"kQEMUMonitor": "QEMU Monitor",
"kAdminVMButtons_PassVote": "Prođi glasovanje",
"kAdminVMButtons_CancelVote": "Otkaži glasovanje",
"kAdminVMButtons_Restore": "Vrati",
"kAdminVMButtons_Reboot": "Ponovno pokreni",
"kAdminVMButtons_ClearTurnQueue": "Očisti red",
"kAdminVMButtons_BypassTurn": "Preskoči red",
"kAdminVMButtons_IndefiniteTurn": "Neograničen red",
"kAdminVMButtons_Ban": "Ban",
"kAdminVMButtons_Kick": "Izbačaj",
"kAdminVMButtons_TempMute": "Privremeni mute",
"kAdminVMButtons_IndefMute": "Trajni mute",
"kAdminVMButtons_Unmute": "Maknite mute",
"kAdminVMButtons_GetIP": "Dohvati IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Unesi novo korisničko ime za {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Jeste li sigurni da želite vratiti VM?",
"kVMPrompts_EnterNewUsernamePrompt": "Unesite novo korisničko ime ili ostavite prazno za korisničko ime posjetitelja:",
"kError_UnexpectedDisconnection": "Neočekivano ste se odspojili sa servera.",
"kError_UsernameTaken": "Korisničko ime je već zauzeto.",
"kError_UsernameInvalid": "Korisničko ime može sadržati samo brojeve, slova, razmak, crte, donje crte, i točke, i mora biti između 3 do 20 znamenaka.",
"kError_UsernameBlacklisted": "Ovo korisničko ime je zabranjeno.",
"kError_IncorrectPassword": "Netočna lozinka.",
"kAccountModal_Verify": "Potvrdi E-Mail",
"kAccountModal_AccountSettings": "Postavke računa",
"kAccountModal_ResetPassword": "Resetiraj lozinku",
"kAccountModal_NewPassword": "Nova lozinka",
"kAccountModal_ConfirmNewPassword": "Potvrdi novu lozinku",
"kAccountModal_CurrentPassword": "Trenutna lozinka",
"kAccountModal_ConfirmPassword": "Potvrdite lozinku",
"kMissingCaptcha": "Molim Vas ispunite captchu..",
"kPasswordsMustMatch": "Lozinke se moraju podudarati.",
"kAccountModal_VerifyText": "Poslali smo E-Mail na {0}. Da biste potvrdili svoj račun, unesite osmero-znamenkasti kod iz poruke ispod.",
"kAccountModal_VerifyPasswordResetText": "Poslali smo E-Mail na {0}. Da biste resetirali svoju lozinku, unesite osmero-znamenkasti kod iz poruke ispod.",
"kAccountModal_PasswordResetSuccess": "Vaša lozinka je uspješno resetirana. Sada se možete prijaviti sa svojom novom lozinkom.",
"kNotLoggedIn": "Niste prijavljeni."
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Magyar",
"translatedLanguageName": "Hungarian",
"flag": "🇭🇺",
"author": "MDMCK10",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Igen",
"kGeneric_No": "Nem",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Mégse",
"kGeneric_Send": "Küldés",
"kGeneric_Understood": "Rendben",
"kGeneric_Username": "Felhasználónév",
"kGeneric_Password": "Jelszó",
"kGeneric_Login": "Bejelentkezés",
"kGeneric_Register": "Regisztráció",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Születési dátum",
"kGeneric_VerificationCode": "Ellenőrző kód",
"kGeneric_Verify": "Ellenőrzés",
"kGeneric_Update": "Frissítés",
"kGeneric_Logout": "Kijelentkezés",
"kWelcomeModal_Header": "Üdvözlünk a CollabVM-en!",
"kWelcomeModal_Body": "<p>Mielőtt elkezded használni az oldalt, kérlek ismertesd meg magad a szabályokkal:</p> <h3>R1. Ne szegd meg a törvényt</h3> Ne használd a CollabVM-et, vagy a CollabVM hálózatát arra a célra, hogy megszegd az Egyesült Államok-i, New York-i, vagy más nemzetközi törvényeket. Ha észleljük hogy egy büncselekmény lett elkövetve a CollabVM-en keresztül, azonnal ki leszel tíltva az oldalról, és ha szükségesnek látjuk, a tevékenységeidet jelentjük a hatóságoknak.<br><br>Vedd figyelembe, hogy a törvény szerint szükséges jelentenünk a hatóságoknak, ha gyermekpornográfiát találunk a CollabVM hálózaton, vagy ha azt továbbítják rajta keresztül.<br><br>A CollabVM-en a COPPA törvény is érvényesül, kérlek ne használd ha még nem töltötted be a 13. életéved. <h3>R2. Ne futtass DoS/DDoS programokat.</h3>Ne használd a CollabVM-et arra, hogy DoS/DDoS támadásokat végezz emberek, cégek, vagy bárki más ellen. <h3>R3. Ne terjessz spam-ot.</h3> Ne küldj spam emaileket az oldal használatával, és spamot se terjessz általánosságban.<h3>R4. Ne élj vissza a biztonsági résekkel.</h3>Ne használj ki semmilyen biztonsági rést, ha látod hogy valaki ezt teszi, vagy be szeretnél jelenteni egyet, kérlek vedd fel a kapcsolatot velünk itt: computernewbab@gmail.com<h3>R5. Ne személyesítse meg más felhasználókat.</h3> Ne személyesítse meg a CollabVM más felhasználóit. Ha elkapunk, ideiglenesen kirúgunk az oldalról, és ha szükséges, kitiltunk téged.<h3>R6. Egy szavazás emberen ként.</h3> Ne használj semmilyen módszereket vagy eszközöket arra, hogy kikerüld a szavazás korlátozásokat. Emberenként mindenféleképpen csak egy szavazás engedélyezett. Ha valaki ezt a szabályt megszegi, ki lesz tiltva. <h3>R7. Ne használj távfelügyeleti programokat.</h3> Ne használj semmilyen távfelügyeleti programot (pl: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, stb.) <h3>R8. Ne kerüld meg a CollabNet-et.</h3> Ne próbáld megkerülni a CollabNet által biztosított blokkolást, föleg ha azt az 1., 2., vagy 7. szabály megszegésére használnád (vagy hülye, túlhasznált dolgok futtatásra). <h3>R9. Ne végezz romboló műveleteket folyamatosan.</h3> Egy felhasználónak nem szabad tönkre tennie a virtuális gépeket (folyamatosan használhatatlanná tenni), nem telepítheti/újratelepítheti az operációs rendszert (kivéve a VM7-en és VM8-on), és nem futtathat botokat amelyek ezeket megteszik. Ez olyan botokra is számít, amelyek hatalmas mennyiségű billentyűzet/egér bevíteleket spamelnek (\"kit\"-elés). <h3>R10. Tilos a kriptobányászat</h3> A kriptobányászat próbálása a virtuális gépeken elsősorban kirúgást, majd végleges kitiltást eredményez, ha tovább próbálod. Különben is, úgyse fogsz sok pénzt keresni belőle. <h3>NSFW Figyelmeztetés</h3> Kérlek vedd figyelembe, hogy az NSFW tartalmak megtekintése engedélyezve van az anarchia VM-en (VM0b0t), és rendszeresen megtekintik ott. Ezen kívül, bár igyekszünk távol tartni az NSFW tartalmakat a fő virtuális gépektől, az emberek időnként átcsempészik.",
"kSiteButtons_Home": "Főoldal",
"kSiteButtons_FAQ": "GYIK",
"kSiteButtons_Rules": "Szabályok",
"kSiteButtons_DarkMode": "Sötét mód",
"kSiteButtons_LightMode": "Világos mód",
"kSiteButtons_Languages": "Nyelv",
"kVM_UsersOnlineText": "Online felhasználók:",
"kVM_TurnTimeTimer": "{0} másodpercig van irányításod.",
"kVM_WaitingTurnTimer": "{0} másodperc múlva lesz irányításod.",
"kVM_VoteCooldownTimer": "Kérlek várj {0} másodpercet egy másik szavazás indítása előtt.",
"kVM_VoteForResetTitle": "Alaphelyzetbe szeretnéd állítani a virtuális gépet?",
"kVM_VoteForResetTimer": "A szavazásnak vége lesz {0} másodperc múlva.",
"kVMButtons_TakeTurn": "Sorban állás",
"kVMButtons_EndTurn": "Sor elhagyása",
"kVMButtons_ChangeUsername": "Felhasználónév módosítása",
"kVMButtons_Keyboard": "Billentyűzet",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Szavazás az alaphelyzetbe állításért",
"kVMButtons_Screenshot": "Képernyőkép",
"kQEMUMonitor": "QEMU Konzol",
"kAdminVMButtons_PassVote": "Szavazás kényzerítése",
"kAdminVMButtons_CancelVote": "Szavazás megszakítása",
"kAdminVMButtons_Restore": "Alaphelyzetbe állítás",
"kAdminVMButtons_Reboot": "Újraindítás",
"kAdminVMButtons_ClearTurnQueue": "Sor kiürítése",
"kAdminVMButtons_BypassTurn": "Sor elejére kerülés",
"kAdminVMButtons_IndefiniteTurn": "Végtelen ideig irányítás",
"kAdminVMButtons_Ban": "Kitiltás",
"kAdminVMButtons_Kick": "Kirúgás",
"kAdminVMButtons_TempMute": "Ideiglenes Némítás",
"kAdminVMButtons_IndefMute": "Némítás örökké",
"kAdminVMButtons_Unmute": "Némítás feloldása",
"kAdminVMButtons_GetIP": "IP-cím lekérése",
"kVMPrompts_AdminChangeUsernamePrompt": "Adj új felhasználónevet {0}-nak:",
"kVMPrompts_AdminRestoreVMPrompt": "Biztos, hogy alaphelyzetbe szeretnéd állítani a virtuális gépet?",
"kVMPrompts_EnterNewUsernamePrompt": "Adj meg egy új felhasználónevet, vagy hagyd üresen ha azt akarod, hogy a rendszer generáljon egyet:",
"kError_UnexpectedDisconnection": "Kapcsolat megszakadt a szerverrel.",
"kError_UsernameTaken": "Ez a felhasználónév már foglalt.",
"kError_UsernameInvalid": "A felhasználónévnek 3 és 20 karakter között kell lennie, és csak számokat, betűket, kötőjeleket, aláhúzásokat és pontokat tartalmazhat.",
"kError_UsernameBlacklisted": "A megadott felhasználónév tiltólistán van.",
"kError_IncorrectPassword": "Helytelen jelszó.",
"kAccountModal_Verify": "E-Mail visszaigazolása",
"kAccountModal_AccountSettings": "Fiókbeállítások",
"kAccountModal_ResetPassword": "Jelszó visszaállítása",
"kAccountModal_NewPassword": "Új Jelszó",
"kAccountModal_ConfirmNewPassword": "Új Jelszó megerősítése",
"kAccountModal_CurrentPassword": "Jelenlegi Jelszó",
"kAccountModal_ConfirmPassword": "Jelszó megerősítése",
"kMissingCaptcha": "Kérlek oldd meg a CAPTCHA-t.",
"kPasswordsMustMatch": "A jelszavaknak egyezniük kell.",
"kAccountModal_VerifyText": "Küldtünk egy E-Mailt a(z) {0}-ra. Kérlek írd be az E-Mailben található 8 jegyű kódot az alábbi mezőbe.",
"kAccountModal_VerifyPasswordResetText": "Küldtünk egy E-Mailt a(z) {0}-ra. Kérlek írd be az E-Mailben található 8 jegyű kódot az alábbi mezőbe a jelszavad visszaállításához.",
"kAccountModal_PasswordResetSuccess": "A jelszavad sikeresen meg lett változtatva. Mostantól bejelentkezhetsz az új jelszavaddal.",
"kNotLoggedIn": "Nem vagy bejelentkezve."
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Bahasa Indonesia",
"translatedLanguageName": "Indonesian",
"flag": "🇮🇩",
"author": "indonesia1331",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Ya",
"kGeneric_No": "Tidak",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Membatalkan",
"kGeneric_Send": "Mengirim",
"kGeneric_Understood": "Dipahami",
"kGeneric_Username": "Nama belakang",
"kGeneric_Password": "Kata sandi",
"kGeneric_Login": "Gabung",
"kGeneric_Register": "Daftar",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Tanggal lahir",
"kGeneric_VerificationCode": "Kode verifikasi",
"kGeneric_Verify": "Memeriksa",
"kGeneric_Update": "Memperbarui",
"kGeneric_Logout": "Keluar",
"kWelcomeModal_Header": "Selamat datang di CollabVM",
"kWelcomeModal_Body": "<p>Sebelum melanjutkan, harap pahami peraturan kami:</p> <h3>R1. Jangan melanggar hukum.</h3> Jangan gunakan CollabVM atau jaringan CollabVM untuk melanggar hukum federal Amerika Serikat, hukum negara bagian New York, atau hukum internasional. Jika CollabVM mengetahui adanya kejahatan yang dilakukan melalui layanannya, Anda akan langsung diblokir, dan aktivitas Anda dapat dilaporkan kepada pihak berwenang jika perlu.<br><br>CollabVM diwajibkan oleh hukum untuk memberi tahu lembaga penegak hukum jika hal itu terjadi menyadari adanya pornografi anak, atau ditransmisikan melalui jaringannya.<br><br>COPPA juga diberlakukan, mohon jangan gunakan CollabVM jika Anda berusia di bawah 13 tahun. <h3>R2. Tidak ada alat DoS/DDoS yang berjalan.</h3> Jangan gunakan CollabVM untuk DoS/DDoS individu, bisnis, perusahaan, atau siapa pun. <h3>R3. Tidak ada distribusi spam.</h3> Jangan mengirim spam ke email apa pun menggunakan layanan ini atau mendorong spam secara umum. <h3>R4. Jangan menyalahgunakan eksploitasi apa pun.</h3> Jangan menyalahgunakan eksploitasi apa pun, selain itu jika Anda melihat seseorang menyalahgunakan eksploitasi atau Anda perlu melaporkannya, silakan hubungi saya di: computernewbab@gmail.com <h3>R5. Jangan meniru identitas pengguna lain.</h3> Jangan meniru identitas anggota CollabVM lainnya. Jika ketahuan, koneksi Anda akan terputus untuk sementara, dan diblokir jika perlu. <h3>R6. Satu suara per orang.</h3> Jangan gunakan metode atau alat apa pun untuk melewati batasan suara. Hanya satu suara per orang yang diperbolehkan, apapun yang terjadi. Siapa pun yang ketahuan melakukan ini akan dilarang. <h3>R7. Tidak Ada Alat Administrasi Jarak Jauh.</h3> Jangan gunakan alat administrasi jarak jauh apa pun (misal: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, dll.) <h3>R8. Tidak boleh melewati CollabNet.</h3> Jangan mencoba menerobos pemblokiran yang disediakan oleh CollabNet, terutama jika pemblokiran tersebut digunakan untuk melanggar Aturan 1, Aturan 2, atau Aturan 7 (atau menjalankan hal-hal bodoh yang sering digunakan). <h3>R9. Dilarang melakukan tindakan merusak secara terus-menerus.</h3> Pengguna mana pun tidak boleh menghancurkan VM (sehingga tidak dapat digunakan terus-menerus), menginstal/menginstal ulang sistem operasi (kecuali pada VM7 atau VM8), atau menjalankan bot yang melakukan tindakan tersebut. Ini termasuk bot yang mengirim spam ke input keyboard/mouse dalam jumlah besar (\"kitting\"). <h3>R10. Tidak Ada Penambangan Kripto</h3> Mencoba menambang mata uang kripto di VM akan menghasilkan tendangan, dan kemudian larangan permanen jika Anda terus mencobanya. Selain itu, Anda tidak akan mendapat uang darinya. <h3>Peringatan NSFW</h3> Harap perhatikan bahwa konten NSFW diizinkan di VM anarki kami (VM0b0t), dan dilihat secara rutin. Selain itu, meskipun kami berupaya keras untuk menjauhkan NSFW dari VM utama, terkadang ada orang yang lolos.",
"kSiteButtons_Home": "Beranda",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Aturan",
"kSiteButtons_DarkMode": "Mode Gelap",
"kSiteButtons_LightMode": "Mode Terang",
"kSiteButtons_Languages": "Bahasa",
"kVM_UsersOnlineText": "Pengguna Daring:",
"kVM_TurnTimeTimer": "Gilirannya berakhir dalam {0} detik.",
"kVM_WaitingTurnTimer": "Menunggu giliran dalam {0} detik.",
"kVM_VoteCooldownTimer": "Harap tunggu {0} detik sebelum memulai pemungutan suara lainnya.",
"kVM_VoteForResetTitle": "Apakah Anda ingin menyetel ulang VM?",
"kVM_VoteForResetTimer": "Pemungutan suara berakhir dalam {0} detik",
"kVMButtons_TakeTurn": "Mengambil giliran",
"kVMButtons_EndTurn": "Akhiri Putaran",
"kVMButtons_ChangeUsername": "Ubah Nama Pengguna",
"kVMButtons_Keyboard": "Keyboard",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Pilih Reset",
"kVMButtons_Screenshot": "Screenshot",
"kQEMUMonitor": "Pemantau QEMU",
"kAdminVMButtons_PassVote": "Lulus pemungutan suara",
"kAdminVMButtons_CancelVote": "Batalkan Suara",
"kAdminVMButtons_Restore": "Memulihkan",
"kAdminVMButtons_Reboot": "Menyalakan ulang",
"kAdminVMButtons_ClearTurnQueue": "Hapus Antrian Putar",
"kAdminVMButtons_BypassTurn": "Lewati Putaran",
"kAdminVMButtons_IndefiniteTurn": "Putaran Tidak Terbatas",
"kAdminVMButtons_Ban": "Ban",
"kAdminVMButtons_Kick": "Kick",
"kAdminVMButtons_TempMute": "Bisu Sementara",
"kAdminVMButtons_IndefMute": "Bisu Tanpa Batas",
"kAdminVMButtons_Unmute": "kabut mendung",
"kAdminVMButtons_GetIP": "Dapatkan IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Masukkan nama pengguna baru untuk {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Apakah Anda yakin ingin memulihkan VM?",
"kVMPrompts_EnterNewUsernamePrompt": "Masukkan nama pengguna baru, atau biarkan kolom kosong untuk diberi nama pengguna tamu",
"kError_UnexpectedDisconnection": "Anda telah terputus dari server.",
"kError_UsernameTaken": "nama pengguna itu telah terpakai",
"kError_UsernameInvalid": "Nama pengguna hanya boleh berisi angka, huruf, spasi, tanda hubung, garis bawah, dan titik, dan harus terdiri dari 3 hingga 20 karakter.",
"kError_UsernameBlacklisted": "Nama pengguna itu telah masuk daftar hitam.",
"kError_IncorrectPassword": "Kata kunci Salah.",
"kAccountModal_Verify": "Verifikasi email",
"kAccountModal_AccountSettings": "Pengaturan akun",
"kAccountModal_ResetPassword": "Atur Ulang Kata Sandi",
"kAccountModal_NewPassword": "Kata Sandi Baru",
"kAccountModal_ConfirmNewPassword": "Konfirmasi Password Baru",
"kAccountModal_CurrentPassword": "Kata Sandi Saat Ini",
"kAccountModal_ConfirmPassword": "Konfirmasi Sandi",
"kMissingCaptcha": "Silakan isi captchanya.",
"kPasswordsMustMatch": "Kata sandi harus cocok.",
"kAccountModal_VerifyText": "Kami mengirimkan E-Mail ke {0}. Untuk memverifikasi akun Anda, silakan masukkan kode 8 digit dari E-Mail di bawah ini.",
"kAccountModal_VerifyPasswordResetText": "Kami mengirimkan E-Mail ke {0}. Untuk mereset kata sandi Anda, silakan masukkan kode 8 digit dari E-Mail di bawah ini.",
"kAccountModal_PasswordResetSuccess": "Kata sandi Anda telah berhasil diubah. Anda sekarang dapat masuk dengan kata sandi baru Anda.",
"kNotLoggedIn": "Belum masuk"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "日本語",
"translatedLanguageName": "Japanese",
"flag": "🇯🇵",
"author": "pap",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "はい",
"kGeneric_No": "いいえ",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "キャンセル",
"kGeneric_Send": "送信",
"kGeneric_Understood": "理解しました",
"kGeneric_Username": "ユーザー名",
"kGeneric_Password": "パスワード",
"kGeneric_Login": "ログイン",
"kGeneric_Register": "登録",
"kGeneric_EMail": "Eメール/メールアドレス",
"kGeneric_DateOfBirth": "生年月日",
"kGeneric_VerificationCode": "認証コード",
"kGeneric_Verify": "認証",
"kGeneric_Update": "更新",
"kGeneric_Logout": "ログアウト",
"kWelcomeModal_Header": "CollabVMへようこそ",
"kWelcomeModal_Body": "<p>続行する前に、これらのルールをよく理解してください:</p> <h3>R1. 法律を守ること</h3> 米国連邦法、ニューヨーク州法、または国際法に違反するためにCollabVMまたはCollabVMのネットワークを使用しないでください。 CollabVMがそのサービスを通じて犯罪が行われたことを認識した場合、利用者は直ちに利用を禁止され、必要に応じて利用者の活動が当局に報告されることがあります。<br><br>CollabVMは、そのネットワーク上に児童ポルが存在すること、またはそのネットワークを通じて児童ポルが送信されていることに気付いた場合、法執行機関に通知することが法律で義務付けられています。<br><br>COPPAも施行されていますので、13歳未満の方はCollabVMを使用しないでください。<h3>R2. DoS/DDoSツールを実行しないこと。</h3> CollabVMを使用して、個人、企業、会社、または他の誰かをDoS/DDoSしないでください。<h3>R3. 他サービスへのスパム(連投)は禁止。</h3> このサービスを利用したスパムメールや、一般的なプッシュスパムはご遠慮ください。<h3>R4. エクスプロイトの乱用は禁止。</h3> 誰かが悪用しているのを見たり、悪用を報告する必要がある場合は computernewbab@gmail.com までご連絡ください。英語でのみサポートを受け付けています。<h3>R5. 他のユーザーになりすますのは禁止。</h3> CollabVMの他のメンバーになりすまさないでください。もし見つかった場合、一時的に接続を切られ、必要であれば禁止されます。<h3>R6. 一人1投票まで。</h3> 投票制限を迂回する方法やツールを使用しないでください。何があっても、1人1票しか投票できません。このような行為が発覚した場合は出入り禁止となります。<h3>R7. リモートアクセスツールの実行/インストールは禁止。</h3>VM内でTeamViewer、AnyDesk、Splashtopなどのリモートアクセスツールを実行/インストールしないでください。<h3>R8. CollabNetの回避は禁止。</h3> CollabNetが提供するブロッキングを迂回しようとしないでください。特に、ルール1、2、7を破るためにアクセスする場合はもってのほか。<h3>R9. 常にマシンを破壊/使用不能にするような行動をとらないこと。</h3> どのユーザーもマシンを常に使用不能にする、またはOSのインストールVM6/7は例外、ボットの実行は禁止されています。 これには、大量のキーボード/マウス入力をスパムするボットも含まれます。<h3>R10. 仮想通貨のマイニングは禁止。</h3> マシン上で暗号通貨を採掘しようとするとキックされ、それでも続けると禁止となります。というか、1円すら掘れないと思います。やめてね。<h3>NSFWR18についての警告</h3> NSFWコンテンツはアナーキーマシン (VM0b0t) で許可されており、 それらはなんの警告もなしに普通に見ることができます。加えて、私たちはNSFWをメインVMに表示しないよう努力していますが、時折、それをすり抜ける人もいます。",
"kSiteButtons_Home": "ホーム",
"kSiteButtons_FAQ": "FAQよくある質問",
"kSiteButtons_Rules": "ルール",
"kSiteButtons_DarkMode": "ダークモード",
"kSiteButtons_LightMode": "ライトモード",
"kSiteButtons_Languages": "言語",
"kVM_UsersOnlineText": "ユーザー数:",
"kVM_TurnTimeTimer": "{0} 秒後にあなたのターンが終了します。",
"kVM_WaitingTurnTimer": "{0} 秒後にあなたのターンが来ます。",
"kVM_VoteCooldownTimer": "次の投票を開始する前に、{0} 秒間お待ちください。",
"kVM_VoteForResetTitle": "マシンの再起動を行いますか?",
"kVM_VoteForResetTimer": "投票は {0} 秒後に終了します。",
"kVMButtons_TakeTurn": "ターンを得る",
"kVMButtons_EndTurn": "ターンを終了する",
"kVMButtons_ChangeUsername": "ユーザー名の変更",
"kVMButtons_Keyboard": "仮想キーボード",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "再起動の投票",
"kVMButtons_Screenshot": "スクリーンショット",
"kQEMUMonitor": "QEMU Monitor",
"kAdminVMButtons_PassVote": "投票をスキップ",
"kAdminVMButtons_CancelVote": "投票をキャンセル",
"kAdminVMButtons_Restore": "復元",
"kAdminVMButtons_Reboot": "再起動",
"kAdminVMButtons_ClearTurnQueue": "ターンのキューをクリア",
"kAdminVMButtons_BypassTurn": "ターンをスキップ",
"kAdminVMButtons_IndefiniteTurn": "無期限ターン",
"kAdminVMButtons_Ban": "禁止(Ban)",
"kAdminVMButtons_Kick": "追放",
"kAdminVMButtons_TempMute": "一時的なミュート",
"kAdminVMButtons_IndefMute": "無期限ミュート",
"kAdminVMButtons_Unmute": "ミュート解除",
"kAdminVMButtons_GetIP": "IPの入手",
"kVMPrompts_AdminChangeUsernamePrompt": "{0} の新しいユーザー名を入力してください:",
"kVMPrompts_AdminRestoreVMPrompt": "マシンの復元をしますか?",
"kVMPrompts_EnterNewUsernamePrompt": "ユーザー名を入力するか、フィールドを空白にしてOKをクリックしてください。(空白にした場合、ゲストユーザーになります。)",
"kError_UnexpectedDisconnection": "サーバーから切断されました。",
"kError_UsernameTaken": "ユーザー名はすでに存在しています。",
"kError_UsernameInvalid": "ユーザーネームは3文字から20文字以内にしなければいけず、かつユーザーネームに入れられる文字は数字、アルファベット、スペース(半角)、ダッシュ記号、アンダースコア、ドットのみです。",
"kError_UsernameBlacklisted": "そのユーザー名はブラックリストに入っています。",
"kError_IncorrectPassword": "パスワードが間違っています。",
"kAccountModal_Verify": "Eメールの確認",
"kAccountModal_AccountSettings": "アカウントの設定",
"kAccountModal_ResetPassword": "パスワードの再設定",
"kAccountModal_NewPassword": "新しいパスワード",
"kAccountModal_ConfirmNewPassword": "新しいパスワードの再確認",
"kAccountModal_CurrentPassword": "現在のパスワード",
"kAccountModal_ConfirmPassword": "パスワードの再確認",
"kMissingCaptcha": "Captcha認証を完了してください。",
"kPasswordsMustMatch": "「新しいパスワード」と「新しいパスワードの再確認」が一致していません。",
"kAccountModal_VerifyText": "{0} へ確認メールを送信しました。アカウントを認証するために、メールに書いてある8桁のコードを下のボックスへ入力してください。",
"kAccountModal_VerifyPasswordResetText": "{0}へ確認メールを送信しました。パスワードをリセットするために、メールに書いてある8桁のコードを下のボックスへ入力してください。",
"kAccountModal_PasswordResetSuccess": "パスワードの変更が完了しました。ログイン時には入力した新しいパスワードを使用してください。",
"kNotLoggedIn": "ログインしていません"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "한국어(KR)",
"translatedLanguageName": "Korean (KR)",
"flag": "🇰🇷",
"author": "Johnmacro",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "예",
"kGeneric_No": "아니요",
"kGeneric_Ok": "확인",
"kGeneric_Cancel": "취소",
"kGeneric_Send": "보내기",
"kGeneric_Understood": "이해함",
"kGeneric_Username": "사용자명",
"kGeneric_Password": "암호",
"kGeneric_Login": "로그인",
"kGeneric_Register": "등록",
"kGeneric_EMail": "전자 메일",
"kGeneric_DateOfBirth": "생년월일",
"kGeneric_VerificationCode": "인증 코드",
"kGeneric_Verify": "인증",
"kGeneric_Update": "갱신",
"kGeneric_Logout": "로그아웃",
"kWelcomeModal_Header": "CollabVM에 오신 것을 환영합니다",
"kWelcomeModal_Body": "<p>계속하기 전에 규칙을 숙지하시기 바랍니다.</p> <h3>규칙 1. 법을 어기지 말 것.</h3> CollabVM이나 CollabVM의 네트워크를 미국 연방법이나 뉴욕주법, 국제법을 위반하는 데 사용하지 마십시오. CollabVM이 그 서비스를 통해 범죄가 저질러졌음을 알게 되면 귀하는 즉시 차단되며 필요한 경우 귀하의 활동이 당국에 신고될 수 있습니다.<br><br>CollabVM은 법에 따라 그 네트워크상 또는 네트워크를 통해 전송되는 아동 포르노의 존재를 알게 되면 법 집행 기관에 알려야 합니다.<br><br>COPPA도 시행되므로 13세 미만이라면 CollabVM을 이용하지 마십시오. <h3>규칙 2. DoS/DDoS 도구 실행 금지.</h3> CollabVM을 개인이나 기업, 회사, 다른 사람을 DoS/DDoS하는 데 사용하지 마십시오. <h3>규칙 3. 스팸 배포 금지.</h3> 이 서비스를 사용하여 전자 메일 스팸을 보내거나, 전반적으로 스팸을 푸시하지 마십시오.<h3>규칙 4. 익스플로잇을 악용하지 말 것.</h3> 어떠한 익스플로잇도 악용하지 마십시오. 추가로 익스플로잇을 악용하는 사람을 목격했거나 신고해야 한다면 computernewbab@gmail.com으로 연락하시기 바랍니다 <h3>규칙 5. 다른 이용자를 사칭하지 말 것.</h3> CollabVM의 다른 회원을 사칭하지 마십시오. 적발되면 일시적으로 연결이 끊어지며 필요한 경우 차단됩니다. <h3>규칙 6. 한 사람당 한 표.</h3> 투표 제한을 우회하기 위한 어떠한 방법이나 도구도 사용하지 마십시오. 어떠한 경우에도 한 사람당 한 표만 허용됩니다. 이 행위가 적발된 사람은 차단됩니다. <h3>규칙 7. 원격 관리 도구 금지.</h3> 어떠한 원격 관리 도구(예: DarkComet, NanoCore, AnyDesk, TeamViewer, Orcus 등)도 사용하지 마십시오 <h3>규칙 8. CollabNet 우회 금지.</h3> CollabNet에서 제공하는 차단 우회를 시도하지 마십시오(특히 규칙 1이나 규칙 2, 규칙 7을 어기기 위해 (또는 과도하게 사용되는 바보 같은 것들을 실행하기 위해) 사용되는 경우). <h3>규칙 9. 지속적인 파괴적인 행위 수행 금지.</h3> 어떠한 이용자도 VM을 파괴하거나(지속적으로 사용할 수 없게 하는 것), 운영 체제를 설치/다시 설치하거나(VM7이나 VM8 제외), 그러한 행위를 하는 봇을 실행해서는 안 됩니다. 여기에는 대량의 키보드/마우스 입력 스팸을 보내는(\"키팅(kitting)\") 봇이 포함됩니다. <h3>규칙 10. 암호화폐 채굴 금지</h3> VM에서 암호화폐를 채굴하려고 하면 추방되며 계속 시도하면 영구적으로 차단됩니다. 게다가 그걸로 돈을 조금이라도 벌 수 있는 것도 아니잖아요. <h3>후방주의 경고</h3> 후방주의 콘텐츠가 무정부 VM(VM0b0t)에서 허용되며 자주 시청됨에 유의하시기 바랍니다. 추가로 저희는 주 VM에서 후방주의를 막으려고 많은 노력을 기울이지만 사람들이 가끔 몰래 통과시킵니다.",
"kSiteButtons_Home": "홈",
"kSiteButtons_FAQ": "자주 하는 질문",
"kSiteButtons_Rules": "규칙",
"kSiteButtons_DarkMode": "다크 모드",
"kSiteButtons_LightMode": "라이트 모드",
"kSiteButtons_Languages": "언어",
"kVM_UsersOnlineText": "접속 중인 이용자:",
"kVM_TurnTimeTimer": "차례가 {0}초 후 만료됩니다.",
"kVM_WaitingTurnTimer": "{0}초 후 차례를 기다리고 있습니다.",
"kVM_VoteCooldownTimer": "다른 투표를 시작하기 전에 {0}초 동안 기다려 주세요.",
"kVM_VoteForResetTitle": "VM을 초기화하시겠습니까?",
"kVM_VoteForResetTimer": "투표가 {0}초 후 종료됩니다",
"kVMButtons_TakeTurn": "차례 가져가기",
"kVMButtons_EndTurn": "차례 끝내기",
"kVMButtons_ChangeUsername": "사용자명 변경",
"kVMButtons_Keyboard": "키보드",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "초기화 투표",
"kVMButtons_Screenshot": "스크린샷",
"kQEMUMonitor": "QEMU 모니터",
"kAdminVMButtons_PassVote": "투표 건너뛰기",
"kAdminVMButtons_CancelVote": "투표 취소",
"kAdminVMButtons_Restore": "복원",
"kAdminVMButtons_Reboot": "재부팅",
"kAdminVMButtons_ClearTurnQueue": "차례 대기열 비우기",
"kAdminVMButtons_BypassTurn": "차례 우회",
"kAdminVMButtons_IndefiniteTurn": "무기한 차례",
"kAdminVMButtons_Ban": "차단",
"kAdminVMButtons_Kick": "추방",
"kAdminVMButtons_TempMute": "일시 뮤트",
"kAdminVMButtons_IndefMute": "무기한 뮤트",
"kAdminVMButtons_Unmute": "뮤트 해제",
"kAdminVMButtons_GetIP": "IP 가져오기",
"kVMPrompts_AdminChangeUsernamePrompt": "{0}의 새 사용자명을 입력하세요.",
"kVMPrompts_AdminRestoreVMPrompt": "VM을 복원하시겠습니까?",
"kVMPrompts_EnterNewUsernamePrompt": "새 사용자명을 입력하거나, 게스트 사용자명을 할당받으려면 필드를 비워 두세요",
"kError_UnexpectedDisconnection": "서버와의 연결이 끊어졌습니다.",
"kError_UsernameTaken": "해당 사용자명은 이미 사용 중입니다",
"kError_UsernameInvalid": "사용자명은 숫자, 영문자, 공백, 대시, 밑줄, 점만 포함할 수 있으며 3자에서 20자 사이여야 합니다.",
"kError_UsernameBlacklisted": "해당 사용자명은 블랙리스트에 등록되어 있습니다.",
"kError_IncorrectPassword": "잘못된 암호입니다.",
"kAccountModal_Verify": "전자 메일 인증",
"kAccountModal_AccountSettings": "계정 설정",
"kAccountModal_ResetPassword": "암호 재설정",
"kAccountModal_NewPassword": "새 암호",
"kAccountModal_ConfirmNewPassword": "새 암호 확인",
"kAccountModal_CurrentPassword": "현재 암호",
"kAccountModal_ConfirmPassword": "암호 확인",
"kMissingCaptcha": "보안 문자를 입력하세요.",
"kPasswordsMustMatch": "암호가 일치해야 합니다.",
"kAccountModal_VerifyText": "{0}(으)로 전자 메일을 보냈습니다. 계정을 인증하려면 아래에 전자 메일의 8자리 코드를 입력하세요.",
"kAccountModal_VerifyPasswordResetText": "{0}(으)로 전자 메일을 보냈습니다. 암호를 재설정하려면 아래에 전자 메일의 8자리 코드를 입력하세요.",
"kAccountModal_PasswordResetSuccess": "암호가 변경되었습니다. 이제 새 암호로 로그인할 수 있습니다.",
"kNotLoggedIn": "로그인되지 않음"
}
}

View File

@ -1,85 +0,0 @@
{
"languages": {
"en-us": {
"languageName": "English (US)",
"flag": "🇺🇸"
},
"cv-m": {
"languageName": "nonsense",
"flag": "NS"
},
"fr-fr": {
"languageName": "Français",
"flag": "🇫🇷"
},
"de-de": {
"languageName": "Deutsch",
"flag": "🇩🇪"
},
"ja-jp": {
"languageName": "日本語",
"flag": "🇯🇵"
},
"pl-pl": {
"languageName": "Polski",
"flag": "🇵🇱"
},
"es-es": {
"languageName": "Español (España)",
"flag": "🇪🇸"
},
"ru-ru": {
"languageName": "Русский",
"flag": "🇷🇺"
},
"hu-hu": {
"languageName": "Magyar",
"flag": "🇭🇺"
},
"uk-ua": {
"languageName": "Українська",
"flag": "🇺🇦"
},
"hr-hr": {
"languageName": "Hrvatski",
"flag": "🇭🇷"
},
"ro-ro": {
"languageName": "Română",
"flag": "🇷🇴"
},
"tok": {
"languageName": "Toki Pona",
"flag": "🌐"
},
"id": {
"languageName": "Bahasa Indonesia",
"flag": "🇮🇩"
},
"ko-kr": {
"languageName": "한국어",
"flag": "🇰🇷"
},
"pt-br": {
"languageName": "Português (Brasil)",
"flag": "🇧🇷"
},
"th": {
"languageName": "ภาษาไทย",
"flag": "🇹🇭"
},
"zh-cn": {
"languageName": "中文 (简体)",
"flag": "🇨🇳"
},
"vi-vn": {
"languageName": "Tiếng Việt",
"flag": "🇻🇳"
},
"tr-tr": {
"languageName": "Türkçe",
"flag": "🇹🇷"
}
},
"defaultLanguage": "en-us"
}

View File

@ -1,32 +0,0 @@
{
"languageName": "Pirate",
"translatedLanguageName": "Arrrghh, matey!",
"flag": "🏴‍☠️",
"author": "Computernewb",
"stringKeys": {
"kSiteName": "Argh, matey, it's CollabVM!",
"kHomeButton": "Arrrghh, set sail",
"kFAQButton": "Arrrgh, get your Map",
"kRulesButton": "Arrrrghh, get the Etiquette Book",
"kVMResetTitle": "Argh, matey, this VM is broken. Would you like to particpate in the VM War?",
"kGeneric_Yes": "Arrrghh, yay!",
"kGeneric_No": "Arrrrgh, nay!",
"kVMVoteTime": "Argghh, the cannon fight ends in {0} seconds",
"kPassVoteButton": "Rig the vote",
"kCancelVoteButton": "Ignore the vote",
"kTakeTurnButton": "Take the Wheel",
"kEndTurnButton": "Give Up the Wheel",
"kChangeUsernameButton": "Argh, select a new pirate name",
"kVoteButton": "Argh, my matey, it's broken...",
"kScreenshotButton": "Take a Polaroid",
"kUsersOnlineHeading": "Pirate Friends Online:",
"kTurnTime": "Arrrrgh, your wheel rights expire in {0} seconds.",
"kWaitingTurnTime": "Waiting for the wheel in {0} seconds.",
"kVoteCooldown": "Arrgh matey, you need to wait {0} seconds to vote again.",
"kEnterNewUsername": "Arggghh, matey, what would you like to be known as?",
"kError_UsernameTaken": "Sorry, matey, but another pirate is using that name.",
"kError_UsernameInvalid": "Sorry matey, but that name is invalid. Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and it must be between 3 and 20 characters.",
"kError_UsernameBlacklisted": "Sorry matey, but you cannot use that name."
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Polski",
"translatedLanguageName": "Polish",
"flag": "🇵🇱",
"author": "Mattx, 6eamed",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Tak",
"kGeneric_No": "Nie",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Anuluj",
"kGeneric_Send": "Wyślij",
"kGeneric_Understood": "Zrozumiałem(am)",
"kGeneric_Username": "Nazwa Użytkownika",
"kGeneric_Password": "Hasło",
"kGeneric_Login": "Zaloguj Sie",
"kGeneric_Register": "Załóż Konto",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Data Urodzenia",
"kGeneric_VerificationCode": "Kod Weryfikacyjny",
"kGeneric_Verify": "Zweryfikuj",
"kGeneric_Update": "Zaktualizuj",
"kGeneric_Logout": "Wyloguj",
"kWelcomeModal_Header": "Witamy na CollabVM.",
"kWelcomeModal_Body": "<p>Zanim rozpoczniesz, prosimy o przeczytanie regulaminu:</p> <h3>R1. Nie łam prawa.</h3> Nie korzystaj z CollabVM czy sieci CollabVM aby łamać prawo federalne w Stanach Zjednoczonych, prawo stanu w Nowym Jorku czy międzynarodowych praw. W razie, że CollabVM dowie się o przestępstwie popełnionym za pośrednictwem jego usług, twój dostęp zostanie zablokowany i twoja działalność może być w razie potrzeby zgłoszona władzom.<br><br>CollabVM jest zobowiązany prawnie do zawiadomienia organów ścigania jeśli dowie się o obecności pornografii dziecięcej na, lub transmitowanej przez jego sieć.<br><br>Akt COPPA jest także egzekwowany, prosimy o nie korzystanie z CollabVM jeśli nie ukończyłeś 13 lat. <h3>R2. Zakaz narzędzi DoS/DDoS.</h3> Nie używaj CollabVM aby wykonywać ataki DoS/DDoS na osoby prywatne, działalności, firmy czy na kogokolwiek innego. <h3>R3. Zakaz rozpowszechniania spamu.</h3>Nie spamuj żadnych emaili korzystając z tej usługi, ani nie wysyłaj żadnego innego rodzaju spamu. <h3>R4. Nie nadużywaj exploitów.</h3> Nie używaj żadnych exploitów, w przypadku gdy zobaczysz że ktoś je wykorzystuje lub potrzebujesz zgłosić exploit, proszę o kontakt na emailu: computernewbab@gmail.com <h3>R5. Nie podszywaj się pod innych.</h3> Nie podszywaj się pod innych użytkowników CollabVM. Jeśli zostaniesz złapany, zostaniesz tymczasowo odłączony i zbanowany w razie potrzeby. <h3>R6. Jeden głos na osobę.</h3> Nie wykorzystuj żadnych metod lub narzędzi aby obejść ograniczenie na głosach. Tylko jeden głos na osobę jest zezwolony, niezależnie od sytuacji. Każdy, kto jest przyłapany na obchodzenie głosów zostanie natychmiast zbanowany. <h3>R7. Zakaz narzędzi zdalnej kontroli/administracji i narzędzi typu RAT</h3> Nie korzystaj z narzędzi zdalnej administracji (przykładowo: DarkComet, NanoCore, AnyDesk, TeamViewer, Orcus, itp.) <h3>R8. Zakaz obchodzenia systemu CollabNet.</h3> Nie próbuj obejść blokad zapewnionych przez CollabNet, zwłaszcza jeśli jest to aby łamać zasady R1, R2 lub R7 (albo żeby uruchamiać głupie, nadużyte rzeczy). <h3>R9. Nie wykonuj stale działań niszczących.</h3> Żaden użytkownik nie może stale niszczyć wirtualnej maszyny (co powoduje, że wirtualna maszyna jest stale niemożliwa do wykorzystania), instalować/ponownie instalować systemu operacyjnego (oprócz na wirtualnych maszynach VM7 i VM8), ani uruchamiać boty, które wykonują takie czynności. Ta zasada też obejmuje boty, które wysyłają dużą ilość losowych wejść klawiatury/myszki (często nazywane \"kitting\"). <h3>R10. Zakaz kopania kryptowalut.</h3> Wszystkie próby kopania kryptowalut na wirtualnych maszynach będzie skutkować wyrzuceniem, a potem stałym banem jeśli będziesz próbować dalej. I tak nawet nie zrobiłbyś żadnych pieniędzy z robienia tego. <h3>Uwaga o nieodpowiednich treściach</h3> Proszę o zwrócenie uwagi na to, że nieodpowiednie treści są zezwolone na naszej anarchicznej maszynie wirtualnej (VM0b0t) i są wyświetlane regularnie. W dodatku, wkładamy wysiłek w zapobieganiu wyświetlania nieodpowiednich treści na naszych głównych wirtualnych maszynach, czasami użytkownicy mogą prześlizgnąć je.",
"kSiteButtons_Home": "Strona domowa",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Regulamin",
"kSiteButtons_DarkMode": "Tryb Ciemny",
"kSiteButtons_LightMode": "Tryb Jasny",
"kSiteButtons_Languages": "Języki",
"kVM_UsersOnlineText": "Użytkownicy online:",
"kVM_TurnTimeTimer": "Kolejka wygasa za {0} sekund.",
"kVM_WaitingTurnTimer": "Czekanie na kolejkę za {0} sekund.",
"kVM_VoteCooldownTimer": "Prosimy zaczekać {0} sekund przed rozpoczęciem następnego głosowania.",
"kVM_VoteForResetTitle": "Czy chcesz zresetować wirtualną maszynę?",
"kVM_VoteForResetTimer": "Głosowanie kończy się za {0} sekund.",
"kVMButtons_TakeTurn": "Weź kolej",
"kVMButtons_EndTurn": "Zakończ kolej",
"kVMButtons_ChangeUsername": "Zmień nazwę użytkownika",
"kVMButtons_Keyboard": "Klawiatura",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Zagłosuj o reset",
"kVMButtons_Screenshot": "Zrzut ekranu",
"kQEMUMonitor": "Monitor QEMU",
"kAdminVMButtons_PassVote": "Przekaż Głosowanie",
"kAdminVMButtons_CancelVote": "Anuluj Głosowanie",
"kAdminVMButtons_Restore": "Przywróć",
"kAdminVMButtons_Reboot": "Uruchom Ponownie",
"kAdminVMButtons_ClearTurnQueue": "Usuń Kolejkę",
"kAdminVMButtons_BypassTurn": "Omiń Kolejkę",
"kAdminVMButtons_IndefiniteTurn": "Nieograniczona Kolej",
"kAdminVMButtons_Ban": "Zbanuj",
"kAdminVMButtons_Kick": "Wyrzuć",
"kAdminVMButtons_TempMute": "Tymczasowe Wyciszenie",
"kAdminVMButtons_IndefMute": "Nieokreślone Wyciszenie",
"kAdminVMButtons_Unmute": "Wyłącz Wyciszenie",
"kAdminVMButtons_GetIP": "Pokaż IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Wprowadź nową nazwę użytkownika dla {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Czy na pewno chcesz przywrócić tą wirtualną maszyne?",
"kVMPrompts_EnterNewUsernamePrompt": "Wpisz nową nazwę użytkownika",
"kError_UnexpectedDisconnection": "Zostałeś(aś) odłączony od serwera.",
"kError_UsernameTaken": "Nazwa użytkownika jest już w użyciu",
"kError_UsernameInvalid": "Nazwy użytkownika mogą zawierać tylko cyfry, litery, spacje, myślniki, podkreślniki, kropki, i muszą mieć od 3 do 20 znaków.",
"kError_UsernameBlacklisted": "Nazwa użytkownika jest zakazana.",
"kError_IncorrectPassword": "Złe hasło",
"kAccountModal_Verify": "Zweryfikuj E-Mail",
"kAccountModal_AccountSettings": "Ustawienia Konta",
"kAccountModal_ResetPassword": "Zresetuj Hasło",
"kAccountModal_NewPassword": "Nowe Hasło",
"kAccountModal_ConfirmNewPassword": "Potwierdź Nowe Hasło",
"kAccountModal_CurrentPassword": "Aktualne Hasło",
"kAccountModal_ConfirmPassword": "Potwierdź Hasło",
"kMissingCaptcha": "Proszę wypełnić captchę.",
"kPasswordsMustMatch": "Hasła muszą być takie same.",
"kAccountModal_VerifyText": "Wysłaliśmy E-Maila na adres {0}. Aby zweryfikować twoje konto wprowadź 8-cyfrowy kod z poniższej wiadomości E-Mail.",
"kAccountModal_VerifyPasswordResetText": "Wysłaliśmy E-Maila na adres {0}. Aby zresetować twoje hasło wprowadź 8-cyfrowy kod z poniższej wiadomości E-Mail.",
"kAccountModal_PasswordResetSuccess": "Twoje hasło zostało zmienione. Możesz teraz zalogować się przy użyciu nowego hasła.",
"kNotLoggedIn": "Nie zalogowany(a)"
}
}

View File

@ -1,100 +0,0 @@
{
"languageName": "Português (Brasil)",
"translatedLanguageName": "Brazilian Portuguese",
"flag": "🇧🇷",
"author": "Iceburg",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Sim",
"kGeneric_No": "Não",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Cancelar",
"kGeneric_Send": "Enviar",
"kGeneric_Understood": "Entendido",
"kGeneric_Username": "Nome de usuário",
"kGeneric_Password": "Senha",
"kGeneric_Login": "Fazer login",
"kGeneric_Register": "Registrar",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Data de Nascimento",
"kGeneric_VerificationCode": "Código de verificação",
"kGeneric_Verify": "Verificar",
"kGeneric_Update": "Salvar",
"kGeneric_Logout": "Sair",
"kWelcomeModal_Header": "Bem vindo a CollabVM",
"kWelcomeModal_Body": "<p>Antes de continuar, familiarize-se com nossas regras:</p> <h3>R1. Não infrinja a lei.</h3> Não use o CollabVM ou a rede do CollabVM para violar leis federais dos Estados Unidos, lei do estado de Nova York ou lei internacional. Se o CollabVM tomar conhecimento de que um crime foi cometido por meio de seu serviço, você será imediatamente banido e suas atividades poderão ser denunciadas às autoridades, se necessário.<br><br>O CollabVM é. é obrigado por lei a notificar as agências de aplicação da lei se tomar conhecimento da presença de pornografia infantil ou sendo transmitida através de sua rede.<br><br>A COPPA também é aplicada, por favor, não use o CollabVM se você tiver menos de idade 13 anos. <h3>R2. Não é permitido executar ferramentas DoS/DDoS.</h3> Não use CollabVM para DoS/DDoS em um indivíduo, empresa, ou qualquer outra pessoa.</h3>R3. h3> Não envie spam em e-mails usando este serviço ou envie spam em geral. <h3>R4 Não abuse nenhum exploit (vulnerabilidade).</h3> Não abuse nenhum exploit, além disso, se você vir alguém abusando exploits ou precisar denunciar um, entre em contato comigo em: computernewbab@gmail.com <h3>R5. Não se faça passar por outros usuários.</h3> Não se faça passar por outros membros do CollabVM. Se for pego, você será temporariamente desconectado e banido, se necessário. <h3>T6. Um voto por pessoa.</h3> Não use nenhum método ou ferramenta para contornar a restrição de voto. Apenas um voto por pessoa é permitido, não importa o que aconteça. Qualquer pessoa que for flagrada fazendo isso será banida. <h3>T7. Sem ferramentas de administração remota.</h3> Não use nenhuma ferramenta de administração remota (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.) <h3>R8. Não é possível ignorar o CollabNet.</h3> Não tente ignorar o bloqueio fornecido pelo CollabNet, especialmente se ele estiver sendo usado para quebrar a Regra 1, Regra 2 ou Regra 7 (ou executar coisas estúpidas e usadas demais). <h3>R9. Não é permitido realizar ações destrutivas constantemente.</h3> Qualquer usuário não pode destruir a VM (tornando-a inutilizável constantemente), instalar/reinstalar o sistema operacional (exceto em VM7 ou VM8) ou executar bots que façam isso. Isso inclui bots que enviam spam para grandes quantidades de entradas de teclado/mouse (ou kitting). <h3>R10. Sem criptografia</h3> A tentativa de minerar criptomoedas nas VMs resultará em uma expulsão e, em seguida, em um banimento permanente se você continuar tentando. Além disso, não é como se você fosse ganhar dinheiro com isso. <h3>Aviso NSFW</h3> Observe que o conteúdo NSFW é permitido em nossa VM anarquia (VM0b0t) e é visualizado regularmente. Além disso, embora façamos um grande esforço para manter o NSFW fora das VMs principais, as pessoas ocasionalmente irão passar despercebidas.",
"kSiteButtons_Home": "Início",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Regras",
"kSiteButtons_DarkMode": "Modo Escuro",
"kSiteButtons_LightMode": "Modo Claro",
"kSiteButtons_Languages": "Idiomas",
"kVM_UsersOnlineText": "Usuários online:",
"kVM_TurnTimeTimer": "Sua vez expira em {0} segundos.",
"kVM_WaitingTurnTimer": "Esperando sua vez em {0} segundos.",
"kVM_VoteCooldownTimer": "Aguarde {0} segundos antes de iniciar outra votação.",
"kVM_VoteForResetTitle": "Deseja redefinir a VM?",
"kVM_VoteForResetTimer": "Voto acaba em {0} segundos",
"kVMButtons_TakeTurn": "Tomar turno",
"kVMButtons_EndTurn": "Finalizar turno",
"kVMButtons_ChangeUsername": "Mudar nome de usuário",
"kVMButtons_Keyboard": "Teclado",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Votar para Redefinir",
"kVMButtons_Screenshot": "Captura de tela",
"kQEMUMonitor": "Monitor QEMU",
"kAdminVMButtons_PassVote": "Passar Voto",
"kAdminVMButtons_CancelVote": "Cancelar Voto",
"kAdminVMButtons_Restore": "Redefinir",
"kAdminVMButtons_Reboot": "Reiniciar",
"kAdminVMButtons_ClearTurnQueue": "Limpar fila de turno",
"kAdminVMButtons_BypassTurn": "Ignorar Turno",
"kAdminVMButtons_IndefiniteTurn": "Turno infinito",
"kAdminVMButtons_GhostTurnOn": "Turno Fantasma (Ativar)",
"kAdminVMButtons_GhostTurnOff": "Turno Fantasma (Desativar)",
"kAdminVMButtons_Ban": "Banir",
"kAdminVMButtons_Kick": "Expulsar",
"kAdminVMButtons_TempMute": "Mudo temporáriamente",
"kAdminVMButtons_IndefMute": "Mudo indefinido",
"kAdminVMButtons_Unmute": "Desmudar",
"kAdminVMButtons_GetIP": "Obter IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Digite o novo nome para {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Tem certeza de que deseja restaurar a VM?",
"kVMPrompts_EnterNewUsernamePrompt": "Digite um novo nome de usuário ou deixe o campo em branco para receber um nome de usuário convidado",
"kError_UnexpectedDisconnection": "Você foi desconectado do servidor.",
"kError_UsernameTaken": "Esse nome de usuário já está em uso",
"kError_UsernameInvalid": "Nomes de usuário podem conter apenas números, letras, espaços, travessões, sublinhados e pontos e devem ter entre 3 e 20 caracteres.",
"kError_UsernameBlacklisted": "Esse nome de usuário foi colocado na lista negra.",
"kError_IncorrectPassword": "Senha incorreta.",
"kAccountModal_Verify": "Verificar E-Mail",
"kAccountModal_AccountSettings": "Configurações de Conta",
"kAccountModal_ResetPassword": "Redefinir senha",
"kAccountModal_NewPassword": "Nova senha",
"kAccountModal_ConfirmNewPassword": "Confirmar nova senha",
"kAccountModal_CurrentPassword": "Senha atual",
"kAccountModal_ConfirmPassword": "Confirmar senha atual",
"kMissingCaptcha": "Por favor preencha o captcha.",
"kPasswordsMustMatch": "As senhas devem corresponder.",
"kAccountModal_VerifyText": "Enviamos um e-mail para {0}. Para verificar sua conta, insira o código de 8 dígitos do e-mail abaixo.",
"kAccountModal_VerifyPasswordResetText": "Enviamos um e-mail para {0}. Para redefinir sua senha, digite o código de 8 dígitos do e-mail abaixo.",
"kAccountModal_PasswordResetSuccess": "Sua senha foi alterada com sucesso. Agora você pode fazer login com sua nova senha.",
"kNotLoggedIn": "Não logado"
}
}

View File

@ -1,97 +0,0 @@
{
"languageName": "Română (RO)",
"translatedLanguageName": "Romanian (RO)",
"flag": "🇷🇴",
"author": "n0vac, mvoolt",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Da",
"kGeneric_No": "Nu",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Anulează",
"kGeneric_Send": "Trimite",
"kGeneric_Understood": "Ințeles",
"kGeneric_Username": "Numele de utilizator",
"kGeneric_Password": "Parolă",
"kGeneric_Login": "Contectază-te",
"kGeneric_Register": "Înregistrează-te",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Data de naștere",
"kGeneric_VerificationCode": "Codul de verificare",
"kGeneric_Verify": "Verifica",
"kGeneric_Update": "Actualizează",
"kGeneric_Logout": "Deconectare",
"kWelcomeModal_Header": "Bun venit la CollabVM",
"kWelcomeModal_Body": "<p>Înainte de a continua, vă rugăm să vă familiarizați cu regulile noastre:</p> <h3>R1. Nu încălcați legea.</h3> Nu utilizați CollabVM sau rețeaua CollabVM pentru a încălca legea federală a Statelor Unite, legea statului New York sau legea internațională. Dacă CollabVM devine conștientă că s-a comis o crimă prin intermediul serviciului său, veți fi imediat interzis, iar activitățile dvs. pot fi raportate autorităților dacă este necesar.<br><br>CollabVM este obligat prin lege să notifice agențiile de aplicare a legii dacă devine conștient de prezența pornografiei infantile pe rețeaua sa.<br><br>De asemenea, COPPA este respectată, vă rugăm să nu utilizați CollabVM dacă aveți sub 13 ani. <h3>R2. Nu rulați instrumente DoS/DDoS.</h3> Nu utilizați CollabVM pentru a efectua DoS/DDoS asupra unei persoane, afaceri, companii sau altor persoane. <h3>R3. Nu distribuiți spam.</h3> Nu trimiteți spam pe e-mailuri folosind acest serviciu sau nu trimiteți spam în general. <h3>R4. Nu abuzați de nicio vulnerabilitate.</h3> Nu abuzați de nicio vulnerabilitate, în plus, dacă vedeți pe cineva abuzând de vulnerabilități sau trebuie să raportați una, vă rugăm să ne contactați la: computernewbab@gmail.com. <h3>R5. Nu vă impersonați alți utilizatori.</h3> Nu vă impersonați alți membri ai CollabVM. Dacă sunteți prins, veți fi temporar deconectat și interzis dacă este necesar. <h3>R6. Un vot pe persoană.</h3> Nu folosiți niciun metodă sau instrument pentru a evita restricția de vot. Este permis un singur vot per persoană, indiferent de circumstanțe. Orice persoană prinsă făcând acest lucru va fi interzisă. <h3>R7. Fără unelte de administrare de la distanță.</h3> Nu utilizați niciunelte de administrare de la distanță (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, etc.). <h3>R8. Fără a evita CollabNet.</h3> Nu încercați să evitați blocarea furnizată de CollabNet, în special dacă este utilizată pentru a încălca Regula 1, Regula 2 sau Regula 7 (sau pentru a rula lucruri stupide și prea utilizate). <h3>R9. Fără a efectua acțiuni distructive constant.</h3> Orice utilizator nu poate distruge constant VM (făcându-l constant inutilizabil), instala/reinstala sistemul de operare (cu excepția VM7 sau VM8) sau rula bot-uri care fac astfel. Acest lucru include bot-uri care trimit cantități masive de intrări de la tastatură/mouse (\"kitting\"). <h3>R10. Fără minare de criptomonede</h3> Încercarea de a mina criptomonede pe VM-uri va rezulta într-o eliminare, și apoi o interzicere permanentă dacă continuați să încercați. În plus, nu e ca și cum ai face vreun bani de pe urma asta. <h3>Avertisment NSFW</h3> Vă rugăm să rețineți că conținutul NSFW este permis pe VM-ul nostru de anarhie (VM0b0t) și este vizualizat în mod regulat. În plus, deși facem un efort bun pentru a menține conținutul NSFW departe de VM-urile principale, uneori oamenii îl vor strecura.",
"kSiteButtons_Home": "Acasă",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Reguli",
"kSiteButtons_DarkMode": "Modul întunecat",
"kSiteButtons_LightMode": "Modul luminos",
"kSiteButtons_Languages": "Limbi",
"kVM_UsersOnlineText": "Utilizatori online:",
"kVM_TurnTimeTimer": "Rândul expiră în {0} secunde.",
"kVM_WaitingTurnTimer": "Așteptând rândul în {0} secunde.",
"kVM_VoteCooldownTimer": "Vă rugăm să așteptați {0} secunde înainte de a începe o altă votare.",
"kVM_VoteForResetTitle": "Vrei să resetezi mașina virtuală?",
"kVM_VoteForResetTimer": "Votul se încheie în {0} secunde",
"kVMButtons_TakeTurn": "Ia rândul",
"kVMButtons_EndTurn": "Încheie rândul tău",
"kVMButtons_ChangeUsername": "Schimbă numele de utilizator",
"kVMButtons_Keyboard": "Tastatură",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Votează pentru resetare",
"kVMButtons_Screenshot": "Captură de ecran",
"kQEMUMonitor": "Monitor QEMU",
"kAdminVMButtons_PassVote": "Aprobă votul",
"kAdminVMButtons_CancelVote": "Anulează votul",
"kAdminVMButtons_Restore": "Restaurează",
"kAdminVMButtons_Reboot": "Repornire",
"kAdminVMButtons_ClearTurnQueue": "Șterge coada de rânduri",
"kAdminVMButtons_BypassTurn": "Ocolire rând",
"kAdminVMButtons_IndefiniteTurn": "Rând indefinit",
"kAdminVMButtons_Ban": "Interzice",
"kAdminVMButtons_Kick": "Elimină",
"kAdminVMButtons_TempMute": "Mut temporar",
"kAdminVMButtons_IndefMute": "Mut indefinit",
"kAdminVMButtons_Unmute": "Revocă mutul",
"kAdminVMButtons_GetIP": "Obține IP-ul",
"kVMPrompts_AdminChangeUsernamePrompt": "Introduceți noul nume de utilizator pentru {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Sunteți sigur că doriți să resetați mașina virtuală?",
"kVMPrompts_EnterNewUsernamePrompt": "Introduceți un nou nume de utilizator sau lăsați câmpul gol pentru a primi un nume de utilizator de oaspete",
"kError_UnexpectedDisconnection": "Ați fost deconectat de la server.",
"kError_UsernameTaken": "Acel nume de utilizator este deja luat.",
"kError_UsernameInvalid": "Numele de utilizator pot conține doar numere, litere, spații, cratime, sublinieri și puncte, și trebuie să aibă între 3 și 20 de caractere.",
"kError_UsernameBlacklisted": "Acel nume de utilizator a fost adăugat în lista neagră.",
"kError_IncorrectPassword": "Parolă incorectă.",
"kAccountModal_Verify": "Verifică E-mailul",
"kAccountModal_AccountSettings": "Setări cont",
"kAccountModal_ResetPassword": "Resetare parolă",
"kAccountModal_NewPassword": "Parolă nouă",
"kAccountModal_ConfirmNewPassword": "Confirmă parola nouă",
"kAccountModal_CurrentPassword": "Parola curentă",
"kAccountModal_ConfirmPassword": "Confirmă parola",
"kMissingCaptcha": "Vă rugăm să completați captcha-ul.",
"kPasswordsMustMatch": "Parolele trebuie să fie identice.",
"kAccountModal_VerifyText": "Am trimis un E-mail către {0}. Pentru a vă verifica contul, vă rugăm să introduceți codul de 8 cifre din E-mail-ul de mai jos.",
"kAccountModal_VerifyPasswordResetText": "Am trimis un E-mail către {0}. Pentru a vă reseta parola, vă rugăm să introduceți codul de 8 cifre din E-mail-ul de mai jos.",
"kAccountModal_PasswordResetSuccess": "Parola dvs. a fost schimbată cu succes. Acum vă puteți autentifica cu noua parolă.",
"kNotLoggedIn": "Nu sunteți autentificat"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "Русский",
"translatedLanguageName": "Russian",
"flag": "🇷🇺",
"author": "Undefishin, lua5.3, Noname",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Да",
"kGeneric_No": "Нет",
"kGeneric_Ok": "ОК",
"kGeneric_Cancel": "Отменить",
"kGeneric_Send": "Отправить",
"kGeneric_Understood": "Принять",
"kGeneric_Username": "Имя",
"kGeneric_Password": "Пароль",
"kGeneric_Login": "Войти",
"kGeneric_Register": "Зарегистрироваться",
"kGeneric_EMail": "Электронная почта",
"kGeneric_DateOfBirth": "День рождения",
"kGeneric_VerificationCode": "Код подтверждения",
"kGeneric_Verify": "Подтвердить",
"kGeneric_Update": "Обновить",
"kGeneric_Logout": "Выйти из аккаунта",
"kWelcomeModal_Header": "Добро пожаловать в CollabVM!",
"kWelcomeModal_Body": "<p>Пожалуйста, ознакомьтесь с правилами:</p> <h3>Не нарушайте закон</h3> Не используйте CollabVM или сеть CollabVM для нарушения федерального законодательства США, законодательства штата Нью-Йорк или международного законодательства. В случае необходимости мы сообщим властями о вашей деятельности. <h3>Не запускать инструменты DoS/DDoS.</h3> Не используйте CollabVM для DDoS аттак отдельных лиц, компаний, предприятий или т.д. <h3>Не рассылайте почтовый спам.</h3> Не рассылайте спам по электронной почте используя эту CollabVM. <h3>Не злоупотребляйте эксплойтами.</h3> Не злоупотребляйте эксплойтами, кроме того, если вы видите, что кто-то злоупотребляет эксплойтами вам нужно сообщить об этом по почте computernewbab@gmail.com. <h3>Не выдавайте себя за других пользователей.</h3> Не выдавайте себя за других пользователй или персонал CollabVM. <h3>Один голос от одного человека.</h3> Разрешается только один голос для сброса от одного человека. Нельзя голосовать за сброс виртуальной машину больше чем 1 раз. <h3>Никаких инструментов удаленного доступа</h3> Не используйте инструменты удаленного доступа (например, AnyDesk , DarkComet , NjRAT или TeamViewer). <h3>Не обходить CollabNet</h3> Не пытайтесь обойти блокировку, предоставляемую CollabNet. <h3>Не уничтожайте Виртуальную Машину постоянно</h3> Не уничтожайте постоянно Виртуальную Машину/приводите ее в негодность, не переустанавливайте операционную систему (кроме VM7 или VM8) и не запускайте ботов, которые это делают. <h3>Никакого крипто-майнинга</h3> Не майните криптовалюту на виртуальной машине, если об этом узнают, вы будете забанены. Вы все равно вы не получите за это денег. <h3>Предупреждение о NSFW</h3> NSFW-контент разрешен только на Вертуальной Машине \"Анархия\" (ВМ0) . На всех остальных вертуальный машинах NSFW контент запрщен и блокирует через E2Guarden.",
"kSiteButtons_Home": "Главная",
"kSiteButtons_FAQ": "ЧаВо",
"kSiteButtons_Rules": "Правила",
"kSiteButtons_DarkMode": "Темная тема",
"kSiteButtons_LightMode": "Светлая тема",
"kSiteButtons_Languages": "Языки",
"kVM_UsersOnlineText": "Пользователи в сети",
"kVM_TurnTimeTimer": "Твой ход закончится через {0} секунд(ы)",
"kVM_WaitingTurnTimer": "Твой ход будет через {0} секунд(ы)",
"kVM_VoteCooldownTimer": "Пожалуйста, подождите {0} секунд(ы) перед началом голосования за сброс",
"kVM_VoteForResetTitle": "Вы хотите сбросить виртуальную машину?",
"kVM_VoteForResetTimer": "Голосование закончится через {0} секунд(ы)",
"kVMButtons_TakeTurn": "Сделать ход",
"kVMButtons_EndTurn": "Закончить ход",
"kVMButtons_ChangeUsername": "Сменить имя",
"kVMButtons_Keyboard": "Клавиатура",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Голосовать за сброс",
"kVMButtons_Screenshot": "Скриншот",
"kQEMUMonitor": "QEMU Монитор",
"kAdminVMButtons_PassVote": "Восстановить виртуальную машину",
"kAdminVMButtons_CancelVote": "Отменить голосование",
"kAdminVMButtons_Restore": "Восстановить",
"kAdminVMButtons_Reboot": "Перезагрузить",
"kAdminVMButtons_ClearTurnQueue": "Очистить очередь ходов",
"kAdminVMButtons_BypassTurn": "Обойти чужые ходы",
"kAdminVMButtons_IndefiniteTurn": "Бесконечный ход",
"kAdminVMButtons_Ban": "Забанить",
"kAdminVMButtons_Kick": "Кикнуть",
"kAdminVMButtons_TempMute": "Замутить на время",
"kAdminVMButtons_IndefMute": "Замутить на неопределённое время",
"kAdminVMButtons_Unmute": "Размутить",
"kAdminVMButtons_GetIP": "Получить IP-адрес",
"kVMPrompts_AdminChangeUsernamePrompt": "Введите новый никнейм для {0}",
"kVMPrompts_AdminRestoreVMPrompt": "Вы уверены, что хотите восстановить виртуальную машину?",
"kVMPrompts_EnterNewUsernamePrompt": "Введите новое имя пользователя",
"kError_UnexpectedDisconnection": "Вы были отключены от сервера",
"kError_UsernameTaken": "Это имя занято.",
"kError_UsernameInvalid": "Имена могут содержать только цифры, буквы, пробелы, тере, нижнее подчеркивание, и точки, и должны быть от 3 до 20 букв.",
"kError_UsernameBlacklisted": "Это имя запрещено.",
"kError_IncorrectPassword": "Неверный пароль.",
"kAccountModal_Verify": "Подтвердить электронную почту",
"kAccountModal_AccountSettings": "Настройки аккаунта",
"kAccountModal_ResetPassword": "Сбросить пароль",
"kAccountModal_NewPassword": "Новый пароль",
"kAccountModal_ConfirmNewPassword": "Подтвердить новый пароль",
"kAccountModal_CurrentPassword": "Текущий пароль",
"kAccountModal_ConfirmPassword": "Подтвердите пароль",
"kMissingCaptcha": "Пожалуйста, пройдите капчу.",
"kPasswordsMustMatch": "Пароли должны совпадать.",
"kAccountModal_VerifyText": "Мы отправили письмо на {0}. Чтобы подтвердить ваш аккаунт, пожалуйста, введи 8-значный код из письма ниже.",
"kAccountModal_VerifyPasswordResetText": "Мы отправили письмо на {0}. Чтобы сбросить ваш пароль, пожалуйста, введи 8-значный код из письма ниже.",
"kAccountModal_PasswordResetSuccess": "Пароль сброшен успешно! Теперь, вы можете войти с новым паролем.",
"kNotLoggedIn": "Не авторизован"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,98 +0,0 @@
{
"languageName": "Toki Pona",
"translatedLanguageName": "toki pona",
"flag": "🌐",
"author": "Computernewb",
"stringKeys": {
"kGeneric_CollabVM": "ilo CollabVM",
"kGeneric_Yes": "lon",
"kGeneric_No": "ala",
"kGeneric_Ok": "pona",
"kGeneric_Cancel": "o ala",
"kGeneric_Send": "o pana",
"kGeneric_Understood": "mi sona",
"kGeneric_Username": "nimi",
"kGeneric_Password": "nimi len",
"kGeneric_Login": "o lon sijelo",
"kGeneric_Register": "o pali e sijelo",
"kGeneric_EMail": "o toki E-mail",
"kGeneric_DateOfBirth": "tenpo pi kama lon",
"kGeneric_VerificationCode": "sitelen pi pona sona",
"kGeneric_Verify": "o pona e sona",
"kGeneric_Update": "o sin",
"kGeneric_Logout": "o weka tan sijelo",
"kWelcomeModal_Header": "ilo CollabVM la o kama pona",
"kWelcomeModal_Body": "<p>open la o sona e lawa mi:</p> <h3>nanpa wan la o ike ala tawa lawa ma.</h3> kepeken ilo mi la o ike ala tawa lawa pi ma Mewika, tawa lawa pi ma Nujo, tawa lawa pi kulupu ma. mi kama sona e ike ni sina la, mi weka e sina tan ilo. mi o toki tawa kulupu lawa ma la, mi toki.<br><br>mi kama sona e sitelen unpa pi jan lili lon ilo mi la, lawa ma la mi o toki tawa kulupu pi lawa ma.<br><br>sina majuna pi tenpo sike 13 ala la, lawa COPPA la o kepeken ala ilo CollabVM. <h3>nanpa tu la o pakala ala e ilo Internet ante kepeken ilo mi.</h3><!-- Rest omitted because redundant sentences do not translate well in toki pona. --> <h3>nanpa tu la o pana ala e jaki.</h3> kepeken ilo mi la o jaki ala kepeken ilo E-mail, kepeken ilo ante. <h3>nanpa tu tu la o ike ala kepeken pakala ilo.</h3> sina lukin e pakala ilo la, o ike ala kepeken ona. toki tawa mi kepeken nimi E-mail \"computernewbab@gmail.com\" <h3>nanpa luka la nasin ike la o sama ala jan ante.</h3> sina ni la, mi weka e sina lon tenpo lili. mi o weka e sina lon tenpo ale la, mi ni. <h3>nanpa luka wan la jan wan li ken toki e wile lon tenpo wan.</h3> o weka ala tan lawa ni kepeken ilo. sina toki e wile lon tenpo mute la, mi weka e sina. <h3>nanpa luka tu la o kepeken ala ilo pi lawa ilo.</h3> ala la o kepeken ilo DarkComet, kepeken ilo NanoCore, kepeken ilo Anydesk, kepeken ilo TeamViewer, kepeken ilo Orcus, kepeken ilo ante sama ni. <h3>nanpa luka tu wan la mi weka e ken la, o alasa ala e ni.</h3> ni la sina wile pali ike tawa lawa nanpa wan, anu tawa lawa nanpa tu, anu tawa lawa nanpa luka tu la, ni li ike mute. sina wile kepeken ilo jaki pi kepeken mute la, ni li ike mute.<!-- this is a bad translation but it's a badly-written rule --> <h3>lawa nanpa luka tu tu la o pakala suli ala e ilo.</h3> pakala ilo la o weka ala e ken pali ilo. ilo li ilo nanpa luka tu ala, li ilo nanpa luka tu wan ala la, o sin ala e nasin ilo, o kepeken ala ilo pi sin nasin. ilo li kepeken mute ilo sitelen, kepeken mute ilo wile la, ona li ni. <h3>lawa nanpa luka luka la o alasa ala e mani nanpa</h3> sina ni la, mi weka e sina pi tenpo wan. sina awen alasa, mi weka e sina pi tenpo ale. kin sina kama ala jo e mani tan ni. <h3>unpa la</h3> o sona e ni: ilo 0b0t la sitelen unpa li ken, li lon mute. mi alasa weka e sitelen unpa tan ilo ante. taso tenpo la ona li kama.",
"kSiteButtons_Home": "tomo",
"kSiteButtons_FAQ": "wile sona",
"kSiteButtons_Rules": "lawa",
"kSiteButtons_DarkMode": "pimeja",
"kSiteButtons_LightMode": "walo",
"kSiteButtons_Languages": "toki",
"kVM_UsersOnlineText": "jan ni li lon:",
"kVM_TurnTimeTimer": "ken pali li weka lon tenpo lili {0}.",
"kVM_WaitingTurnTimer": "sina ken pali lon tenpo lili {0}.",
"kVM_VoteCooldownTimer": "sina wile open sin e toki wile la, o awen lon tenpo lili {0}.",
"kVM_VoteForResetTitle": "sina wile ala wile sin e ilo?",
"kVM_VoteForResetTimer": "toki wile li pini lon tenpo lili {0}",
"kVMButtons_TakeTurn": "o pali",
"kVMButtons_EndTurn": "o pini e pali",
"kVMButtons_ChangeUsername": "o ante e nimi",
"kVMButtons_Keyboard": "ilo sitelen",
"KVMButtons_CtrlAltDel": "sitelen Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "o wile e sin ilo",
"kVMButtons_Screenshot": "o jo e sitelen ni",
"kQEMUMonitor": "sinpin QEMU",
"kAdminVMButtons_PassVote": "mi pilin ala",
"kAdminVMButtons_CancelVote": "ni li wile mi ala",
"kAdminVMButtons_Restore": "o sin",
"kAdminVMButtons_Reboot": "o pini, o open",
"kAdminVMButtons_ClearTurnQueue": "o weka e nasin pali",
"kAdminVMButtons_BypassTurn": "o pana ala e pali",
"kAdminVMButtons_IndefiniteTurn": "pali pi tenpo ale",
"kAdminVMButtons_Ban": "o weka pi tenpo ale",
"kAdminVMButtons_Kick": "o weka pi tenpo wan",
"kAdminVMButtons_TempMute": "tenpo la o weka e ken toki",
"kAdminVMButtons_IndefMute": "ale la o weka e ken toki",
"kAdminVMButtons_Unmute": "o sin e ken toki",
"kAdminVMButtons_GetIP": "o sona e sitelen IP",
"kVMPrompts_AdminChangeUsernamePrompt": "jan {0} la o pana e nimi sin:",
"kVMPrompts_AdminRestoreVMPrompt": "sina wile ala wile sin e ilo?",
"kVMPrompts_EnterNewUsernamePrompt": "o pana e nimi. ala la, mi pana e nimi pi jan sin",
"kError_UnexpectedDisconnection": "sina weka tan ilo.",
"kError_UsernameTaken": "jan ante li jo e nimi ni",
"kError_UsernameInvalid": "nimi li ken jo taso e sitelen [A-Za-z0-9 \\-_.]. sitelen tu li lili ike. siten mute wan li suli ike.",
"kError_UsernameBlacklisted": "nimi ni li ken ala.",
"kError_IncorrectPassword": "ni li nimi len sina ala.",
"kAccountModal_Verify": "nimi E-mail la o pona e sona",
"kAccountModal_AccountSettings": "wile sijelo",
"kAccountModal_ResetPassword": "o ante e nimi len",
"kAccountModal_NewPassword": "nimi len sin",
"kAccountModal_ConfirmNewPassword": "sin la nimi len sin",
"kAccountModal_CurrentPassword": "nimi len lon",
"kAccountModal_ConfirmPassword": "sin la nimi len sin",
"kMissingCaptcha": "o toki e sitelen CAPTCHA.",
"kPasswordsMustMatch": "nimi len o sama.",
"kAccountModal_VerifyText": "mi toki E-mail tawa nimi {0}. pona sona la o pana e sitelen luka tu wan tan ni.",
"kAccountModal_VerifyPasswordResetText": "mi toki E-mail tawa nimi {0}. ante pi nimi len la o pana e sitelen luka tu wan tan ni.",
"kAccountModal_PasswordResetSuccess": "nimi len sina li kama ante. sina ken lon sijelo kepeken ona.",
"kNotLoggedIn": "sina lon sijelo ala"
}
}

View File

@ -1,100 +0,0 @@
{
"languageName": "Türkçe",
"translatedLanguageName": "Turkish",
"flag": "🇹🇷",
"author": "enescetinkal",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Evet",
"kGeneric_No": "Hayır",
"kGeneric_Ok": "Tamam",
"kGeneric_Cancel": "İptal et",
"kGeneric_Send": "Gönder",
"kGeneric_Understood": "Anlaşıldı",
"kGeneric_Username": "Takma isim",
"kGeneric_Password": "Şifre",
"kGeneric_Login": "Giriş yap",
"kGeneric_Register": "Kayıt ol",
"kGeneric_EMail": "E-Mail",
"kGeneric_DateOfBirth": "Doğum Tarihi",
"kGeneric_VerificationCode": "Doğrulama kodu",
"kGeneric_Verify": "Doğrula",
"kGeneric_Update": "Güncelle",
"kGeneric_Logout": ıkış yap",
"kWelcomeModal_Header": "CollabVM'e hoşgeldiniz.",
"kWelcomeModal_Body": "<p>Devam etmeden önce, lütfen bu kuralları tanıyın.</p> <h3>K1. Kanunları çiğnemeyin. </h3> Collab VM'i ve ağlarını lütfen ABD,New york State yada uluslararası kanunu çiğnemek için kullanmayın. Eğer CollabVm hizmetlerinde senin kanun çiğnediğini görerse ,hemen banlanırsınız., ve yaptığınız eylemleri gerekirse otoritelere söyler.<br><br>CollabVM kanun nedeniyle çocuk pornografisini eğer hizmetlerinde veya ağlarında paylaşılp kullanılırsa kanuni hizmetlere bildirilir.<br><br>COPPA'da vardır. Lütfen CollabVM'i ve araçlarını 13 yaş altındaysanız kullanmayın. <h3>K2. DoS/DDoS araçlarını çalıştırmayın.</h3> CollabVM'i lütfen bir kişiyi,işletmey,markayı yada hiçkimseyi DoS/DDoS'lamak için kullanmayın. <h3>K3. Spam Paylaşmayın.</h3> Bu hizmetleri lütfen spam mail yada herhangi bir spam dağıtmak için kullanmayın. <h3>K4. Açıkları kötüye ve aşırıya kullanmayın.</h3> Açıkları kötüye kullanmayın. Eğer birisinin açıkları görüp raporlamak istersen, Lütfen computernewbab@gmail.com adresinden benimle iletişime geçin. <h3>K5. Başka kişileri taklit etme.</h3> CollabVm'in kullanıcılarını taklit etmeyin. Yakalınırsanız, bağlantınız kesilir ve gerekirse banlanırsınız. <h3>K6. Her kişiye bir oy.</h3> Oy sınıralını geçmek için hiçbir araç yada yontem kullanmayın. Her zaman her bir kişi sadece bir oy kullanır. Bunu yapıp yakalanan herkes banlanır. <h3>K7. Uzaktan Yönetme araçları yasak. </h3> Hiçbir uzaktan yönetme araçı kullanmayın.(öğrn: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, vs.) <h3>K8. CollabNet'in sınırlaını geçmeyin.</h3> CollabNet'in sunduğu engelleri aşamya çalışmayın, özellikle kural 1,2 ve 7'yi çiğnemek için yapmayın (yada salakca çok yapılmış şeyleri çalıştırmak için). <h3>K9. Sürekli yok eden aksiyonlar yapmayın.</h3> Hiçbir kişi VM'i yok etmemeli (sürekli kullanışsız yapmak), işletim sistemini yüklememeli/gene yüklememeli (VM7 yada VM8 dışında), yada bunları yapan botları çalıştırmamalı. Bu birsürü klavye/mouse girişi spamlayan botlarada dahildir (\"kitting\"). <h3>K10. Kripto Minelamak Yasak.</h3>VMlerde kripto kazmaya çalışırsanız atılırsınız , hala yapmaya çalışırsanız da kalıcı ban alırsınız. Zaten para yapacak gibi değilsin. <h3>NSFW Uyarısı</h3> Lütfen anarşi vm'de (VM0b0t'ta) NSFW materiyal olabileceğine ve normalde izlendiğine not alın. Birde, Ana VM'lerde NSFW materiyalleri engellemeye çalışıyoruz ama bazı kişiler genede açabilir.",
"kSiteButtons_Home": "Ev",
"kSiteButtons_FAQ": "SSS",
"kSiteButtons_Rules": "Kurallar",
"kSiteButtons_DarkMode": "Koyu Mod",
"kSiteButtons_LightMode": "Açık Mod",
"kSiteButtons_Languages": "Diller",
"kVM_UsersOnlineText": "Çevrimiçi kişiler:",
"kVM_TurnTimeTimer": "Sıranız {0} saniye içinde bitecektir.",
"kVM_WaitingTurnTimer": "Sıranızın gelmesi için {0} saniye bekleyin. ",
"kVM_VoteCooldownTimer": "Oylama yapmadan önce lütfen {0} saniye bekleyin.",
"kVM_VoteForResetTitle": "Bu VM'i sıfırlamak istermisin?",
"kVM_VoteForResetTimer": "Oylama {0} saniye içinde bitecek.",
"kVMButtons_TakeTurn": "Sıra al",
"kVMButtons_EndTurn": "Sıranı sonlandır",
"kVMButtons_ChangeUsername": "Takma ismini değiştir",
"kVMButtons_Keyboard": "Klavye",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Sıfırlamak için oylama aç",
"kVMButtons_Screenshot": "Ekran görüntüsü",
"kQEMUMonitor": "QEMU Monitörü",
"kAdminVMButtons_PassVote": "Oyu geçir",
"kAdminVMButtons_CancelVote": "Oyu iptal et",
"kAdminVMButtons_Restore": "Geri Yükle",
"kAdminVMButtons_Reboot": "Yeniden başlat",
"kAdminVMButtons_ClearTurnQueue": "Bekleyen sıraları temizle",
"kAdminVMButtons_BypassTurn": "Sırayı atla",
"kAdminVMButtons_IndefiniteTurn": "Sonsuz sıra",
"kAdminVMButtons_GhostTurnOn": "Gizli sıra (Açık)",
"kAdminVMButtons_GhostTurnOff": "Gizli sıra (Kapalı)",
"kAdminVMButtons_Ban": "Ban",
"kAdminVMButtons_Kick": "At",
"kAdminVMButtons_TempMute": "Geçici Mute",
"kAdminVMButtons_IndefMute": "Kalıcı Mute",
"kAdminVMButtons_Unmute": "Mute'u sil",
"kAdminVMButtons_GetIP": "IP'yi al",
"kVMPrompts_AdminChangeUsernamePrompt": "{0} için yeni bir isim gir:",
"kVMPrompts_AdminRestoreVMPrompt": "VM'i geri yüklemeye eminmisin?",
"kVMPrompts_EnterNewUsernamePrompt": "Yeni bir isim gir yada yazı yerini boş tutarak bir misafir ismi al.",
"kError_UnexpectedDisconnection": "Sunucudan bağlantın kesildi.",
"kError_UsernameTaken": "Takma adı başkası aldı.",
"kError_UsernameInvalid": "Takma adlar içinde sadece numaralar, harfler, boşluklar, çizgiler, altreler ve noktalar içerir. Ve isimler 3 ve 20 karakter içermelidir.",
"kError_UsernameBlacklisted": "Bu takma isim karalistelendi.",
"kError_IncorrectPassword": "Yanlış şifre.",
"kAccountModal_Verify": "E-maili Doğrula",
"kAccountModal_AccountSettings": "Hesap Ayarları",
"kAccountModal_ResetPassword": "Reset Password",
"kAccountModal_NewPassword": "Yeni Şifre",
"kAccountModal_ConfirmNewPassword": "Yeni Şifreyi Doğrula",
"kAccountModal_CurrentPassword": "Şimdiki Şifre",
"kAccountModal_ConfirmPassword": "Şifreyi Değiştir",
"kAccountModal_HideFlag": "Ülke Bayrağımı Gizle",
"kMissingCaptcha": "Lütfen Caphta'yı doğrulayın.",
"kPasswordsMustMatch": "Şifleler uymalı.",
"kAccountModal_VerifyText": "{0} adresine E-mail gönderdik. Hesabınızı doğrulamak için lütfen attığımız E-mail'daki 8 haneli kodu aşağığa girin.",
"kAccountModal_VerifyPasswordResetText": "{0} adresine E-mail gönderdik. Şifrenizi sıfırlamak için lütfen attığımız E-mail'daki 8 haneli kodu aşağığa girin.",
"kAccountModal_PasswordResetSuccess": "Şifreniz başarılyla değiştirildi. Yeni şifrenizle hesabınıza girebilirsiniz.",
"kNotLoggedIn": "Giriş Yapılmadı"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,98 +0,0 @@
{
"languageName": "Tiếng Việt",
"translatedLanguageName": "Vietnamese",
"flag": "vn",
"author": "ubuntu2310fake",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "Có",
"kGeneric_No": "Không",
"kGeneric_Ok": "OK",
"kGeneric_Cancel": "Hủy",
"kGeneric_Send": "Gửi",
"kGeneric_Understood": "Hiểu",
"kGeneric_Username": "Tên",
"kGeneric_Password": "Mật khẩu",
"kGeneric_Login": "Đăng Nhập",
"kGeneric_Register": "Đăng kí",
"kGeneric_EMail": "Email",
"kGeneric_DateOfBirth": "Ngày tháng năm sinh",
"kGeneric_VerificationCode": "Mã xác thực",
"kGeneric_Verify": "Xác minh",
"kGeneric_Update": "Cập nhật",
"kGeneric_Logout": "Đăng xuất",
"kWelcomeModal_Header": "Chào mừng bạn đã đến với CollabVM",
"kWelcomeModal_Body": "<p>Trước khi tiếp tục, vui lòng làm quen với các quy tắc của chúng tôi:</p> <h3>R1. Đừng vi phạm pháp luật.</h3> Không sử dụng mạng CollabVM hoặc CollabVM để vi phạm luật liên bang Hoa Kỳ, luật tiểu bang New York hoặc luật pháp quốc tế. Nếu CollabVM biết được một tội ác đã được thực hiện thông qua dịch vụ của mình, bạn sẽ bị cấm ngay lập tức và các hoạt động của bạn có thể bị báo cáo cho chính quyền nếu cần thiết.<br><br>CollabVM được pháp luật yêu cầu phải thông báo cho các cơ quan thực thi pháp luật nếu họ biết về sự hiện diện của nội dung khiêu dâm trẻ em trên hoặc được truyền qua mạng của mình.<br><br>COPPA cũng được thực thi, vui lòng không sử dụng CollabVM nếu bạn dưới 13 tuổi. <h3>R2. Không chạy các công cụ DoS/DDoS.</h3> Không sử dụng CollabVM để DoS/DDoS của cá nhân, doanh nghiệp, công ty hoặc bất kỳ ai khác. <h3>R3. Không phát tán thư rác.</h3> Không spam bất kỳ email nào sử dụng dịch vụ này hoặc đẩy thư rác nói chung. <h3>R4. Đừng lạm dụng bất kỳ lợi ích nào.</h3> Không lạm dụng bất kỳ hành vi khai thác nào, ngoài ra nếu bạn thấy ai đó lạm dụng hành vi khai thác hoặc bạn cần báo cáo một hành vi khai thác, vui lòng liên hệ với tôi theo địa chỉ: computernewbab@gmail.com <h3>R5. Không mạo danh người dùng khác.</h3> Không mạo danh các thành viên khác của CollabVM. Nếu bị bắt, bạn sẽ bị ngắt kết nối tạm thời và bị cấm nếu cần thiết. <h3>R6. Một phiếu bầu cho mỗi người.</h3> Không sử dụng bất kỳ phương pháp hoặc công cụ nào để vượt qua hạn chế bỏ phiếu. Mỗi người chỉ được phép bỏ phiếu một lần, bất kể thế nào. Bất cứ ai bị bắt làm điều này sẽ bị cấm. <h3>R7. Không có công cụ quản trị máy tính từ xa.</h3> Không sử dụng bất kỳ công cụ quản trị từ xa nào (ex: DarkComet, NanoCore, Anydesk, TeamViewer, Orcus, Ultraviewer, etc.) <h3>R8. Không bỏ qua CollabNet.</h3> Đừng cố gắng vượt qua tính năng chặn do CollabNet cung cấp, đặc biệt nếu nó đang được sử dụng để vi phạm Quy tắc 1, Quy tắc 2 hoặc Quy tắc 7 (hoặc chạy những thứ ngu ngốc được sử dụng quá mức). <h3>R9. Không thực hiện hành động phá hoại liên tục.</h3> Bất kỳ người dùng nào cũng không được phá hủy VM (làm cho nó liên tục không sử dụng được), cài đặt/cài đặt lại hệ điều hành (ngoại trừ trên VM7 hoặc VM8) hoặc chạy các bot thực hiện việc đó. Điều này bao gồm các bot spam số lượng lớn thao tác nhập bằng bàn phím/chuột (\"kitting\"). <h3>R10. Không khai thác tiền điện tử</h3> Cố gắng khai thác tiền điện tử trên máy ảo sẽ bị phạt và sau đó là lệnh cấm vĩnh viễn nếu bạn tiếp tục cố gắng. Ngoài ra, có vẻ như bạn sẽ không kiếm được tiền từ nó. <h3>Cảnh báo NSFW</h3> Xin lưu ý rằng nội dung NSFW được cho phép trên máy ảo vô chính phủ (VM0b0t) của chúng tôi và được xem thường xuyên. Ngoài ra, trong khi chúng tôi nỗ lực hết sức để loại bỏ NSFW khỏi các máy ảo chính, đôi khi mọi người sẽ bỏ qua nó.",
"kSiteButtons_Home": "Trang chủ",
"kSiteButtons_FAQ": "FAQ",
"kSiteButtons_Rules": "Luật",
"kSiteButtons_DarkMode": "Giao diện tối",
"kSiteButtons_LightMode": "Giao diện sáng",
"kSiteButtons_Languages": "Ngôn ngữ",
"kVM_UsersOnlineText": "Người dùng trực tuyến:",
"kVM_TurnTimeTimer": "Lượt hết hạn sau {0} giây.",
"kVM_WaitingTurnTimer": "Chờ đến lượt sau {0} giây.",
"kVM_VoteCooldownTimer": "Vui lòng đợi {0} giây trước khi bắt đầu cuộc bỏ phiếu khác.",
"kVM_VoteForResetTitle": "Bạn có muốn thiết lập lại VM không?",
"kVM_VoteForResetTimer": "Cuộc bình chọn kết thúc sau {0} giây",
"kVMButtons_TakeTurn": "Bắt đầu",
"kVMButtons_EndTurn": "Kết thúc",
"kVMButtons_ChangeUsername": "Chỉnh sửa tên người dùng",
"kVMButtons_Keyboard": "Bàn phím",
"KVMButtons_CtrlAltDel": "Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "Bầu phiếu thiết lập lại",
"kVMButtons_Screenshot": "Chụp ảnh màn hình",
"kQEMUMonitor": "Màn hình stdio (QEMU)",
"kAdminVMButtons_PassVote": "Vượt qua bình chọn",
"kAdminVMButtons_CancelVote": "Hủy bỏ phiếu",
"kAdminVMButtons_Restore": "Khôi phục",
"kAdminVMButtons_Reboot": "Khởi động lại",
"kAdminVMButtons_ClearTurnQueue": "Xóa hàng đợi rẽ",
"kAdminVMButtons_BypassTurn": "Bỏ qua lượt",
"kAdminVMButtons_IndefiniteTurn": "lượt không xác định",
"kAdminVMButtons_Ban": "Cấm",
"kAdminVMButtons_Kick": "Đuổi",
"kAdminVMButtons_TempMute": "Khóa mồm tạm thời",
"kAdminVMButtons_IndefMute": "Khóa mồm không xđ",
"kAdminVMButtons_Unmute": "Hủy khóa mồm",
"kAdminVMButtons_GetIP": "Lấy IP",
"kVMPrompts_AdminChangeUsernamePrompt": "Nhập tên người dùng mới cho {0}:",
"kVMPrompts_AdminRestoreVMPrompt": "Bạn có chắc chắn muốn khôi phục VM không?",
"kVMPrompts_EnterNewUsernamePrompt": "Nhập tên người dùng mới hoặc để trống trường để được chỉ định tên người dùng khách",
"kError_UnexpectedDisconnection": "Bị ngắt kết nối tới máy chủ.",
"kError_UsernameTaken": "Tên người dùng đó đã được sử dụng",
"kError_UsernameInvalid": "Tên người dùng chỉ có thể chứa số, chữ cái, dấu cách, dấu gạch ngang, dấu gạch dưới và dấu chấm và phải có từ 3 đến 20 ký tự.",
"kError_UsernameBlacklisted": "Tên này thuộc danh sách đen.",
"kError_IncorrectPassword": "Sai mật khẩu.",
"kAccountModal_Verify": "Xác nhận Email",
"kAccountModal_AccountSettings": "Cài đặt tài khoản",
"kAccountModal_ResetPassword": "Khôi phục mật khẩu",
"kAccountModal_NewPassword": "Mật khẩu mới",
"kAccountModal_ConfirmNewPassword": "Xác nhận mật khẩu mới",
"kAccountModal_CurrentPassword": "Mật khẩu hiện tại",
"kAccountModal_ConfirmPassword": "Xác nhận mật khẩu",
"kMissingCaptcha": "Vui lòng điền hình ảnh xác thực.",
"kPasswordsMustMatch": "Mật khẩu phải trùng khớp.",
"kAccountModal_VerifyText": "Chúng tôi đã gửi E-mail tới {0}. Để xác minh tài khoản của bạn, vui lòng nhập mã gồm 8 chữ số từ E-Mail bên dưới.",
"kAccountModal_VerifyPasswordResetText": "Chúng tôi đã gửi E-mail tới {0}. Để đặt lại mật khẩu, vui lòng nhập mã gồm 8 chữ số từ E-Mail bên dưới.",
"kAccountModal_PasswordResetSuccess": "Mật khẩu của bạn đã được thay đổi thành công. Bây giờ bạn có thể đăng nhập bằng mật khẩu mới của mình.",
"kNotLoggedIn": "Đang không đăng nhập"
}
}

View File

@ -1,98 +0,0 @@
{
"languageName": "简体中文(中国)",
"translatedLanguageName": "Simplified Chinese (China)",
"flag": "🇨🇳",
"author": "Starmoe(辉夜星瞳)",
"stringKeys": {
"kGeneric_CollabVM": "CollabVM",
"kGeneric_Yes": "是",
"kGeneric_No": "否",
"kGeneric_Ok": "好的",
"kGeneric_Cancel": "取消",
"kGeneric_Send": "发送",
"kGeneric_Understood": "我已知晓",
"kGeneric_Username": "用户名",
"kGeneric_Password": "密码",
"kGeneric_Login": "登录",
"kGeneric_Register": "注册",
"kGeneric_EMail": "电子邮件",
"kGeneric_DateOfBirth": "出生日期",
"kGeneric_VerificationCode": "验证码",
"kGeneric_Verify": "验证",
"kGeneric_Update": "更新",
"kGeneric_Logout": "登出",
"kWelcomeModal_Header": "欢迎来到 CollabVM",
"kWelcomeModal_Body": "<p>在您的计算机之旅前, 请确认您已认真读过以下内容:</p> <h3>1. 不得破坏规则</h3> 不要使用 CollabVM 或者 CollabVM 的网络去违法美国联邦法律、纽约州的法律,或国际法。 如果 CollabVM 发现有人通过其服务实施犯罪,您将被立即禁止使用,必要时您的活动可能会被报告给当局。<br><br>根据法律规定,如果 CollabVM 发现其网络上存在或通过其网络传输儿童色情内容,必须通知执法机构。<br><br>COPPA 也会被执行请不要未满13岁的用户使用 CollabVM。<h3>2. 不要运行 DoS/DDoS 工具。</h3> 请勿使用 CollabVM 对个人、企业、公司或其他任何人进行 DoS/DDoS 攻击。<h3>3. 不发送垃圾邮件。</h3> 请勿使用此服务发送垃圾邮件或推送垃圾邮件。<h3>4. 不要滥用任何漏洞。</h3> 请勿滥用任何漏洞,此外,如果您发现有人滥用漏洞或您需要报告漏洞,请通过以下方式联系: computernewbab@gmail.com <h3>5. 不要冒充其他用户。</h3> 请勿冒充 CollabVM 的其他成员。如果被发现,你将会被暂时断开连接,必要时还会被禁言。<h3>R6。每人只能投一票。</h3> 请勿使用任何方法或工具绕过投票限制。无论如何,每人只能投一票。任何被发现这样做的人都将被禁言。<h3>7. 没有远程管理工具。</h3> 不要使用任何远程管理工具例如DarkComet、NanoCore、Anydesk、TeamViewer、Orcus 等)。<h3>8. 不得绕过 CollabNet。</h3> 不要试图绕过 CollabNet 提供的屏蔽,尤其是当它被用于破坏规则 1、规则 2 或规则 7 时。 (或运行愚蠢的过度使用的东西)。 <h3>9. 不要不断地进行破坏性行动。</h3> 任何用户都不得破坏虚拟机(使其始终无法使用)、安装/重装操作系统VM7 或 VM8 除外)或运行执行此类操作的机器人。这包括垃圾邮件式大量键盘/鼠标输入(\"kitting\")的机器人。 <h3>10. 不得使用虚拟机挖掘加密货币 </h3> 试图在虚拟机上挖掘加密货币将导致被踢,如果你继续尝试,则会被永久禁止。此外,你也不会从中赚到钱。<h3>NSFW 警告!</h3> 请注意,在我们的无政府虚拟机 (VM0b0t) 上允许出现 NSFW 内容,而且这些内容会定期被查看。此外,虽然我们努力不让非主流内容出现在主虚拟机上,但偶尔也会有人漏网。",
"kSiteButtons_Home": "主页",
"kSiteButtons_FAQ": "疑难解答",
"kSiteButtons_Rules": "规则",
"kSiteButtons_DarkMode": "深色模式",
"kSiteButtons_LightMode": "浅色模式",
"kSiteButtons_Languages": "语言",
"kVM_UsersOnlineText": "在线的用户:",
"kVM_TurnTimeTimer": "操作轮次将在 {0} 秒后失效。",
"kVM_WaitingTurnTimer": "正在等待操作,还有 {0} 秒",
"kVM_VoteCooldownTimer": "请等待 {0} 秒后再开始投票。",
"kVM_VoteForResetTitle": "您想重置虚拟机吗?",
"kVM_VoteForResetTimer": "投票在 {0} 秒后结束",
"kVMButtons_TakeTurn": "开始轮次",
"kVMButtons_EndTurn": "结束轮次",
"kVMButtons_ChangeUsername": "更改用户名",
"kVMButtons_Keyboard": "键盘",
"KVMButtons_CtrlAltDel": "发送 Ctrl+Alt+Del",
"kVMButtons_VoteForReset": "投票重置",
"kVMButtons_Screenshot": "截图",
"kQEMUMonitor": "QEMU 监视器",
"kAdminVMButtons_PassVote": "投票通过",
"kAdminVMButtons_CancelVote": "投票取消",
"kAdminVMButtons_Restore": "恢复",
"kAdminVMButtons_Reboot": "重启",
"kAdminVMButtons_ClearTurnQueue": "清楚操作请求",
"kAdminVMButtons_BypassTurn": "分流操作轮次",
"kAdminVMButtons_IndefiniteTurn": "无限操作轮次",
"kAdminVMButtons_Ban": "被封禁",
"kAdminVMButtons_Kick": "被踢出",
"kAdminVMButtons_TempMute": "临时静音",
"kAdminVMButtons_IndefMute": "永久静音",
"kAdminVMButtons_Unmute": "取消静音",
"kAdminVMButtons_GetIP": "取得 IP",
"kVMPrompts_AdminChangeUsernamePrompt": "请为 {0} 输入新的名称:",
"kVMPrompts_AdminRestoreVMPrompt": "您确定要恢复虚拟机吗?",
"kVMPrompts_EnterNewUsernamePrompt": "输入新的用户名,或留空以分配一个访客用户名",
"kError_UnexpectedDisconnection": "您已与服务器断开连接。",
"kError_UsernameTaken": "该用户名已被注册",
"kError_UsernameInvalid": "用户名只能包含数字、字母、空格、破折号、下划线和点,字符数必须在 3 到 20 之间。",
"kError_UsernameBlacklisted": "该用户名已被列入黑名单。",
"kError_IncorrectPassword": "不正确的密码。",
"kAccountModal_Verify": "验证电子邮件",
"kAccountModal_AccountSettings": "账户设置",
"kAccountModal_ResetPassword": "重置密码",
"kAccountModal_NewPassword": "新的密码",
"kAccountModal_ConfirmNewPassword": "确认新的密码",
"kAccountModal_CurrentPassword": "当前密码",
"kAccountModal_ConfirmPassword": "确认密码",
"kMissingCaptcha": "请通过人机验证。",
"kPasswordsMustMatch": "密码必须匹配。",
"kAccountModal_VerifyText": "我们已向 {0} 发送了电子邮件。要验证您的帐户,请输入下面电子邮件中的 8 位代码。",
"kAccountModal_VerifyPasswordResetText": "我们已向 {0} 发送了电子邮件。要重置密码,请输入下面电子邮件中的 8 位数字代码。",
"kAccountModal_PasswordResetSuccess": "您的密码已成功更改。您现在可以使用新密码登录。",
"kNotLoggedIn": "未登录"
}
}

View File

@ -1,111 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ES2022", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
"node_modules/@hcaptcha"
], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 10 KiB

31
webpack.config.js Normal file
View File

@ -0,0 +1,31 @@
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env'
]
]
}
}
}
]
},
mode: "production"
}

3616
yarn.lock

File diff suppressed because it is too large Load Diff