Skip to content

Instantly share code, notes, and snippets.

@scheibo
Last active January 26, 2024 03:01
Show Gist options
  • Save scheibo/a72ff41ce7c454ca428f7ed30a30ce44 to your computer and use it in GitHub Desktop.
Save scheibo/a72ff41ce7c454ca428f7ed30a30ce44 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
'use strict';
// !!!!!!!!!!!!!!!!!!!!!
// ! ADD ACCOUNTS HERE !
// !!!!!!!!!!!!!!!!!!!!!
const ACCOUNTS = {
// 'username': 'password',
};
const packages = [];
for (const dep of ['@pkmn/login', '@pkmn/protocol', 'ws']) {
try {
require.resolve(dep);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err;
packages.push(dep);
}
}
if (packages.length) {
require('child_process').execSync(`npm install ${packages.join(' ')} --no-audit --no-save `, {
stdio: 'inherit',
cwd: __dirname,
});
}
const https = require('https');
const {Actions} = require('@pkmn/login');
const {Protocol} = require('@pkmn/protocol');
const WebSocket = require('ws');
const server = 'sim.smogon.com';
const serverport = 8000;
function fetch(action, cookie) {
const headers = cookie ? {'Set-Cookie': cookie, ...action.headers} : action.headers;
return new Promise((resolve, reject) => {
let buf = '';
const req = https.request(action.url, {method: action.method, headers}, res => {
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
const sid = res.headers['set-cookie']?.find(c => c.startsWith('sid='));
res.on('data', d => {
buf += d;
});
res.on('end', () => resolve(buf));
});
req.on('error', reject);
req.write(action.data);
req.end();
});
}
class User {
constructor(connection) {
this.connection = connection;
}
async login(details) {
const action = Actions.login(details);
const cmd = action.onResponse(await fetch(action));
if (cmd) this.connection.send(cmd);
}
async upkeep(details, cookie) {
const action = Actions.upkeep(details);
const cmd = action.onResponse(await fetch(action, cookie));
if (cmd) this.connection.send(cmd);
}
async logout() {
if (this.username) {
const action = Actions.logout({username: this.username});
const cmd = action.onResponse(await fetch(action));
if (cmd) this.connection.send(cmd);
this.username = undefined;
}
}
}
class Connection {
open(fn) {
this.ws = new WebSocket(`ws://${server}:${serverport}/showdown/websocket`);
this.ws.onmessage = ({data}) => {
fn(data);
};
this.ws.onopen = () => {
console.log(`Connected to ${this.ws.url}`);
};
this.ws.onclose = e => {
const clean = e.wasClean ? ' cleanly ' : ' ';
const reason = e.reason ? `: ${e.reason}` : '';
console.log(`Disconnected${clean}from ${this.ws.url} with ${e.code}${reason}`);
};
this.ws.onerror = e => {
const msg = e.message;
if (msg === 'TIMEOUT') return;
console.error(`Connection error${e.message ? `: ${e.message}` : ''}`);
};
}
close() {
this.ws.close();
}
send(message) {
this.ws.send(message);
}
}
(async () => {
const connection = new Connection();
try {
const done = new Promise(end => {
connection.open(async data => {
for (const username in ACCOUNTS) {
const wait = new Promise(async resolve => {
const user = new User(connection);
for (const {args} of Protocol.parse(data)) {
switch (args[0]) {
case 'challstr': {
const challstr = args[1];
const password = ACCOUNTS[username];
user.login({username, challstr, password}).catch(err => {
console.error(err);
connection.close();
process.exit(1);
});
break;
}
case 'updateuser': {
const name = args[1].trim();
if (name.startsWith('Guest')) continue;
console.log(`Logged in to '${name.trim()}'`);
try {
await user.logout();
console.log(`Logged out of '${username.trim()}'`);
} finally {
resolve();
}
break;
}
}
}
});
await wait;
}
end();
});
});
await done;
} finally {
connection.close();
}
})().catch(err => {
console.error(err);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment