Skip to content

Instantly share code, notes, and snippets.

@miku-orb
Last active October 25, 2023 06:27
Show Gist options
  • Select an option

  • Save miku-orb/a312cc35b56284fb889dde8863474acc to your computer and use it in GitHub Desktop.

Select an option

Save miku-orb/a312cc35b56284fb889dde8863474acc to your computer and use it in GitHub Desktop.
Batch-add your library symbol to selected bibliographic records in Virtua VTLS system (more reliable than AutoIt scripts and whatever)
// Created with the following Virtua system versions (by reverse engineering the network protocol):
// Server: 16.1.SP3.HF8(163939d)-189
// Client: 16.1.SP3.HF3(16.1.SP3-8-gbdfe5ec)-189
const VIRTUA_HOST = "0.0.0.0";
const VIRTUA_PORT = 1234;
const VIRTUA_USERNAME = "";
const VIRTUA_PASSWORD = "";
const LIBRARY_SYMBOL = "TEST_123"; // !!! CHANGE TO YOURS !!!
const LIBRARY_SYMBOL_ADD = true; // adding true, removing false
const IDs_FILE = "ids.txt"; // internal Virtua VTLS id numbers to which you wanna add your library symbol, one per line
const COMPUTER_NAME = "bot";
const USER_AGENT_1 = "Virtua-Client";
const USER_AGENT_2 = "VTLS Virtua Z39.50 Client";
const USER_AGENT_VERSION = "16.1.SP3.HF3(16.1.SP3-8-gbdfe5ec)-189"; // probably better not to change this without some forethought, in case they change the protocol format...?
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
const net = require("net");
const fs = require("fs");
const IDS = fs.readFileSync(IDs_FILE, "utf-8").toString().split("\n").map(l => l.trim());
const authMsg = Buffer.from(
"\xb4" + String.fromCodePoint(34 + VIRTUA_USERNAME.length + VIRTUA_PASSWORD.length + USER_AGENT_1.length + USER_AGENT_2.length + USER_AGENT_VERSION.length)
+ `\x83\x02\x05\xe0`
+ `\x84\x03\x01\xc3\xa0`
+ `\x85\x02\x20\x00`
+ `\x86\x02\x40\x00`
+ `\xa7${String.fromCodePoint(6+VIRTUA_USERNAME.length+VIRTUA_PASSWORD.length)}\x30${String.fromCodePoint(4+VIRTUA_USERNAME.length+VIRTUA_PASSWORD.length)}`
+ `\x81${String.fromCodePoint(VIRTUA_USERNAME.length)}${VIRTUA_USERNAME}`
+ `\x82${String.fromCodePoint(VIRTUA_PASSWORD.length)}${VIRTUA_PASSWORD}`
+ `\x9f\x6e${String.fromCodePoint(USER_AGENT_1.length)}${USER_AGENT_1}`
+ `\x9f\x6f${String.fromCodePoint(USER_AGENT_2.length)}${USER_AGENT_2}`
+ `\x9f\x70${String.fromCodePoint(USER_AGENT_VERSION.length)}${USER_AGENT_VERSION}`
, "ascii");
assert(authMsg.byteLength === 2 + 34 + VIRTUA_USERNAME.length + VIRTUA_PASSWORD.length + USER_AGENT_1.length + USER_AGENT_2.length + USER_AGENT_VERSION.length);
function createSymbolPacket(id) {
let buf = Buffer.from(
`\xbf\x81\x48`
+ String.fromCodePoint(11 + 3 + LIBRARY_SYMBOL.length) // len
+ `\xa6`
+ String.fromCodePoint(9 + 3 + LIBRARY_SYMBOL.length) // len
+ `\xa0`
+ String.fromCodePoint(7 + 3 + LIBRARY_SYMBOL.length) // len
+ `\x80\x01${LIBRARY_SYMBOL_ADD ? "\x01" : "\x00"}`
+ `\x81`
+ `\x03` // len of next three
+ String.fromCodePoint(((+id) & 0xff0000) >> 16)
+ String.fromCodePoint(((+id) & 0x00ff00) >> 8)
+ String.fromCodePoint(((+id) & 0x0000ff) >> 0)
+ `\x82`
+ String.fromCodePoint(LIBRARY_SYMBOL.length) // len of symbol
+ LIBRARY_SYMBOL
, "ascii");
assert(buf.byteLength === 4 + 11 + 3 + LIBRARY_SYMBOL.length);
return buf;
}
const client = new net.Socket();
client.connect(VIRTUA_PORT, VIRTUA_HOST, function() {
console.log('Connected, authenticating... (may take several seconds)');
client.write(authMsg);
});
let di = 0;
let ndi = 0;
let started = false;
let sentOutSymbols = false;
function addSymbols() {
console.log("Gonna add symbols...");
let i = 0;
for (const id of IDS) {
if (+id) {
console.log(`[#${++i}] Adding symbol for record id ${id}...`);
client.write(createSymbolPacket(+id));
}
}
sentOutSymbols = true;
}
client.on('data', function(data) {
// hardcoded server response to auth packet (it takes several seconds to finally respond...)
if (data.toString("hex") === "bc3ca03a2838060a2a8648ce130887681803a02a30283016810101a203820101a40c060a2a8648ce130d8768182f300e810104a203820101a40402022328")
{
console.log("Authed!!!");
if (!started) {
started = true;
setTimeout(() => client.write(Buffer.from(`\xbd${String.fromCodePoint(COMPUTER_NAME.length + 3)}\x9f\x26${String.fromCodePoint(COMPUTER_NAME.length)}${COMPUTER_NAME}`, "ascii")), 500);
setTimeout(() => addSymbols(), 3000);
}
}
//console.log('Received: ' + data);
if (started && sentOutSymbols && (data.byteLength % 30) !== 0 && (data.byteLength % 11) !== 0) {
console.log(`Odd length packet (${data.byteLength} bytes)`, data.toString("hex").match(/../g).join(' '));
}
// example confirmation: bf814807a605a103800101
// example rejection: bf81481aa618a116800100a111300f06072a8648ce1304010202157c1b00
// that's a kinda simple loop and far from being perfectly valid
// but still better than the first iteration :)
let o = 0;
while (true) {
if (data.byteLength - o <= 4) break;
if (data.readUInt8(o+0) == 0xbf
&& data.readUInt8(o+1) == 0x81
&& data.readUInt8(o+2) == 0x48) {
const chunklen = data.readUInt8(o+3);
if (chunklen >= 7) {
if (data.readUInt8(o+8) == 0x80 && data.readUInt8(o+9) == 0x01) {
const added = !!data.readUInt8(o+10);
if (added) {
console.log(`Added #${++di}`);
} else {
console.log(`Not added #${++ndi}`); // cause was already added
}
}
}
o += 4 + chunklen;
continue;
}
break; // well shit
}
});
client.on('close', function() {
console.log('Connection closed');
});
// Skrypt, który wyszukuje rekordy bibliograficzne w internetowym katalogu NUKAT po polu kontrolnym 035
// a następnie wypisuje ich mapowanie do wewnętrznego ID rekordu w nukatowskim systemie Virtua.
//
// Lista owych wewnętrznych ID jest potrzebna do działania skryptu "!virtua_add_library_symbol.js".
//
// Wypisanie samych numerów ID może się odbyć poprzez np.:
// $ node nukat_035_to_001vtls.js > kontrolne_do_dopisania_mapping.txt
// <w tym momencie sprawdzić czy nie ma żadnych problemów w pliku wyszukując frazę "PROBLEM" i poprawić jeśli jest...>
// $ cat kontrolne_do_dopisania_mapping.txt | cut -d' ' -f3 | sort --numeric-sort --unique > ids.txt
const assert = require("assert");
const fs = require("fs");
const controls = fs.readFileSync("kontrolne_do_dopisania.txt", "utf-8").toString().split("\n").map(l => l.trim());
function splitToNChunks(array, n) {
let result = [];
for (let i = n; i > 0; i--) {
result.push(array.splice(0, Math.ceil(array.length / i)));
}
return result;
}
const chunks = splitToNChunks([...controls], 30);
for (const chunk of chunks) {
(async (chunk) => {
for (const control of chunk) {
try {
const html = await fetch(`http://katalog.nukat.edu.pl/search/query?match_1=PHRASE&field_1=control&term_1=${control}&theme=nukat`).then(r => r.text());
assert(html.includes("Bieżące wyszukiwanie:"));
assert(html.includes("<span>Opisy od 1 do 1 z 1</span>"));
assert(!html.includes("Brak wyników dla wyszukiwania"));
const vtls = /item\?id=chamo:([0-9]+)\&amp/.exec(html)[1];
assert(vtls && vtls.length);
console.log(`${control} => ${vtls}`);
} catch (e) {
console.log(`PROBLEM(${control}): ${e+''}`);
}
}
})(chunk);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment