init
This commit is contained in:
commit
5fd343c87b
5 changed files with 896 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
config.js
|
259
client.js
Normal file
259
client.js
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// Small single-file CollabVM client library, in semi-modern Javascript
|
||||||
|
import * as ws from 'ws';
|
||||||
|
|
||||||
|
const guacutils = {
|
||||||
|
parse: (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
|
||||||
|
sections.push(string.slice(len + 1, pos)
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(///g, '/')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
);
|
||||||
|
|
||||||
|
if(string.slice(pos, pos + 1) === ';')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
},
|
||||||
|
|
||||||
|
encode: (cypher) =>{
|
||||||
|
let command = '';
|
||||||
|
|
||||||
|
for(var i = 0; i < cypher.length; i++) {
|
||||||
|
let current = cypher[i];
|
||||||
|
command += current.length + '.' + current;
|
||||||
|
command += ( i < cypher.length - 1 ? ',' : ';');
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectionState = Object.freeze({
|
||||||
|
CLOSED: 0,
|
||||||
|
CONNECTING: 1,
|
||||||
|
CONNECTED: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
// System chat messages have a nil username.
|
||||||
|
function IsSystemChatInstruction(inst) {
|
||||||
|
return inst[1] == '';
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserData {
|
||||||
|
#_name;
|
||||||
|
#_rank;
|
||||||
|
|
||||||
|
constructor(name, rank) {
|
||||||
|
this._name = name;
|
||||||
|
this._rank = rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetName() { return this._name; }
|
||||||
|
GetRank() { return this._rank; }
|
||||||
|
|
||||||
|
UpdateRank(new_rank) { this._rank = new_rank; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CollabVMClient {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._state = ConnectionState.CLOSED;
|
||||||
|
this._users = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
GetState() { return this._state; }
|
||||||
|
|
||||||
|
Connect(uri) {
|
||||||
|
this._ws = new ws.WebSocket(uri, 'guacamole', {
|
||||||
|
origin: "https://computernewb.com"
|
||||||
|
});
|
||||||
|
this._ws.onopen = this.OnWebSocketOpen.bind(this);
|
||||||
|
this._ws.onclose = this.OnWebSocketClose.bind(this);
|
||||||
|
this._ws.onerror = this.OnWebSocketError.bind(this)
|
||||||
|
this._ws.onmessage = this.OnWebSocketMessage.bind(this);
|
||||||
|
this._state = ConnectionState.CONNECTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWebSocketOpen() {
|
||||||
|
this._state = ConnectionState.CONNECTED;
|
||||||
|
this.OnOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWebSocketClose() {
|
||||||
|
this._state = ConnectionState.CLOSED;
|
||||||
|
this.OnClose(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWebSocketError() {
|
||||||
|
// fire the close handler
|
||||||
|
this._state = ConnectionState.CLOSED;
|
||||||
|
this.OnClose(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Close() {
|
||||||
|
this._state = ConnectionState.CLOSED;
|
||||||
|
this._ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWebSocketMessage(ev) {
|
||||||
|
// cvm server should never send binary data
|
||||||
|
if(typeof(ev.data) !== "string")
|
||||||
|
return;
|
||||||
|
|
||||||
|
let message = guacutils.parse(ev.data);
|
||||||
|
if(message.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Hardcoded, we need to keep this to be alive
|
||||||
|
if(message[0] === "nop") {
|
||||||
|
this.SendGuacamoleMessage("nop");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if(message[0] === "chat") {
|
||||||
|
|
||||||
|
// console.log(`FUCK (${message.length}) ${message[1]} ${message[2]}`)
|
||||||
|
//}
|
||||||
|
|
||||||
|
if(message[0] === "chat" && message.length === 3 && !IsSystemChatInstruction(message)) {
|
||||||
|
if(message[1] != this._username) {
|
||||||
|
//console.log(`FUCK 2 (${message.length}) ${message[1]} ${message[2]}`)
|
||||||
|
this.OnChat(message[1], message[2]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message[0] === "adduser") {
|
||||||
|
this.OnAddUser(message.slice(2), parseInt(message[1]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message[0] === "remuser")
|
||||||
|
this.OnRemUser(message.slice(2), parseInt(message[1]));
|
||||||
|
|
||||||
|
// Handle renames
|
||||||
|
if(message[0] === "rename") {
|
||||||
|
if(message.length === 5) {
|
||||||
|
if(message[1] == '1') {
|
||||||
|
this._username = message[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnGuacamoleMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnAddUser(users, count) {
|
||||||
|
//console.log(users);
|
||||||
|
for(var i = 0; i < count * 2; i += 2) {
|
||||||
|
let name = users[i];
|
||||||
|
let rank = users[i + 1];
|
||||||
|
|
||||||
|
//console.log(`[${this.GetVM()}] user ${name} rank ${rank}`)
|
||||||
|
|
||||||
|
let existingUser = this._users.find(elem => elem.GetName() == name);
|
||||||
|
if(existingUser === undefined) {
|
||||||
|
//console.log(`[${this.GetVM()}] New user ${name} rank ${rank}`)
|
||||||
|
this._users.push(new UserData(name, rank));
|
||||||
|
} else {
|
||||||
|
// Handle admin/mod rank update
|
||||||
|
if(existingUser.GetRank() != rank) {
|
||||||
|
//console.log(`[${this.GetVM()}] updating ${name} to rank ${rank}`)
|
||||||
|
existingUser.UpdateRank(rank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.OnAddUser_Bot(this.GetUserCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
OnRemUser(users, count) {
|
||||||
|
for(var i = 0; i < count; i++) {
|
||||||
|
let saveUserTemp = this.GetUser(users[i]);
|
||||||
|
this._users = this._users.filter(user => user.GetName() != users[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnRemUser_Bot(this.GetUserCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This subtracts bots, including ourselves
|
||||||
|
GetUserCount() {
|
||||||
|
const KnownBots = [
|
||||||
|
this.GetUsername(),
|
||||||
|
//"General Darian"
|
||||||
|
// logger bots
|
||||||
|
"Specialized Egg",
|
||||||
|
"Emperor Kevin"
|
||||||
|
];
|
||||||
|
|
||||||
|
var len = this._users.length;
|
||||||
|
|
||||||
|
// subtract known bots
|
||||||
|
for(var i = len - 1; i != 0; --i) {
|
||||||
|
// ?
|
||||||
|
if(this._users[i] === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = this._users[i].GetName();
|
||||||
|
if(KnownBots.find(elem => name == elem) !== undefined) {
|
||||||
|
//console.log("found blacklisted username", name)
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GetUserCountFull() {
|
||||||
|
return this._users.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetUsers() {
|
||||||
|
this._users;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetUser(username) {
|
||||||
|
let existingUser = this._users.find(elem => elem.GetName() == username);
|
||||||
|
|
||||||
|
// Apparently this can fail somehow..?
|
||||||
|
if(existingUser === undefined)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return existingUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetUsername() { return this._username; }
|
||||||
|
|
||||||
|
Rename(name) {
|
||||||
|
this._username = name;
|
||||||
|
this.SendGuacamoleMessage("rename", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetVM() { return this._vm; }
|
||||||
|
|
||||||
|
ConnectToVM(vm) {
|
||||||
|
this._vm = vm;
|
||||||
|
this.SendGuacamoleMessage("connect", vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
SendGuacamoleMessage() {
|
||||||
|
if(this._state !== ConnectionState.CONNECTED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._ws.send(guacutils.encode(Array.prototype.slice.call(arguments)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
collabvm-anyosbot.service
Normal file
38
collabvm-anyosbot.service
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
[Unit]
|
||||||
|
Description=CollabVM AnyOS bot
|
||||||
|
|
||||||
|
Wants=collabvmts@vm7.service
|
||||||
|
#Wants=collabvm@vm8.service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=collabvm
|
||||||
|
Group=collabvm
|
||||||
|
Type=simple
|
||||||
|
|
||||||
|
|
||||||
|
WorkingDirectory=/srv/collabvm/anyos-bot-new-new
|
||||||
|
ExecStart=/bin/node index.js --max-old-space-size=1024 --use-largepages=on
|
||||||
|
|
||||||
|
# Hardening
|
||||||
|
PrivateTmp=yes
|
||||||
|
NoNewPrivileges=true
|
||||||
|
RestrictNamespaces=uts ipc pid user cgroup
|
||||||
|
|
||||||
|
# bleh
|
||||||
|
CPUQuota=50%
|
||||||
|
MemoryHigh=512M
|
||||||
|
MemoryMax=1G
|
||||||
|
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
|
||||||
|
# avoids funny business
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
583
index.js
Normal file
583
index.js
Normal file
|
@ -0,0 +1,583 @@
|
||||||
|
import CollabVMClient from './client.js';
|
||||||
|
import {BANNED_ISO, ISO_DIRECTORIES, INSTALLBOT_VMS, BOT_PREFIX, ADMIN_TOKEN, kGeneralLimitBaseSeconds, kRebootLimitBaseSeconds} from './config.js';
|
||||||
|
|
||||||
|
function Log() {
|
||||||
|
// console.log(`[AnyOSBot] [${new Date()}]`, [...arguments].join(' '))
|
||||||
|
console.log('[AnyOSBot]', [...arguments].join(' '))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// you people SUCK man (dynamic edition, with less bugs!)
|
||||||
|
// and it actually probably works without hanging or negative seconds this time.
|
||||||
|
class RateLimit {
|
||||||
|
constructor(time, factor, ident) {
|
||||||
|
this._ident = ident;
|
||||||
|
this._timeBase = time;
|
||||||
|
this._msUntil = 0;
|
||||||
|
this._n = 1;
|
||||||
|
this._factor = factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetTime() {
|
||||||
|
return this._msUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsLimited() {
|
||||||
|
return this._limited;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMs() {
|
||||||
|
let ret = Math.floor(
|
||||||
|
((this._n * (1/5)) * this._timeBase) * this._factor
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure it's at least time base
|
||||||
|
if(ret < 1000)
|
||||||
|
ret = this._timeBase;
|
||||||
|
|
||||||
|
// Clean out fractional milliseconds
|
||||||
|
if((ret % 1000) != 0)
|
||||||
|
ret -= (ret % 1000);
|
||||||
|
|
||||||
|
|
||||||
|
//Log(`Debug: Ratelimit returns ${ret} (where N = ${this._n})`);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetUserCount(count) {
|
||||||
|
if(count == 0)
|
||||||
|
count = 1;
|
||||||
|
this._n = count;
|
||||||
|
|
||||||
|
//Log(`Debug: Ratelimit \"${this._ident}\" count set to ${count}, algo says time will be: ${this.GetMs()} (${this.GetMs() / 1000} seconds)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
StartLimit() {
|
||||||
|
// TODO: this might work for the dyna ratelimit?
|
||||||
|
if(this._limited)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
self._msUntil = this.GetMs();
|
||||||
|
Log(`Ratelimit \"${this._ident}\" started, will be done in ${self._msUntil} ms (${self._msUntil / 1000} seconds)`);
|
||||||
|
self._limited = true;
|
||||||
|
|
||||||
|
this._siHandle = setInterval(() => {
|
||||||
|
self._msUntil -= 1000;
|
||||||
|
if(self._msUntil <= 0) {
|
||||||
|
Log(`Ratelimit \"${self._ident}\" is done.`)
|
||||||
|
clearInterval(self._siHandle);
|
||||||
|
self._limited = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ToString() {
|
||||||
|
const time = Math.floor(this.GetTime() / 1000);
|
||||||
|
let second_or_seconds = () => {
|
||||||
|
if(time > 1 || time === 0)
|
||||||
|
return "seconds";
|
||||||
|
return "second";
|
||||||
|
};
|
||||||
|
return `${time} ${second_or_seconds()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HelperBot extends CollabVMClient {
|
||||||
|
constructor(wsUri, vmId, ide2, floppy) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._wsUri = wsUri;
|
||||||
|
this._vmId = vmId;
|
||||||
|
this._ide2 = ide2;
|
||||||
|
this._hasFloppy = floppy;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DoConn() {
|
||||||
|
Log(`[${this._vmId}]`, `Connecting to ${this._wsUri}`);
|
||||||
|
this.Connect(this._wsUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnOpen() {
|
||||||
|
Log(`[${this._vmId}]`, `Bot connected to CollabVM server`);
|
||||||
|
this.CreateRateLimits();
|
||||||
|
|
||||||
|
// This dummy rename is required for some dumb reason..
|
||||||
|
this.Rename("fucker google");
|
||||||
|
this.ConnectToVM(this._vmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnClose(ev) {
|
||||||
|
//console.log(arguments)
|
||||||
|
// reconnect lol
|
||||||
|
|
||||||
|
/* the right way doesnt work thanks to something
|
||||||
|
Log(`[${this._vmId}]`, `Connection closed, reconnecting in 5 seconds`);
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
setTimeout(() => {
|
||||||
|
Log(`[${this._vmId}]`, `Reconnecting now`);
|
||||||
|
self.DoConn();
|
||||||
|
}, 1000 * 5)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
Log(`[${this._vmId}]`, `Connection closed, exiting process`);
|
||||||
|
process.exit(0);
|
||||||
|
|
||||||
|
// The bot should probably give up after some attempts
|
||||||
|
}
|
||||||
|
|
||||||
|
Chat(message) {
|
||||||
|
this.SendGuacamoleMessage("chat", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OnAddUser_Bot(count) {
|
||||||
|
this.GeneralCmdLimit.SetUserCount(count);
|
||||||
|
this.RebootLimit.SetUserCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnRemUser_Bot(count) {
|
||||||
|
this.GeneralCmdLimit.SetUserCount(count);
|
||||||
|
this.RebootLimit.SetUserCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserCanBypass(username) {
|
||||||
|
let existingUser = this.GetUser(username);
|
||||||
|
|
||||||
|
// Apparently this can fail somehow..?
|
||||||
|
if(existingUser == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let rank = existingUser.GetRank();
|
||||||
|
return rank == 2 || rank == 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SendMonitorCommand(cmd) {
|
||||||
|
this.SendGuacamoleMessage("admin", "5", this.GetVM(), cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcatPath(isodir, otherPath) {
|
||||||
|
let isopath = ISO_DIRECTORIES[isodir];
|
||||||
|
if (isopath === undefined) {
|
||||||
|
// imo crashing and being restarted by systemd is better than not knowing why shit was coming out as undefined
|
||||||
|
// gotta love javascript
|
||||||
|
throw new Error(`Undefined iso directory ${isodir}`);
|
||||||
|
}
|
||||||
|
return `${isopath}/${otherPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QEMU Abstractions
|
||||||
|
|
||||||
|
QemuEjectDevice(devname) {
|
||||||
|
this.SendMonitorCommand(`eject ${devname}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
QemuChangeDevice(devname, source, opts) {
|
||||||
|
this.SendMonitorCommand(`change ${devname} "${source}" ${opts}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
QemuEjectCd() {
|
||||||
|
if(this._ide2)
|
||||||
|
this.QemuEjectDevice("ide2-cd0");
|
||||||
|
else
|
||||||
|
this.QemuEjectDevice("vm.cd");
|
||||||
|
}
|
||||||
|
|
||||||
|
QemuEjectFloppy() {
|
||||||
|
if(this._hasFloppy)
|
||||||
|
this.QemuEjectDevice("vm.floppy");
|
||||||
|
}
|
||||||
|
|
||||||
|
QemuChangeCd(source, opts) {
|
||||||
|
if(this._ide2)
|
||||||
|
this.QemuChangeDevice("ide2-cd0", source, opts);
|
||||||
|
else
|
||||||
|
this.QemuChangeDevice("vm.cd", source, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
QemuChangeFloppy(source) {
|
||||||
|
if(this._hasFloppy)
|
||||||
|
this.QemuChangeDevice("vm.floppy" , source, "raw read-only");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OnChat(username, message) {
|
||||||
|
//console.log(`${username}> ${message}`);
|
||||||
|
|
||||||
|
if(username == this.GetUsername())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(message[0] === BOT_PREFIX) {
|
||||||
|
this.HandleCommands(username, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleCommands(username, message) {
|
||||||
|
{
|
||||||
|
let user = this.GetUser(username);
|
||||||
|
if(user == null) {
|
||||||
|
Log(`[${this._vmId}]`, `I don't know about user \"${username}\" `);
|
||||||
|
process.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should disallow unregistered users,
|
||||||
|
// please don't fuck the rank up to where I can't do this
|
||||||
|
if(!(user.GetRank() >= 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Little code fragment to make rate limiting
|
||||||
|
// more portable.
|
||||||
|
const DoLimit = (limit) => {
|
||||||
|
//console.log(`[AnyOSBot] [${this._vmId}] ${this.GetUserCount()} users online (${this.GetUserCountFull()} actual)`)
|
||||||
|
|
||||||
|
|
||||||
|
//if(this._vmId !== 'vm0b0t') {
|
||||||
|
//
|
||||||
|
if(limit.IsLimited() && !this.UserCanBypass(username)) {
|
||||||
|
this.Chat(`You may not use commands yet. Please wait ${limit.ToString()}.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//console.log(`[AnyOSBot] [${this._vmId}] ${new Date()} ${username} executed command ${command}`)
|
||||||
|
|
||||||
|
//Log(`[${this._vmId}]`, `${username} executed \"!${command}\"`);
|
||||||
|
Log(`[${this._vmId}]`, `${username} executed \"${message}\"`);
|
||||||
|
|
||||||
|
if(!this.UserCanBypass(username)) {
|
||||||
|
limit.StartLimit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a help HTML string for the help
|
||||||
|
const generateHelp = (arr) => {
|
||||||
|
let str = "<h4>AnyOSInstallBot Help:</h4><ul>";
|
||||||
|
for(var cmd of arr) {
|
||||||
|
// remove commands which depend on the floppy on a VM without floppy
|
||||||
|
if(cmd.usesFloppy && !this._hasFloppy)
|
||||||
|
continue;
|
||||||
|
str += `<li><b>${BOT_PREFIX}${cmd.command}</b> - ${cmd.help}</li>`;
|
||||||
|
}
|
||||||
|
str += "</ul>";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateList = (title, arr) => {
|
||||||
|
let str = `<h4>${title}</h4><ul>`;
|
||||||
|
for(var cmd of arr) {
|
||||||
|
str += `<li>${cmd}</li>`;
|
||||||
|
}
|
||||||
|
str += "</ul>";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let command = '';
|
||||||
|
|
||||||
|
if(message.indexOf(' ') !== -1)
|
||||||
|
command = message.slice(1, message.indexOf(' '));
|
||||||
|
else
|
||||||
|
command = message.slice(1);
|
||||||
|
|
||||||
|
switch(command) {
|
||||||
|
case 'help':
|
||||||
|
if(!DoLimit(this.GeneralCmdLimit))
|
||||||
|
return;
|
||||||
|
this.SendGuacamoleMessage("admin", "21", generateHelp([
|
||||||
|
{ command: "certerror", help: "Provides information on the CollabNet SSL certificate" },
|
||||||
|
{ command: "network", help: "Provides network driver information" },
|
||||||
|
{ command: "cd [path]", help: "Change CD image to Computernewb ISO image (see computernewb.com/isos)" },
|
||||||
|
{ command: "lilycd [path]", help: "Change CD image to Lily ISO image (see computernewb.com/~lily/ISOs)" },
|
||||||
|
{ command: "crustycd [path]", help: "Change CD image to CrustyWindows ISO image (see crustywindo.ws/collection)" },
|
||||||
|
{ command: "flp [path]", help: "Change Floppy image to Dartz IMG/flp image", usesFloppy: true },
|
||||||
|
{ command: "lilyflp [path]", help: "Change Floppy image to Lily IMG/flp image", usesFloppy: true },
|
||||||
|
{ command: "httpcd [URL]", help: "Change CD image to HTTP server ISO file. Whitelisted domains only (see computernewb.com/CHOCOLATEMAN/domains.txt for a list)" },
|
||||||
|
{ command: "eject [cd/flp]", help: "Ejects media from the specified drive." },
|
||||||
|
{ command: "reboot", help: "Reboot the VM. Has a larger cooldown, so don't be a retard with it." },
|
||||||
|
{ command: "bootset [string of c,a,d,n]", help: "Change the VM's boot order" },
|
||||||
|
]));
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'network':
|
||||||
|
if(!DoLimit(this.GeneralCmdLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch(this._vmId) {
|
||||||
|
case "vm7":
|
||||||
|
this.SendGuacamoleMessage("admin", "21", generateList("VirtIO Network Setup Instructions (Windows):", [
|
||||||
|
"Run \"!cd driver/virtio-win-0.1.225.iso\" to insert the VirtIO driver CD into the VM.",
|
||||||
|
"Run \"devmgmt.msc\" in the VM, look for the \"Ethernet Controller\" device, and update its driver.",
|
||||||
|
"When asked for a path, put in D:\\NetKVM\\{OS}\\x86 (or on a 64-bit OS, D:\\NetKVM\\{OS}\\amd64)",
|
||||||
|
"After that, see the !certerror command."
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
case "vm8":
|
||||||
|
this.SendGuacamoleMessage("admin", "21", generateList("RTL8139 Network Setup Instructions (Windows):", [
|
||||||
|
"Run \"!cd Driver/VM8 Network Drivers.iso\" to insert the RTL8139 network driver CD into the VM.",
|
||||||
|
"Run \"devmgmt.msc\" in the VM, look for the \"Ethernet Controller\" device, and update its driver.",
|
||||||
|
"When asked for a path, put in D:\\(name of OS)\\. For example, on Windows NT 4, put in D:\\WINNT4\\ (click browse and select the OS if you can't figure it out)",
|
||||||
|
"After that, you should be online. This step should NOT be required on Windows 2000, XP, Vista, or 7. This step should not be required on ANY Linux distro unless its really shit."
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.chat("This VM should already have internet.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'certerror':
|
||||||
|
if(!DoLimit(this.GeneralCmdLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//if(this._vmId == 'vm0b0t') {
|
||||||
|
// this.SendGuacamoleMessage("admin", "21", "<h1>fuck off retard why are you trying to get collabnet on vm0</h1>");
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
this.SendGuacamoleMessage("admin", "21", generateList("CollabNet Setup Instructions:", [
|
||||||
|
"Follow the instructions on http://192.168.1.1 to install the certificate."
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
// this is gigantic holy fuck
|
||||||
|
case 'cd':
|
||||||
|
case 'lilycd':
|
||||||
|
case 'crustycd':
|
||||||
|
case 'flp':
|
||||||
|
case 'lilyflp':
|
||||||
|
case 'httpcd':
|
||||||
|
case 'httpflp': {
|
||||||
|
if(!DoLimit(this.GeneralCmdLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let arg = message.slice(message.indexOf(' ')+1);
|
||||||
|
let ext = arg.slice(arg.lastIndexOf('.') + 1);
|
||||||
|
if(arg.indexOf('..') !== -1)
|
||||||
|
return;
|
||||||
|
for (var ii = 0; ii < BANNED_ISO.length; ii++) {
|
||||||
|
if (BANNED_ISO[ii].test(arg)) {
|
||||||
|
this.Chat("That ISO is currently blacklisted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(command) {
|
||||||
|
case 'cd':
|
||||||
|
case 'lilycd':
|
||||||
|
case 'crustycd':
|
||||||
|
if(arg.indexOf('http://') !== -1 || arg.indexOf('https://') != -1) {
|
||||||
|
this.Chat("Use the http versions of these commands, if the iso is locally hosted you can try !command Path/iso.iso (case-sensitive)")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ext.toLowerCase() === "iso") {
|
||||||
|
// repetitive but whatever it works
|
||||||
|
if(command === 'lilycd')
|
||||||
|
this.QemuChangeCd(this.ConcatPath('lily', arg), "");
|
||||||
|
else if (command == 'crustycd')
|
||||||
|
this.QemuChangeCd(this.ConcatPath('crustywin', arg), "");
|
||||||
|
else if (command == 'cd')
|
||||||
|
this.QemuChangeCd(this.ConcatPath('computernewb', arg), "");
|
||||||
|
|
||||||
|
//this.QemuChangeCd(this.ConcatPath(command === 'lilycd' && 'lily' || command === "crustycd" && 'crustywin' || 'computernewb', arg), "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'flp':
|
||||||
|
case 'lilyflp':
|
||||||
|
if(arg.indexOf('http://') !== -1 || arg.indexOf('https://') != -1) {
|
||||||
|
this.Chat("Use the http versions of these commands")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(!this._hasFloppy) {
|
||||||
|
this.Chat("This VM does not have a floppy drive.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ext.toLowerCase() === "iso") {
|
||||||
|
this.Chat("dumbass, use !cd or !lilycd");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ext.toLowerCase() === "img" || ext.toLowerCase() === "ima") {
|
||||||
|
this.QemuChangeFloppy(this.ConcatPath(command === 'lilyflp' ? 'lily' : 'computernewb', arg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'httpcd':
|
||||||
|
//this.Chat("Disabled due to retards, sorry! Try !cd, !lilycd or !crustycd for some local isos.");
|
||||||
|
//return;
|
||||||
|
|
||||||
|
// whitelisted domains
|
||||||
|
const whitelist = ['http://kernel.org', 'https://kernel.org', 'http://distro.ibiblio.org',
|
||||||
|
'https://distro.ibiblio.org', 'https://dl.collabsysos.xyz', 'https://egg.l5.ca',
|
||||||
|
'https://download.manjaro.org', 'https://ubuntu.osuosl.org/', 'https://mirror.kku.ac.th/',
|
||||||
|
'https://cdimage.debian.org', 'https://archive.elijahr.dev/Games/pcgc.iso' ];
|
||||||
|
|
||||||
|
/*var is_founded = false; // this might not be elegant sorry - Hilda
|
||||||
|
for (var piss in whitelist) {
|
||||||
|
//console.log(`${arg.indexOf(whitelist[piss])}`);
|
||||||
|
if (arg.startsWith(whitelist[piss]) === true) { // no archive.org/cock.iso?ignore=validurl
|
||||||
|
is_founded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Yeah I knew it, thanks modeco! - Hilda
|
||||||
|
let is_founded = (() => whitelist.find(e => arg.startsWith(e)) !== undefined)();
|
||||||
|
if(is_founded === false)
|
||||||
|
{
|
||||||
|
//this.Chat("This is sparta!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if(arg.indexOf('~dartz/isos') !== -1 || arg.indexOf('~lily/ISOs') !== -1) {
|
||||||
|
this.Chat('Use the non-http versions of these commands for local images, please.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*if(arg.indexOf('crustywindo.ws') !== -1) { // make this an else if or bundle in previous if if latter is added back
|
||||||
|
this.Chat("Use the !crustycd command for the bootleg collection, please.");
|
||||||
|
return;
|
||||||
|
}*/ // wait im retarded whitelist
|
||||||
|
|
||||||
|
if(ext.toLowerCase() === "iso")
|
||||||
|
this.QemuChangeCd(arg, "");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'httpflp':
|
||||||
|
//this.Chat("Disabled due to retards; sorry!");
|
||||||
|
//return;
|
||||||
|
|
||||||
|
if(arg.indexOf('~dartz/isos') !== -1 || arg.indexOf('~lily/ISOs') !== -1) {
|
||||||
|
this.Chat('Use the non-http versions of these commands for local images, please.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this._hasFloppy) {
|
||||||
|
this.Chat("This VM does not have a floppy drive.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ext.toLowerCase() == "img" || ext.toLowerCase() == "ima")
|
||||||
|
this.QemuChangeFloppy(arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// bleh
|
||||||
|
this.Chat("Tried to put media into specified device.");
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'eject': {
|
||||||
|
if(!DoLimit(this.GeneralCmdLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let arg = message.slice(message.indexOf(' ')+1);
|
||||||
|
|
||||||
|
//this.Chat("sorry, severe autism not allowed right now");
|
||||||
|
//return;
|
||||||
|
|
||||||
|
switch(arg) {
|
||||||
|
case 'cd':
|
||||||
|
this.QemuEjectCd();
|
||||||
|
break;
|
||||||
|
case 'flp':
|
||||||
|
this.QemuEjectFloppy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'reboot':
|
||||||
|
if(!DoLimit(this.RebootLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// this.Chat("hold on fellas");
|
||||||
|
// return;
|
||||||
|
this.SendMonitorCommand("system_reset");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bootset':
|
||||||
|
if(!DoLimit(this.GeneralCmdLimit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//this.Chat("sorry, severe autism not allowed right now");
|
||||||
|
//return;
|
||||||
|
this.SendMonitorCommand(`boot_set ${message.slice(message.indexOf(' ')+1)}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.Chat(`Unknown command ${command}. See !help?`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateRateLimits() {
|
||||||
|
// Instanciate rate limit classes
|
||||||
|
// TODO: Should these be shared? idk
|
||||||
|
|
||||||
|
//if (vmId == 'vm0b0t')
|
||||||
|
// this.GeneralCmdLimit = new RateLimit(25 * 1000);
|
||||||
|
//else
|
||||||
|
this.GeneralCmdLimit = new RateLimit(kGeneralLimitBaseSeconds * 1000, 2, `General commands/${this._vmId}`);
|
||||||
|
|
||||||
|
//this.EjectLimit = new RateLimit(30 * 1000);
|
||||||
|
this.RebootLimit = new RateLimit(kRebootLimitBaseSeconds * 1000, 3, `!reboot/${this._vmId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnGuacamoleMessage(message) {
|
||||||
|
if(message[0] == "connect") {
|
||||||
|
if(message[1] == '0') {
|
||||||
|
Log(`[${this._vmId}]`, `Failed to connect to VM`);
|
||||||
|
this.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(message);
|
||||||
|
Log(`[${this._vmId}]`, `Connected to VM`);
|
||||||
|
|
||||||
|
|
||||||
|
// I'm fucking lazy
|
||||||
|
this.SendGuacamoleMessage("login", ADMIN_TOKEN);
|
||||||
|
this.CreateRateLimits();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.admin,1.2,44.unknown command: 'aaaaa' ;
|
||||||
|
if(message[0] == "admin" && message[1] == "2") {
|
||||||
|
if(message[2].indexOf("Could not open") !== -1) {
|
||||||
|
this.Chat("Could not open selected CD or floppy image. Please check the filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(message[2].indexOf("boot device list now set to") !== -1) {
|
||||||
|
this.Chat("Successfully set boot order.");
|
||||||
|
}
|
||||||
|
//console.log(`Admin response: ${message[2]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for(let vm of INSTALLBOT_VMS) {
|
||||||
|
// initalize this bot instance
|
||||||
|
new HelperBot(vm.uri, vm.id, vm.usesIde2, vm.hasFloppy)
|
||||||
|
.DoConn();
|
||||||
|
}
|
||||||
|
|
12
package.json
Normal file
12
package.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "AnyOSInstallBot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Helper bot to insert media into AnyOS VMs",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "Computernewb",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^8.3.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue