littlebigcomputer/chatbot/bot.js
2023-10-30 17:50:05 +01:00

191 lines
4.6 KiB
JavaScript

import tmi from 'tmi.js';
import { WebSocketServer } from 'ws';
import * as secrets from './secret.js';
const CHECKSUM_SEND_INTERVAL = 8;
// Define configuration options
const opts = {
options: { debug: false },
identity: {
username: secrets.USERNAME,
password: secrets.ACCESS_TOKEN,
},
channels: [secrets.CHANNEL_NAME]
};
// Create a client with our options
const client = new tmi.Client(opts);
client.connect().catch(console.error);
client.on('connected', onConnectedHandler);
function onConnectedHandler(addr, port) {
console.log(`* Connected to ${addr}:${port}`);
listen();
}
function listen() {
const wss = new WebSocketServer({ port: 8090 });
wss.on('connection', (ws) => {
let cancelRequested = false;
ws.on('message', (message) => {
const payload = JSON.parse(message);
if (payload.type === 'data') {
const data = JSON.parse(payload.data);
console.log(`sending ${data.length} values with inveral ${payload.speed}ms...`);
write(data, (update) => {
ws.send(update);
}, () => cancelRequested, payload.speed);
} else if (payload.type === 'cancel') {
cancelRequested = true;
console.log('cancel requested');
}
});
});
}
// Commands:
// 0: reset checksum
// 1: address low
// 2: address high
// 3: value low; write; increment address
// 4: value high
// 5: checksum low; compare
// 6: checksum high
// 7: unlock address
async function write(data, progressCallback, isCancelRequested, waitIntervalMs) {
let previousAddress = null;
let previousVhValue = null;
let checksum = 0;
// Reset checksum
send(0, 0);
await wait(waitIntervalMs);
for (const i in data) {
const remaining = (data.length - i) + (data.length / CHECKSUM_SEND_INTERVAL);
const eta = remaining * (waitIntervalMs * 2) / 1000;
progressCallback(JSON.stringify({
status: 'sending',
message: `${i}/${data.length} (${(i / data.length * 100).toFixed(1)}%) ETA: ${formatDuration(eta)}`,
checksum,
}));
if (i % CHECKSUM_SEND_INTERVAL === 0 && i != 0) {
await sendChecksum(checksum, waitIntervalMs);
}
const [address, value] = data[i];
checksum += address + value;
checksum &= 0xffffff;
const [al, ah] = [address & 0xfff, address >> 12];
const [vl, vh] = [value & 0xfff, value >> 12];
if (address !== previousAddress + 1) {
send(0, 7); // unlock address
await wait(waitIntervalMs);
send(al, 1);
await wait(waitIntervalMs);
send(ah, 2);
await wait(waitIntervalMs);
}
previousAddress = address;
if (vh !== previousVhValue) {
send(vh, 4);
await wait(waitIntervalMs);
}
previousVhValue = vh;
send(vl, 3);
await wait(waitIntervalMs);
if (isCancelRequested()) {
console.log('cancelled');
progressCallback(JSON.stringify({
status: 'cancelled',
message: `Cancelled`,
checksum,
}));
return;
}
}
await sendChecksum(checksum, waitIntervalMs);
progressCallback(JSON.stringify({
status: 'complete',
message: `Sent ${data.length} values`,
checksum,
}));
}
async function wait(timeMs = 1500) {
await new Promise(resolve => setTimeout(resolve, timeMs));
}
function send(value, command) {
// console.log(`sending ${command}: ${value} `);
say(encode(value, command));
}
async function sendChecksum(checksum, waitIntervalMs) {
const [cl, ch] = [checksum & 0xfff, checksum >> 12];
send(ch, 6);
await wait(waitIntervalMs);
send(cl, 5);
await wait(waitIntervalMs);
}
function say(message) {
client.say(secrets.CHANNEL_NAME, message);
}
function formatDuration(seconds) {
seconds = Math.ceil(seconds);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes === 0) {
return `${remainingSeconds}s`;
} else {
return `${minutes}m${remainingSeconds}s`;
}
}
// 1000: al
// 1001: ah
// 1010: vl
// 1011: vh
function encode(value, command) {
const aBitCount = 4;
const bBitCount = 4;
const cBitCount = 4;
const dBitCount = 4;
const valueBitString = value.toString(2).padStart(12, '0');
const commandBitString = command.toString(2).padStart(4, '0');
const bitString = commandBitString + valueBitString;
let index = 0;
const a = parseInt(bitString.slice(0, aBitCount), 2);
index += aBitCount;
const b = parseInt(bitString.slice(index, index + bBitCount), 2);
index += bBitCount;
const c = parseInt(bitString.slice(index, index + cBitCount), 2);
index += cBitCount;
const d = parseInt(bitString.slice(index, index + dBitCount), 2);
index += dBitCount;
const maxTotal = 2 ** aBitCount + 2 ** bBitCount + 2 ** cBitCount + 2 ** dBitCount;
const e = maxTotal - (a + b + c + d);
const values = [...Array(a).fill('a'), ...Array(b).fill('b'), ...Array(c).fill('c'), ...Array(d).fill('d'), ...Array(e).fill('e')];
return values.join(' ');
}