NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name GGn Dwarf Drop Notifier // @namespace https://openuserjs.org/users/SB100 // @description Get a notification if a dwarf drops an item. Needs an API key with 'User' permissions that you can generate here: https://gazellegames.net/user.php?action=edit#save // @updateURL https://openuserjs.org/meta/SB100/GGn_Dwarf_Drop_Notifier.meta.js // @version 1.3.4 // @author SB100 // @copyright 2022, SB100 (https://openuserjs.org/users/SB100) // @license MIT // @match https://gazellegames.net/* // @connect gazellegames.net // @grant GM.xmlHttpRequest // @grant GM.getValue // @grant GM.setValue // ==/UserScript== // ==OpenUserJS== // @author SB100 // ==/OpenUserJS== /** * ============================= * ADVANCED OPTIONS * ============================= */ // check every 5 mins max const SETTING_THROTTLE = 1000 * 60 * 5; // true = never auto hide the popup (click will remove it) // false = popup self disappears after 5 seconds const SETTING_PERMANENT_POPUP = true; // whether to show console output const DEBUG_ENABLED = false; /** * ============================= * END ADVANCED OPTIONS * DO NOT MODIFY BELOW THIS LINE * ============================= */ /** * Output a console.log line if debug is enabled */ function debug(...str) { if (!DEBUG_ENABLED) { return; } // eslint-disable-next-line no-console console.log('[GGn Dwarf Drop Notifier]', ...str); } /** * Try parsing a string into JSON, otherwise fallback */ function JsonParseWithDefault(s, fallback = null) { try { return JSON.parse(s); } catch (e) { return fallback; } } /** * Gets your GGn API key. Asks the user for one if it hasn't been set yet */ async function getGgnApiKey() { const key = await GM.getValue('ggn_key', ''); if (!key) { // eslint-disable-next-line no-alert const input = window.prompt(`Please input your GGn API key. If you don't have one, please generate one from your Edit Profile page: https://gazellegames.net/user.php?action=edit. Please disable this userscript until you have one as this prompt will continue to show until you enter one in.`); const trimmed = input.trim(); if (/[a-f0-9]{64}/.test(trimmed)) { await GM.setValue('ggn_key', trimmed); return trimmed; } } return key; } /** * Creates a UTC date object from a date string * d should be in the form yyyy-mm-dd hh:ii:ss, and is assumed a UTC date */ function createUTCDate(d) { const parts = d.split(/[-\s:]/); parts[1] = (parseInt(parts[1], 10) - 1).toString(); return new Date(Date.UTC(...parts)); } /** * Creates a UTC date string from a date object * Returns the format yyyy-mm-dd hh:ii:ss */ function createUTCString(d = Date.now()) { return new Date(d).toISOString().replace(/T/, ' ').replace(/\..+/, ''); } /** * Return the last drop info from localStorage, as date objects */ function getLastDropInfo() { // default date is 1 day ago. So when you install the script for the first time, it sets up checks properly. const utcString = createUTCString(new Date(Date.now() - 24 * 3600 * 1000)); const defaultDropInfo = { check: utcString, drop: utcString, }; const lastDropInfoAsStrings = JsonParseWithDefault( window.localStorage.getItem('lastDropInfo') || defaultDropInfo, defaultDropInfo ); return { check: createUTCDate(lastDropInfoAsStrings.check), drop: createUTCDate(lastDropInfoAsStrings.drop), }; } /** * Sets the lastDropInfo value in localStorage */ function setLastDropInfo(check, drop, lastDropInfo) { window.localStorage.setItem( 'lastDropInfo', JSON.stringify({ check: check !== null ? check : createUTCString(lastDropInfo.check), drop: drop !== null ? drop : createUTCString(lastDropInfo.drop), }) ); } /** * Returns a boolean by comparing the lastDropInfo.check value to the current time + SETTING_THROTTLE */ function isTimeToCheck(lastDropInfo) { const currentTime = createUTCDate(createUTCString()); if (currentTime < new Date(lastDropInfo.check.getTime() + SETTING_THROTTLE)) { debug('[GGn Dwarf Drop Notifier] Not checking for companion drop'); return false; } debug('Checking for companion drop'); return true; } /** * If we have notifications to show, create a popup, and remove it after 5s */ function createNotification(drops) { if (drops.length === 0) { debug('No new drops found'); return; } const innerHTML = `<a id="ggn-dwarf-drop-notif" href='#close' style='display: block; border-radius: 5px; border: 1px solid rgb(80, 194, 78); box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 4px; color: darkgreen; width: 290px; cursor: pointer; font-size: 13px; line-height: 16px; text-align: left; padding: 8px 10px 9px; overflow: hidden; background-image: linear-gradient(135deg, #8ae68a 25%, #8fee8f 25%, #8fee8f 50%, #8ae68a 50%, #8ae68a 75%, #8fee8f 75%, #8fee8f 100%); background-size: 28.28px 28.28px;'> <ul style='list-style-type: none; margin: 0; padding: 0;'> ${drops.map((drop) => `<li>${drop.message}</li>`).join('')} </ul> </li>`; const notif = document.createElement('div'); notif.classList.add('i-am-new'); notif.innerHTML = innerHTML; document.body.appendChild(notif); if (SETTING_PERMANENT_POPUP) { notif.onclick = () => { notif.remove(); }; } else { setTimeout(() => { notif.remove(); }, 5000); } } /** * Parses the API response, and checks if a new drop has happened */ function xmlOnLoad(response, lastDropInfo) { // something wrong with the response if (response.status !== 'success') { debug('API returned unsuccessful response'); return []; } // no results found if (!response.response || response.response.length === 0) { debug('No drops found in response'); return []; } const results = []; for (let i = 0, len = response.response.length; i < len; i += 1) { const item = response.response[i]; if (createUTCDate(item.time) > lastDropInfo.check) { debug('Pushed', createUTCDate(item.time)); results.push(item); } } setLastDropInfo( createUTCString(), results.length > 0 ? results[0].time : createUTCString(lastDropInfo.drop), lastDropInfo ); return results; } /** * Sends a request to the API, checking for companion drops */ function sendApiRequest(key, action, params, lastDropInfo) { const paramStr = new URLSearchParams(params).toString(); let resolver; let rejecter; const p = new Promise((resolveFn, rejectFn) => { resolver = resolveFn; rejecter = rejectFn; }); const url = `/api.php?request=${action}${ paramStr.length > 0 ? `&${paramStr}` : '' }`; GM.xmlHttpRequest({ method: 'get', url, timeout: 30000, headers: { 'X-API-Key': key, }, onload(done) { if (done.status !== 200) { setLastDropInfo(createUTCString(), null, lastDropInfo); rejecter(new Error('Not OK')); return; } resolver(JsonParseWithDefault(done.response)); }, onerror() { rejecter(new Error('Error')); }, ontimeout() { rejecter(new Error('Timeout')); }, }); return p; } /** * Creates the style tag to show a popup if needed */ function createStyleTag() { const css = `.i-am-new { bottom: 20px; right: 20px; position: fixed; width: 310px; margin: 0px; padding: 0px; list-style-type: none; z-index: 10000000; } .i-am-new + .i-am-new { bottom: 70px!important; }`; const style = document.createElement('style'); style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } (async function main() { const key = await getGgnApiKey(); if (!key) { debug( '[GGn Dwarf Drop Notifier] No valid API key found, exiting userscript' ); return; } const lastDropInfo = getLastDropInfo(); debug('Last Drop Info', lastDropInfo); if (!isTimeToCheck(lastDropInfo)) { return; } createStyleTag(); sendApiRequest(key, 'userlog', { search: 'dropped', limit: 25 }, lastDropInfo) .then((response) => xmlOnLoad(response, lastDropInfo)) .then((drops) => createNotification(drops)) // eslint-disable-next-line no-console .catch((e) => console.error(`[GGn Dwarf Drop Notifier] ${e.message}`)); })();