Select Git revision
-
Rob Swindell authoredRob Swindell authored
avatars.js 24.35 KiB
// $Id: avatars.js,v 1.39 2019/08/19 03:01:58 rswindell Exp $
var REVISION = "$Revision: 1.39 $".split(' ')[1];
load('sbbsdefs.js');
load("lz-string.js");
var lib = load({}, 'avatar_lib.js');
var SAUCE = load({}, 'sauce_lib.js');
var Graphic = load({}, 'graphic.js');
var fidoaddr = load({}, 'fidoaddr.js');
var export_freq = 7; // minimum days between exports
var verbosity = 0;
var options=load({}, "modopts.js", "avatars");
if(!options)
options = {};
if(!options.sub)
options.sub = load({}, "syncdata.js").find();
if(options && options.export_freq > 0)
export_freq = options.export_freq;
const user_avatars = 'SBBS User Avatars';
const shared_avatars = 'SBBS Shared Avatars';
const EXCLUDE_FILES = /\.\d+\.bin$/; // Don't include Pablodraw backups in shared collections
function parse_user_msg(text)
{
var i;
var json_begin;
var json_end;
// Terminate at tear-line
text=text.split("\r\n");
for(i=0; i<text.length; i++) {
if(text[i]=="---" || text[i].substring(0,4)=="--- ")
break;
}
text.length=i;
// Parse JSON block
for(i=0; i<text.length; i++) {
if(text[i].toLowerCase()=="json-begin")
json_begin=i+1;
else if(text[i].toLowerCase()=="json-end")
json_end=i;
}
if(json_begin && json_end > json_begin) {
text=text.splice(json_begin, json_end-json_begin);
try {
if((obj = JSON.parse(text.join(''))) != undefined)
return obj;
} catch(e) {
alert("Error " + e + " parsing JSON");
}
return false;
}
return false;
}
function parse_file_msg(text)
{
var i;
var bin_begin;
var bin_end;
// Terminate at tear-line
text=text.split("\r\n");
for(i=0; i<text.length; i++) {
if(text[i]=="---" || text[i].substring(0,4)=="--- ")
break;
}
text.length=i;
// Parse JSON block
for(i=0; i<text.length; i++) {
if(text[i].toLowerCase()=="bin-lz-begin")
bin_begin=i+1;
else if(text[i].toLowerCase()=="bin-lz-end")
bin_end=i;
}
if(bin_begin && bin_end > bin_begin) {
text = text.splice(bin_begin, bin_end-bin_begin);
return LZString.decompressFromBase64(text.join('').replace(/\s+/g, ''));
}
return false;
}
function find_name(objs, name)
{
for(var i=0; i < objs.length; i++)
if(objs[i].name.toLowerCase() == name.toLowerCase())
return i;
return -1;
}
function import_netuser_list(hdr, list)
{
var objs = [];
var file = new File(lib.netuser_fname(hdr.from_net_addr));
if(file.open("r")) {
objs = file.iniGetAllObjects();
file.close();
} else if(file_exists(file.name)) {
alert("Error " + file.error + " opening " + file.name);
return false;
}
for(var i in list) {
for(var u in list[i]) {
if(system.findstr(system.ctrl_dir + "twitlist.cfg", list[i][u])) {
alert("Ignoring twit-listed user: " + list[i][u]);
continue;
}
var index = find_name(objs, list[i][u]);
if(index >= 0) {
if(verbosity)
printf("%s = %s\r\n", objs[index].name, i);
if(i == "disabled")
objs.splice(index, 1); // Remove rather than just mark as disabled
else if(objs[index].data != i) {
objs[index].data = i;
objs[index].updated = new Date();
}
} else if(i != "disabled")
objs.push({ name: list[i][u], data: i, created: new Date() });
}
}
if(!file.open("w")) {
alert("Error opening " + file.name);
return false;
}
var result = file.iniSetAllObjects(objs);
file.writeln(format(";updated by %s at %s (%s) on %s"
, hdr.from, hdr.subject, hdr.from_net_addr, new Date()));
file.close();
if(verbosity)
printf("%s written with %u avatars\r\n", file.name, objs.length);
var bbses = new File(lib.bbsindex_fname());
if(bbses.open(bbses.exists ? 'r+':'w+')) {
bbses.iniSetValue(hdr.subject, "netaddr", hdr.from_net_addr);
bbses.iniSetValue(hdr.subject, "updated", new Date());
bbses.close();
}
return result;
}
function valid_shared_file(filename)
{
if(!file_exists(filename)) {
alert(filename + " does not exist");
return false;
}
var sauce = SAUCE.read(filename);
if(!sauce) {
alert(filename + " has no SAUCE!");
return false;
}
if(sauce.datatype != SAUCE.defs.datatype.bin
|| sauce.cols != lib.defs.width
|| sauce.filesize < lib.size
|| (sauce.filesize%lib.size) != 0) {
alert(format("%s has invalid SAUCE! (datatype=%u cols=%u size=%u)"
,filename, sauce.datatype, sauce.cols, sauce.filesize));
return false;
}
for(var i in sauce.comment) {
if(strip_ctrl(sauce.comment[i]) != sauce.comment[i]) {
alert(format("%s has invalid SAUCE comment [%u]!", filename, i));
return false;
}
}
var file = new File(filename);
if(!file.open("rb"))
return false;
var data = file.read(sauce.filesize);
file.close();
var list = data.match(new RegExp('([\x00-\xff]{1,' + lib.size + '})', 'g'));
printf("%u avatars\r\n", list.length);
// print(JSON.stringify(list, null, 4));
for(var i in list)
if(!lib.is_valid(list[i])) {
alert("Avatar " + i + " " + sauce.comment[i] + " (" + list[i].length + " bytes) is invalid");
return false;
}
return true;
}
function import_shared_file(hdr, body)
{
var data = parse_file_msg(body);
if(!data)
return false;
// If the filename (in the subject) already contains the sender's QWK-ID, skip-it
var filename = file_getname(hdr.subject);
var prefix = '';
if(!fidoaddr.is_valid(hdr.from_net_addr))
prefix = file_getname(hdr.from_net_addr) + '.';
var suffix = '.bin';
if(filename.length > prefix.length + suffix.length
&& filename.substr(0, prefix.length).toLowerCase() == prefix.toLowerCase())
filename = filename.slice(prefix.length);
filename = prefix + filename;
if(file_getext(filename).toLowerCase() != suffix)
filename += suffix;
var fullpath;
if(fidoaddr.is_valid(hdr.from_net_addr))
fullpath = format("%sfido/%s.%s", system.data_dir, fidoaddr.to_filename(hdr.from_net_addr), filename);
else
fullpath = format("%sqnet/%s", system.data_dir, filename);
var file = new File(fullpath);
if(!file.open("wb")) {
alert("ERROR " + file.error + " opening " + file.name);
return false;
}
file.write(data);
file.close();
print(file.name + " created successfully");
if(!valid_shared_file(file.name))
return false;
var new_path = format("%s%s", lib.local_library(), filename);
var result = file_copy(file.name, new_path);
if(!result)
alert("ERROR copying " + file.name + " to " + new_path);
return result;
}
function import_from_msgbase(msgbase, import_ptr, limit, all)
{
var i = 0;
var count=0;
var highest;
var users_crc=crc16_calc(user_avatars.toLowerCase());
var shared_crc=crc16_calc(shared_avatars.toLowerCase());
var ini = new File(msgbase.file + ".ini");
if(import_ptr == undefined) {
print("Opening " + ini.name);
if(ini.open("r")) {
import_ptr=ini.iniGetValue("avatars","import_ptr", 0);
ini.close();
}
}
highest=import_ptr;
log(LOG_DEBUG, "import_ptr = " + import_ptr + " last_msg = " + msgbase.last_msg);
var total_msgs = msgbase.total_msgs;
if(msgbase.last_msg >= import_ptr)
i = total_msgs - (msgbase.last_msg - import_ptr);
for(; i<total_msgs; i++) {
if(js.terminated)
break;
//print(i);
var idx = msgbase.get_msg_index(/* by_offset: */true, i);
if(!idx) {
// print("Error " + msgbase.error + " reading index of msg offset " + i);
continue;
}
if(idx.number <= import_ptr)
continue;
if(idx.number > highest)
highest = idx.number;
if(idx.to != users_crc && idx.to != shared_crc)
continue;
var hdr = msgbase.get_msg_header(/* by_offset: */true, i);
if(all != true && !hdr.from_net_type) // Skip locally posted messages
continue;
if(system.findstr(system.ctrl_dir + "twitlist.cfg", hdr.from)) {
alert("Ignoring twit-listed sender: " + hdr.from);
continue;
}
printf("Importing msg #%u from %s: ", hdr.number, hdr.from);
var success = false;
var body = msgbase.get_msg_body(/* by_offset: */true, i
,/* strip Ctrl-A */true, /* rfc822-encoded: */false, /* include tails: */false);
if(hdr.to.toLowerCase() == user_avatars.toLowerCase()) {
var l;
var avatars = parse_user_msg(body);
if(!avatars)
continue;
success = import_netuser_list(hdr, decompress_list(avatars));
} else {
// Shared avatars
success = import_shared_file(hdr, body);
}
printf("%s\r\n", success ? "success" : "FAILED");
if(success)
count++;
if(limit && count >= limit)
break;
}
if(highest != import_ptr) {
if(ini.open(file_exists(ini.name) ? 'r+':'w+')) {
print("new import_ptr = " + highest);
ini.iniSetValue("avatars","import_ptr",highest);
ini.close();
} else
print("Error opening/creating " + ini.name);
}
print("Imported " + count + " messages");
}
function decompress_list(list)
{
var new_list = [];
for(var i in list) {
if(i == "disabled") {
new_list[i] = list[i];
continue;
}
var data = LZString.decompressFromBase64(i.replace(/\s+/g, ''));
if(lib.is_valid(data))
new_list[base64_encode(data)] = list[i];
}
return new_list;
}
function export_users(msgbase, realnames, all)
{
var last_user = system.lastuser;
var list = {};
var exported = 0;
for(var n = 1; n <= last_user && !js.terminated; n++) {
if(!file_exists(lib.localuser_fname(n)))
continue;
if(!system.username(n))
continue;
var u = new User(n);
if((u.settings&USER_DELETED)
|| !u.stats.total_posts // No need to export avatars for users that have never posted
|| (u.security.restrictions&(UFLAG_P|UFLAG_N|UFLAG_Q)) // or will never post
) {
if(verbosity)
printf("User #%u hasn't or can't post, skipping\r\n", n);
continue;
}
var avatar = lib.read_localuser(n);
if(!avatar)
continue;
if(avatar.export_count == undefined)
avatar.export_count = 0;
var last_exported = 0;
if(avatar.last_exported)
last_exported = new Date(avatar.last_exported);
if(u.stats.laston_date * 1000 < last_exported) {
if(verbosity)
printf("User #%u is inactive since: %s\r\n", n,new Date(u.stats.laston_date * 1000));
continue; // Don't export avatars of inactive users
}
var updated;
if(avatar.updated)
updated = new Date(avatar.updated);
else
updated = new Date(avatar.created);
if(all == true
|| updated > last_exported
|| new Date() - last_exported >= export_freq * (24*60*60*1000)) {
if(avatar.disabled == true) {
if(!last_exported)
continue;
printf("Exporting disabled-avatar state for user #%u\r\n", n);
if(!list.disabled)
list.disabled = [];
list.disabled.push(u.alias);
if(realnames)
list.disabled.push(u.name);
} else {
if(!lib.is_enabled(avatar)) {
alert("Invalid avatar for user #" + n);
continue;
}
printf("Exporting avatar for user #%u\r\n", n);
var data = LZString.compressToBase64(base64_decode(avatar.data));
if(!list[data])
list[data] = [];
list[data].push(u.alias);
if(realnames)
list[data].push(u.name);
}
avatar.last_exported = new Date();
avatar.export_count++;
lib.write_localuser(n, avatar);
exported++;
}
}
if(!exported) {
print("No avatars to export");
return true; // Nothing to export
}
for(var i in list)
list[i].sort();
var body = "Timestamp: " + new Date() + "\r\n";
body += "json-begin\r\n";
body += lfexpand(JSON.stringify(list, null, 1)) + "\r\n";
body += "json-end\r\n";
body += "--- " + js.exec_file + " " + REVISION + "\r\n";
var result = msgbase.save_msg({ to:user_avatars, from:system.operator, subject:system.name }, body);
if(!result)
alert("MsgBase error: " + msgbase.last_error);
return result;
}
function export_file_to_msgbase(msgbase, filename)
{
var file = new File(filename);
if(!file.open("rb")) {
alert("Error " + file.error + " opening " + filename);
return false;
}
var data = file.read();
file.close();
data = LZString.compressToBase64(data);
var body = "";
body += "sauce-json-begin\r\n";
body += lfexpand(JSON.stringify(SAUCE.read(file.name), null, 1)) + "\r\n";
body += "sauce-json-end\r\n";
body += "bin-lz-begin\r\n";
body += data.match(/([\x00-\xff]{1,72})/g).join("\r\n");
body += "\r\nbin-lz-end\r\n";
body += "--- " + js.exec_file + " " + REVISION + "\r\n";
var result = msgbase.save_msg({ to:shared_avatars, from:system.operator, subject:file_getname(filename) }, body);
if(!result)
alert("MsgBase error: " + msgbase.last_error);
return result;
}
function export_file(msgbase, filename)
{
var last_exported = new Date(0);
var ini = new File(msgbase.file + ".ini");
if(ini.open("r")) {
last_exported=ini.iniGetValue("avatars", file_getname(filename), last_exported);
ini.close();
}
if(file_date(filename) < last_exported.valueOf() / 1000
&& new Date() - last_exported < export_freq * (24*60*60*1000)) {
alert("File not updated recently: " + filename);
return false;
}
var success = export_file_to_msgbase(msgbase, filename);
if(success) {
if(ini.open(file_exists(ini.name) ? 'r+':'w+')) {
ini.iniSetValue("avatars", file_getname(filename), new Date());
ini.close();
} else
alert("Error opening/creating " + ini.name);
}
return success;
}
function install()
{
if(!file_exists(lib.local_library() + "*.bin"))
return "No avatars collections (.bin files) found in " + lib.local_library();
var cnflib = load({}, "cnflib.js");
var xtrn_cnf = cnflib.read("xtrn.cnf");
if(!xtrn_cnf)
return "Failed to read xtrn.cnf";
var changed = false;
if(!xtrn_area.prog["avatchoo"]) {
printf("Adding external program: Avatar Chooser\r\n");
xtrn_cnf.xtrn.push( {
"sec": 0,
"name": "Avatar Chooser",
"code": "AVATCHOO",
"ars": "",
"run_ars": "ANSI & !GUEST & REST ! Q",
"type": 0,
"settings": 1,
"event": 3,
"cost": 0,
"cmd": "?avatar_chooser",
"clean_cmd": "",
"startup_dir": "",
"textra": 0,
"max_time": 0
});
changed = true;
}
if(!xtrn_area.event["avat-in"]) {
printf("Adding timed event: AVAT-IN\r\n");
xtrn_cnf.event.push( {
"code": "AVAT-IN",
"cmd": "?avatars import",
"days": 255,
"time": 0,
"node_num": 1,
"settings": 0,
"startup_dir": "",
"freq": 30, // interval
"mdays": 0,
"months": 0
});
changed = true;
}
if(!xtrn_area.event["avat-out"]) {
printf("Adding timed event: AVAT-OUT\r\n");
xtrn_cnf.event.push( {
"code": "AVAT-OUT",
"cmd": "?avatars export",
"days": 255,
"time": 0,
"node_num": 1,
"settings": 0,
"startup_dir": "",
"freq": 1440, // interval (once a day)
"mdays": 0,
"months": 0
});
changed = true;
}
if(changed && !cnflib.write("xtrn.cnf", undefined, xtrn_cnf))
return "Failed to write xtrn.cnf";
var ini = new File(file_cfgname(system.ctrl_dir, "modopts.ini"));
if(!ini.open(file_exists(ini.name) ? 'r+':'w+'))
return ini.name + " open error " + ini.error;
printf("Updating %s\r\n", ini.name);
ini.iniSetValue("newuser", "avatar_file", "silhouettes.bin");
ini.iniSetValue("newuser", "avatar_offset", 0);
ini.iniSetValue("logon", "set_avatar", true);
ini.close();
return true;
}
function main()
{
var optval={};
var cmd;
var i;
var offset;
var realnames = false;
var ptr;
var limit;
var all;
var share = false;
var files = [];
var usernum;
var cmds = [];
for(i in argv) {
var arg = argv[i];
var val = ""
var eq = arg.indexOf('=');
if(eq > 0) {
val = arg.substring(eq+1);
arg = arg.substring(0,eq);
}
optval[arg] = val;
switch(arg) {
case '-file':
files.push(val);
break;
case '-offset':
offset = val;
break;
case '-realnames':
realnames = true;
break;
case "-ptr":
ptr = val;
break;
case "-all":
all = true;
break;
case "-share":
share = true;
break;
case "-user":
usernum = val;
break;
case "-v":
verbosity++;
break;
case "import":
case "export":
case "dump":
case "draw":
case "show":
case "verify":
case "enable":
case "disable":
case "remove":
case "newuser":
case "install":
case "normalize":
case "count":
case "colls":
case "msg-default":
case "msg_default":
cmds.push(arg);
break;
default:
if(parseInt(arg) < 0)
limit = -parseInt(arg);
else if(arg.charAt(0) != '-')
files.push(arg);
break;
}
}
var used_dir = [ lib.local_library(), system.data_dir + 'qnet', system.data_dir + 'fido' ];
for(var i in used_dir) {
if(file_isdir(used_dir[i]))
continue;
print("Making directory: " + used_dir[i]);
if(!mkdir(used_dir[i]))
alert("Failed to make directory: " + used_dir[i]);
}
for(var i in files)
files[i] = lib.fullpath(files[i]);
if(usernum && !parseInt(usernum))
usernum = system.matchuser(usernum);
else if(!usernum && user && user.number)
usernum = user.number;
for(var c in cmds) {
var cmd = cmds[c].toLowerCase();
switch(cmd) {
case "import":
if(files.length && parseInt(optval[cmd])) {
printf("Importing %s for user #%u\r\n", files[0], optval[cmd]);
var success = lib.import_file(optval[cmd], files[0], offset);
printf("%s\r\n", success ? "Successful" : "FAILED!");
break;
}
var msgbase = new MsgBase(optval[cmd] ? optval[cmd] : options.sub);
print("Opening msgbase " + msgbase.file);
if(!msgbase.open()) {
alert("Error " + msgbase.error + " opening msgbase: " + msgbase.file);
exit(-1);
}
import_from_msgbase(msgbase, ptr, limit, all);
msgbase.close();
break;
case "export":
var msgbase = new MsgBase(optval[cmd] ? optval[cmd] : options.sub);
print("Opening msgbase " + msgbase.file);
if(!msgbase.open()) {
alert("Error " + msgbase.error + " opening msgbase: " + msgbase.file);
exit(-1);
}
var success = true;
if(!files.length) {
printf("Exporting user avatars\n");
success = export_users(msgbase, realnames, all);
}
for(var i in files) {
printf("Exporting avatar collection: %s\n", files[i]);
if(!valid_shared_file(files[i])) {
success = false;
break;
}
else {
if(export_file(msgbase, files[i]))
printf("Exported avatar collection: %s\n", files[i]);
}
}
if(share) {
var filespec = lib.local_library() + system.qwk_id + ".*.bin";
print("Exporting shared avatar collections: " + filespec);
var share_files = directory(filespec);
for(var i in share_files) {
if(share_files[i].search(EXCLUDE_FILES) >= 0) {
log(LOG_DEBUG, format("Excluding file: %s", file_getname(share_files[i])));
continue;
}
printf("Exporting: %s\r\n", file_getname(share_files[i]));
if(!valid_shared_file(share_files[i]))
continue;
if(export_file(msgbase, share_files[i]))
printf("Exported shared avatar collection: %s\n", share_files[i]);
}
}
printf("%s\r\n", success ? "Successful" : "FAILED");
break;
case "dump":
if(!usernum)
usernum = optval[cmd];
var obj = lib.read_localuser(usernum);
print(JSON.stringify(obj));
break;
case "draw": // Uses Graphic.draw()
case "show": // Uses console.write()
if(files.length) {
for(var i in files) {
for(o=offset ? offset : 0; ; o++) {
var data = lib.import_file(null, files[i], o);
if(!data)
break;
console.clear();
if(cmd == "draw") {
lib.draw_bin(data);
console.getkey();
} else {
lib.show_bin(data);
console.pause();
}
console.attributes = 7;
}
}
break;
}
if(!usernum)
usernum = optval[cmd];
console.clear();
var obj = lib[cmd](usernum);
console.getkey();
console.clear(LIGHTGRAY);
break;
case "verify":
for(var i in files) {
printf("%s: ", file_getname(files[i]));
var success = valid_shared_file(files[i]);
print(success ? "Successful" : "FAILED");
}
break;
case "remove":
if(!usernum)
usernum = optval[cmd];
if(usernum) {
printf("Removing user #%u avatar\n", usernum);
var success = lib.remove_localuser(usernum);
print(success ? "Successful" : "FAILED");
}
break;
case "enable":
case "disable":
if(!usernum)
usernum = optval[cmd];
if(usernum) {
var success = lib.enable_localuser(usernum, cmd == "enable");
print(success ? "Successful" : "FAILED");
}
break;
case "newuser":
if(!files.length)
files.push(optval[cmd]);
if(!files.length) {
alert("No file specified");
break;
}
if(!file_exists(files[0])) {
printf("File does not exist: %s\r\n", files[0]);
break;
}
printf("Importing %s for new users\r\n", files[0]);
var data = lib.import_file(null, files[0], offset);
if(!data) {
alert("Failed");
break;
}
var ini = new File(file_cfgname(system.ctrl_dir, "modopts.ini"));
if(!ini.open(file_exists(ini.name) ? 'r+':'w+')) {
alert(ini.name + " open error " + ini.error);
break;
}
var success = ini.iniSetValue("newuser", "avatar", data);
if(success)
ini.iniRemoveKey("newuser", "avatar_file");
printf("%s\r\n", success ? "Successful" : "FAILED!");
ini.close();
break;
case "msg-default":
case "msg_default":
if(!files.length)
files.push(optval[cmd]);
if(!files.length) {
alert("No file specified");
break;
}
if(!file_exists(files[0])) {
printf("File does not exist: %s\r\n", files[0]);
break;
}
printf("Importing %s sub-board default avatar\r\n", files[0]);
var data = lib.import_file(null, files[0], offset);
if(!data) {
alert("Failed");
break;
}
var ini = new File(file_cfgname(system.ctrl_dir, "modopts.ini"));
if(!ini.open(file_exists(ini.name) ? 'r+':'w+')) {
alert(ini.name + " open error " + ini.error);
break;
}
var success = ini.iniSetValue("avatars", "msg_default", data);
printf("%s\r\n", success ? "Successful" : "FAILED!");
ini.close();
break;
case "normalize":
var graphic = new Graphic(lib.defs.width, lib.defs.height);
if(files.length) {
if(!offset)
offset = 0;
var filename = lib.fullpath(files[0]);
try {
if(!graphic.load(filename, offset))
break;
} catch(e) {
alert(e);
break;
}
if(!lib.is_valid(graphic.BIN)) {
alert(filename + " is not a valid avatar");
break;
}
var file = new File(filename);
if(!file.open("r+b")) {
alert("Failed to open " + file.name);
break;
}
file.position = offset * lib.size;
file.write(graphic.normalize(optval[cmd]).BIN, lib.size);
file.close();
break;
}
if(!usernum)
break;
var avatar = lib.read_localuser(usernum);
graphic.base64_decode([avatar.data]);
lib.update_localuser(usernum, base64_encode(graphic.normalize(optval[cmd]).BIN));
break;
case "count":
if(!files.length) {
var total = 0;
files = directory(lib.local_library() + "*.bin");
for(var i in files) {
var filename = files[i];
if(filename.search(EXCLUDE_FILES) >= 0)
continue;
printf("%-32s : ", file_getname(filename));
var sauce = SAUCE.read(filename);
if(!sauce) {
printf("no SAUCE\r\n");
conintue;
}
var count = sauce.filesize / lib.size;
printf("%u\r\n", count);
total += count;
}
printf("%u total\r\n", total);
break;
}
case "colls":
if(!files.length) {
var total = 0;
files = directory(lib.local_library() + "*.bin");
for(var i in files) {
var filename = files[i];
if(filename.search(EXCLUDE_FILES) >= 0)
continue;
printf("%-32s : ", file_getname(filename));
var sauce = SAUCE.read(filename);
if(!sauce) {
printf("no SAUCE\r\n");
conintue;
}
var count = sauce.filesize / lib.size;
printf("%u\r\n", count);
total += count;
var f = new File(filename);
if(!f.open('rb')) {
alert("Error " + f.error + " opening " + f.name);
continue;
}
var buf = f.read();
f.close();
for(var a = 0; a < count; a++) {
var avatar = buf.substr(a * lib.size, lib.size);
var b64 = base64_encode(avatar);
printf("%s #%u: ", file_getname(filename), a+1);
if(sauce.comment[a])
print(sauce.comment[a]);
print(b64);
print(LZString.compressToBase64(avatar));
}
}
printf("%u total\r\n", total);
break;
}
break;
case "install":
var result = install();
printf("%s\r\n", result == true ? "Successful" : "!FAILED: " + result);
break;
}
}
}
main();