updateApps

Rulesy's updateApps

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/31206/204854/updateApps.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

var modules = modules || {};

modules.updateApps = {

	name: 'Map app updater',

	description: 'Keeps map apps up-to-date automatically.',

	icon: '/gfx/icons/item_map.gif',

	pages: 'outside',

	externalApps: {
		'cartographer': {
			name: 'Cartographer',
			directoryId: 19,
			logo: '/file/35.dat',
			success: function(response) {
				return (response.status == 200);
			},
			update: function(externalAppName, extendedData) {

				var externalApp;
				var key;
				var url;
				var data;

				externalApp = modules.updateApps.externalApps[externalAppName];

				if (extendedData.hardcore) {
					externalApp.updating = false;
					externalApp.temporariyDisabled = true;
					externalApp.temporariyDisabledMessage = 'Hardcore not supported';
					modules.updateApps.updateTooltip();
					return;
				}

				url = 'http://wastelandcartographer.com/plugin';

				externalApp = modules.updateApps.externalApps[externalAppName];

				key = modules.updateApps.retrieveAppKey(externalAppName);

				if (!key) {
					externalApp.updating = false;
					externalApp.error = true;
					externalApp.errorMessage = 'Error retrieving key';
					modules.updateApps.updateTooltip();
					return;
				}

				data = 'key=' + key;
				modules.updateApps.sendUpdate(url, data, externalAppName);

			}
		},
		'dusk-till-dawn': {
			name: 'From Dusk Till Dawn',
			directoryId: 14,
			logo: '/file/25.dat',
			success: function(response) {
				return (response.status == 200);
			},
			update: function(externalAppName, extendedData) {

				var items;
				var item;
				var itemKey;
				var i;
				var externalApp;
				var key;
				var data;
				var url;

				url = 'http://d2n.duskdawn.net/zone/extended';

				externalApp = modules.updateApps.externalApps[externalAppName];

				key = modules.updateApps.retrieveAppKey(externalAppName);

				if (!key) {
					externalApp.updating = false;
					externalApp.error = true;
					externalApp.errorMessage = 'Error retrieving key';
					modules.updateApps.updateTooltip();
					return;
				}

				data = 'key=' + key;

				data += '&zombies=' + extendedData.zombies;
				data += '&zone_depleted=' + (extendedData.depleted ? 'true' : 'false');
				data += '&camping_topology=' + extendedData.topology;

				if (extendedData.building) {
					data += '&blueprint_available=' + (extendedData.blueprint ? 'true' : 'false');
				}

				if (extendedData.items.length > 0) {

					items = {};

					for (i = 0; i < extendedData.items.length; i++) {
						item = extendedData.items[i];
						itemKey = item.id + (item.broken ? 'B' : '');
						if (items[itemKey]) {
							items[itemKey]++;
						} else {
							items[itemKey] = 1;
						}
					}

					for (itemKey in items) {
						data += '&items=' + itemKey + '-' + items[itemKey];
					}
				}

				modules.updateApps.sendUpdate(url, data, externalAppName);

			}
		},
		'mapviewer': {
			name: 'Map Viewer',
			directoryId: 1,
			logo: '/file/37.dat',
			success: function(response) {
				return (response.status == 200 && response.responseText.match(/Zone .* was updated successfully/));
			},
			update: function(externalAppName, extendedData) {

				var items;
				var item;
				var itemKey;
				var i;
				var externalApp;
				var key;
				var data;
				var url;
				var unique;

				url = 'http://die2nite.gamerz.org.uk/plugin/extended';

				externalApp = modules.updateApps.externalApps[externalAppName];

				key = modules.updateApps.retrieveAppKey(externalAppName);

				if (!key) {
					externalApp.updating = false;
					externalApp.error = true;
					externalApp.errorMessage = 'Error retrieving key';
					modules.updateApps.updateTooltip();
					return;
				}

				data = 'key=' + key;

				if (extendedData.building) {
					data += '&blueprint=' + (extendedData.blueprint ? 1 : 0);
				}

				if (extendedData.hardcore) {

					data += '&zombies=' + extendedData.zombies;
					data += '&depleted=' + (extendedData.depleted ? 1 : 0);

					if (extendedData.items.length > 0) {

						unique = 0;
						items = {};

						for (i = 0; i < extendedData.items.length; i++) {
							item = extendedData.items[i];
							itemKey = item.id + (item.broken ? 'B' : '');
							if (items[itemKey]) {
								items[itemKey]++;
							} else {
								items[itemKey] = 1;
								unique++;
							}
						}

						data += '&items=';

						i = 1;
						for (itemKey in items) {
							data += itemKey + '-' + items[itemKey];
							if (i < unique) {
								data += ',';
							}
							i++;
						}
					}

				}

				modules.updateApps.sendUpdate(url, data, externalAppName);

			}
		}
	},

	campingTopologies: {
		'L1_SUICIDE': 'Sleeping somewhere like this is basically a form of suicide',
		'L2_SHORTAGE': 'There\'s a distinct shortage of shelter here.',
		'L3_MINIMAL': 'This zone offers nothing more than minimal "natural" protection.',
		'L4_HIDING_PLACE': 'After a quick look around, it looks like you could find a good hiding place here.',
		'L5_FEW_HIDEOUTS': 'For those looking to spend the night, there are a few hideouts in this zone',
		'L6_TOP_HIDEOUTS': 'If required, there are some top-notch hideouts here...',
		'L7_IDEAL': 'This seems like the ideal place to spend the night'
	},

	config: function() {

		log('modules.updateApps.config()');

		var content;
		var externalAppName;
		var externalApp;
		var delay;
		var delayText;

		content = '<p>This module adds a small light above your rucksack. The colour of the light will change while it does its thing:' +
		'<ul>' +
		'<li><div class="modules-updateApps-config-light"></div> All apps are updated <img src="/gfx/forum/smiley/h_lol.gif"></li>' +
		'<li><div class="modules-updateApps-config-light waiting"></div> Waiting for the delay to pass (see below) <img src="/gfx/forum/smiley/h_sleep.gif"></li>' +
		'<li><div class="modules-updateApps-config-light updating"></div> One or more apps are updating <img src="/gfx/forum/smiley/h_sleep.gif"><img src="/gfx/forum/smiley/h_sleep.gif"></li>' +
		'<li><div class="modules-updateApps-config-light errors"></div> Something broke <img src="/gfx/forum/smiley/h_death.gif"></li>' +
		'</ul></p>' +
		'<p>Hover over the light to see the status of each app, or click it to trigger an update.</p>';

		content += '<h2>External Apps</h2>';

		for (externalAppName in this.externalApps) {
			externalApp = this.externalApps[externalAppName];
			content += '<p><img src="' + externalApp.logo + '" height="16" width="16"> ' + app.settings.checkbox('modules.updateApps.externalApps.' + externalAppName + '.enabled', externalApp.name, 'true') + '</p>';
		}

		delay = localStorage.getItem('modules.updateApps.config.delay');
		if (undefined == delay) {
			delay = 2;
		}
		delayText = delay == 0 ? 'No delay' : delay + ' seconds';

		content += '<h2>Delay</h2>';
		content += '<p>This will help prevent apps getting spammed when you pick up or drop a lot of items.</p>';
		content += '<div id="modules-updateApps-config-delay-slider"></div>';
		content += '<span id="modules-updateApps-config-delay">' + delayText + '</span>';

		return content;
	},

	configInit: function() {

		var delay;

		delay = localStorage.getItem('modules.updateApps.config.delay');

		if (undefined == delay) {
			delay = 2;
		}

		$('#modules-updateApps-config-delay-slider').slider({
			value: delay,
			min: 0,
			max: 10,
			slide: function( event, ui ) {
				var delay = ui.value;
				localStorage.setItem('modules.updateApps.config.delay', delay);
				var delayText = delay == 0 ? 'No delay' : delay + ' seconds';
				$('#modules-updateApps-config-delay').html(delayText);
			}
		});
	},

	init: function() {

		log('modules.updateApps.init()');

		// see if the user has set a delay
		var delay = localStorage.getItem('modules.updateApps.config.delay');
		delay = (undefined == delay) ? 2000 : delay * 1000;

		if (delay == 0) {
			// no delay, so update immediately
			this.updateApps();
		} else {
			// (re)set the expiry time
			var time = new Date();
			this.delayUntil = time.getTime() + delay;

			// and delay the update
			window.setTimeout(this.updateApps, delay);

			this.updateTooltip();
		}
		
		modules.updateApps.heartbeat = modules.updateApps.heartbeat || window.setInterval(function() {
			if ($('#modules-updateApps-init').length == 0) {
				window.setTimeout(function() {
					if ($('#modules-updateApps-init').length == 0) {
						modules.updateApps.updateApps();
					}
				}, 1000);
			}
		}, 1000);

	},

	updateApps: function() {

		log('modules.updateApps.updateApps()');

		var time;
		var externalAppName;
		var externalApp;
		var extendedData;

		// see if the user has set a delay
		if (localStorage.getItem('modules.updateApps.config.delay') == 0) {

			// they have, so has their delay expired?
			time = new Date();
			if (time.getTime() < modules.updateApps.delayUntil) {
				// no. they must have triggered another update since this was called
				return false;
			}
		}

		extendedData = modules.updateApps.getExtendedData();
		if (!extendedData) {
			return false;
		}

		$('#modules-updateApps-init').addClass('updating');

		for (externalAppName in modules.updateApps.externalApps) {

			externalApp = modules.updateApps.externalApps[externalAppName];

			if (localStorage.getItem("modules.updateApps.externalApps." + externalAppName + ".enabled") !== 'false') {

				externalApp.updating = true;
				externalApp.error = false;
				externalApp.errorMessage = false;
				externalApp.temporariyDisabled = false;
				externalApp.update(externalAppName, extendedData);
			}
		}

		modules.updateApps.updateTooltip();

		return true;

	},

	updateTooltip: function() {

		log('modules.updateApps.updateTooltip()');

		var updatingCount;
		var errors;
		var tooltipContent;
		var delay;
		var time;
		var waiting;
		var externalApp;
		var externalAppName;
		var tooltipTitle;
		var div;
		var tooltip ;

		updatingCount = 0;
		errors = false;

		delay = localStorage.getItem('modules.updateApps.config.delay');
		if (undefined == delay) {
			delay = 2000;
		} else {
			delay = delay * 1000;
		}

		time = new Date();

		waiting = false;
		if (delay !== 0 && this.delayUntil !== undefined && time.getTime() < this.delayUntil) {
			waiting = true;
		}

		tooltipContent = '<ul class="modules-updateApps-tooltip">';

		for (externalAppName in this.externalApps) {

			externalApp = this.externalApps[externalAppName];

			if (localStorage.getItem("modules.updateApps.externalApps." + externalAppName + ".enabled") == 'false') {
				tooltipContent += '<li class="disabled" data-app="' + externalAppName + '"><img width="16" height="16" src="' + externalApp.logo + '" /> ' + externalApp.name + ': <span>Disabled</span></li>';
			} else if (externalApp.temporariyDisabled) {
				tooltipContent += '<li data-app="' + externalAppName + '"><img width="16" height="16" src="' + externalApp.logo + '" /> ' + externalApp.name + ': <span>' + externalApp.temporariyDisabledMessage + '</span></li>';
			} else if (waiting) {
				tooltipContent += '<li class="waiting" data-app="' + externalAppName + '"><img width="16" height="16" src="' + externalApp.logo + '" /> ' + externalApp.name + ': <span>Waiting...</span></li>';
			} else if (externalApp.error) {
				errors = true;
				tooltipContent += '<li class="broken" data-app="' + externalAppName + '"><img width="16" height="16" src="' + externalApp.logo + '" /> ' + externalApp.name + ': <span>' + externalApp.errorMessage + '</span></li>';
			} else if (externalApp.updating) {
				updatingCount++;
				tooltipContent += '<li class="updating" data-app="' + externalAppName + '"><img width="16" height="16" src="' + externalApp.logo + '" /> ' + externalApp.name + ': <span>Updating...</span></li>';
			} else {
				tooltipContent += '<li class="updated" data-app="' + externalAppName + '"><img width="16" height="16" src="' + externalApp.logo + '" /> ' + externalApp.name + ': <span>Updated!</span></li>';
			}
		}

		tooltipContent += '</ul>';

		tooltipTitle = 'Map app updater <img src="http://data.die2nite.com/gfx/icons/small_archive.gif">';

		div = $('#modules-updateApps-init');

		if (!div.length) {

			div = $('<div id="modules-updateApps-init" class="updating" />');
			div.attr('onmouseout', 'js.HordeTip.hide();');
			$('#generic_section .right h2:first').append(div);

			$('#modules-updateApps-init').on('click', modules.updateApps.updateApps);

		}

		div.attr('onmouseover', 'js.HordeTip.showTip(this, \'' + tooltipTitle + '\', \'' + tooltipContent + '\')');

		tooltip = $('#tooltipContent');

		if ($('.title',tooltip).html() == tooltipTitle) {
			tooltip.html('<div class="title">' + tooltipTitle + '</div>' + tooltipContent);
		}

		if (waiting) {
			div.addClass('waiting');
		} else {
			div.removeClass('waiting');
		}

		if (errors) {
			div.addClass('errors');
		} else {
			div.removeClass('errors');
		}

		if (updatingCount > 0) {
			div.addClass('updating');
		} else {
			div.removeClass('updating');
		}

	},

	sendUpdate: function(url, data, externalAppName) {

		(function(url, data, externalAppName) {
			
			log(url + ': ' + data);
			
			GM_xmlhttpRequest({
				method: "POST",
				url: url,
				data: data,
				headers: {
					"Content-Type": "application/x-www-form-urlencoded"
				},
				onload: function(response) {

					var externalApp;
					
					log(externalAppName + ' replied with:');
					log(response);
					
					externalApp = modules.updateApps.externalApps[externalAppName];
					
					externalApp.updating = false;
					if (!externalApp.success(response)) {
						externalApp.error = true;
						externalApp.errorMessage = 'Invalid response';
					}
					modules.updateApps.updateTooltip();
				}
			});
		})(url, data, externalAppName);
	},

	retrieveAppKey: function(appName) {

		log('modules.updateApps.retrieveAppKey(' + appName + ')');

		var url;
		var key;
		var result;
		var sk;

		key = localStorage.getItem('modules.updateApps.externalApps.' + appName + '.key');
		if (undefined != key) {
			return key;
		}

		result = window.location.hash.match(/sk\=([0-9a-f]{5})/);
		sk = result ? result[1] : '';

		url = '/disclaimer?id=' + modules.updateApps.externalApps[appName].directoryId + ';sk=' + sk + ';rand=' + (Math.floor(Math.random()*999998) + 1);

		$.ajax({
			async: false,
			url: url,
			success: function(data) {

				var search;

				search = data.match(/name="key" value="([0-9a-f]+)"/);

				if (null != search) {
					key = search[1];
					localStorage.setItem('modules.updateApps.externalApps.' + appName + '.key', key);
				} else {
					log('key for ' + appName + ' not found');
					key = false;
				}

			},
			error: function() {
				log('ajax error while retrieving key for ' + appName);
				key = false;
			}
		});

		return key;

	},

	getItemNamesToIds: function() {

		var itemNamesToIdsUpdateTime;
		var itemNamesToIds;
		var time;
		var expiry;
		var url;

		itemNamesToIdsUpdateTime = localStorage.getItem('modules.updateApps.itemNamesToIdsUpdateTime');
		itemNamesToIds = localStorage.getItem('modules.updateApps.itemNamesToIds');

		time = new Date().getTime();
		expiry = 60 * 60 * 24 * 7 * 1000; // a week

		if (undefined == itemNamesToIds || undefined == itemNamesToIdsUpdateTime || time > (itemNamesToIdsUpdateTime + expiry) ) {
			// If Map Viewer goes down, this URL will need to be replaced. It returns
			// a JSON object containing all the items' names and IDs:
			//
			// {
			// "'Wake The Dead'":"97",
			// "A letter with no address":"189",
			// "Adjustable Spanner":"13",
			// ...
			// }

			url = 'https://raw.githubusercontent.com/Miudod2n/die2nitestuff/master/item-names-to-ids.json';

			GM_xmlhttpRequest({
				url: url,
				beforeSend: function(x) {
				    if(x && x.overrideMimeType) {
				        x.overrideMimeType("application/j-son;charset=UTF-8");
				        }
				        },
				        
				onload: function(response) {
					localStorage.setItem('modules.updateApps.itemNamesToIds', response.responseText);
					localStorage.setItem('modules.updateApps.itemNamesToIdsUpdateTime', new Date().getTime());
					modules.updateApps.updateApps();
				}
			});

			return false;
		} else {
			return eval('(' + itemNamesToIds + ')');
		}

	},

	getExtendedData: function() {

		var itemNamesToIds;
		var extendedData;
		var topologyEl;
		var topologyKey;
		var re;

		topologyEl = $('#campInfos p:nth-child(2)');

		if (topologyEl.length == 0) {
			return false;
		}

		itemNamesToIds = modules.updateApps.getItemNamesToIds();

		if (!itemNamesToIds) {
			return false;
		}

		extendedData = {
			hardcore: ($('.day .hard').length == 1),
			building: ($('.outSpot').length > 0),
			blueprint: ($('#campInfos').text().match(/(You will earn)|(You can obtain)/)),
			zombies: $('#zombiePts').text().replace(/[^0-9]/g, ''),
			depleted: ($('.driedZone').length > 0),
			items: []
		};

		for (topologyKey in modules.updateApps.campingTopologies) {
			re = new RegExp(modules.updateApps.campingTopologies[topologyKey]);
			if (topologyEl.html().match(re)) {
				extendedData.topology = topologyKey;
				break;
			}
		}

		$('.outInv li span, .outInv li span a').each(function() {

			var match;
			var name;
			var broken;
			var id;
			var item;

			if (!$(this).attr('onmouseover')) {
				return true;
			}

			match = $(this).attr('onmouseover').match(/'([^<]*)/);
			if (null == match) {
				return true;
			}

			name = match[1].trim().replace(/\\'/g, "'");			
			broken = $(this).hasClass('limited') || $(this).parent().hasClass('limited');
			id = itemNamesToIds[name];

			if (id) {
				item = {
					name: name,
					id: parseInt(itemNamesToIds[name]),
					broken: broken
				};
				extendedData.items.push(item);
			} else {
			// item not found, need to do something here.....
			}

			return true;
		});
		
		log('Extended data:');
		log(extendedData);
		
		return extendedData;
	}

};