Skip to content
Snippets Groups Projects
Select Git revision
  • dd_msg_reader_use_dd_msg_area_chooser_and_area_sort_update
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

avatars.js

Blame
  • 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();