// ==UserScript== // @author DanielOnDiordna // @name Favorite portal details // @category Info // @version 0.1.6.20210724.002500 // @updateURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/DanielOnDiordna/favorite-portal-details.meta.js // @downloadURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/DanielOnDiordna/favorite-portal-details.user.js // @description [danielondiordna-0.1.6.20210724.002500] Quickly show a list of details for your favorite list of portals. // @id favorite-portal-details@DanielOnDiordna // @namespace https://softspot.nl/ingress/ // @match https://intel.ingress.com/* // @grant none // ==/UserScript== function wrapper(plugin_info) { // ensure plugin framework is there, even if iitc is not yet loaded if(typeof window.plugin !== 'function') window.plugin = function() {}; // use own namespace for plugin window.plugin.favoriteportaldetails = function() {}; var self = window.plugin.favoriteportaldetails; self.id = 'favoriteportaldetails'; self.title = 'Favorite portal details'; self.version = '0.1.6.20210724.002500'; self.author = 'DanielOnDiordna'; self.changelog = ` Changelog: version 0.1.1.20180911.233100 - earlier version version 0.1.1.20181030.221200 - bug fix: localstoragesettings was not defined version 0.1.2.20190117.143900 - minor code fix: assign needs empty array version 0.1.3.20190406.225100 - bug fix: resonators and mods object converted to array for sort purposes - support for empty mods - underline resos and mods also if the active player is not the owner - sort resos by level and then by owner name version 0.1.4.20190530.122000 - closedialog fix for smartphone version 0.1.5.20210123.230400 - updated plugin wrapper and userscript header formatting to match IITC-CE coding - store portal titles for later use - fixed width of dialog version 0.1.6.20210328.235400 - fixed portal guid format checker version 0.1.6.20210421.190200 - minor fix for IITC CE where runHooks iitcLoaded is executed before addHook is defined in this plugin version 0.1.6.20210724.002500 - prevent double plugin setup on hook iitcLoaded `; self.namespace = 'window.plugin.' + self.id + '.'; self.pluginname = 'plugin-' + self.id; self.panename = 'plugin-' + self.id; self.localstoragesettings = self.panename + '-settings'; self.dialogobject = null; self.onPortalSelectedPending = false; self.settings = { refreshonstart: true }; self.favoriteslist = []; self.favorites = {}; self.favorite = { title: '', team: '?', level: '?', resonators: [], mods: [], health: '?', owner: '?', timestamp: 0, lat: 0.0, lng: 0.0 }; self.storagename = self.panename + '-favorites'; // self.selector = '
'; self.selector = ''; self.requestlist = {}; self.requestrunning = false; self.requestguid = null; self.requesttimerid = 0; self.ownercolor = 'black'; self.refreshonload_runonce = true; self.shortnames = { '' :' ', 'Heat Sink' :'H', 'Portal Shield' :'S', 'Link Amp' :'L', 'Turret' :'T', 'Multi-hack' :'M', 'Aegis Shield' :'A', 'Force Amp' :'F', 'SoftBank Ultra Link' :'U', 'Ito En Transmuter (+)':'I+', 'Ito En Transmuter (-)':'I-' }; self.shortrarities = { '' :' ', 'COMMON' :'c', 'RARE' :'r', 'VERY_RARE':'v' }; self.restoresettings = function() { if (typeof localStorage[self.localstoragesettings] === 'string') { var settings = JSON.parse(localStorage[self.localstoragesettings]); if (typeof settings === 'object' && settings instanceof Object) { if (typeof settings.refreshonstart === 'boolean') self.settings.refreshonstart = settings.refreshonstart; } } }; self.storesettings = function() { localStorage[self.localstoragesettings] = JSON.stringify(self.settings); }; self.restorefavorites = function() { if (typeof localStorage[self.storagename] === 'string') { var favoriteslist = JSON.parse(localStorage[self.storagename]); // array of separator names and guid=>portal name (or guid only) self.favoriteslist = []; // array of separator names and guids self.favorites = {}; // hash of guid=>{portal details} for (var cnt = 0; cnt < favoriteslist.length; cnt++) { if (typeof favoriteslist[cnt] === 'string' && favoriteslist[cnt].match(/^[0-9a-f]{32}\.[0-9a-f]{2}$/)) { // old file format: in case it's only a guid, convert to new formatting let guid = favoriteslist[cnt]; let favorite = {}; favorite[guid] = guid; // use guid as portal name favoriteslist[cnt] = favorite; } if (typeof favoriteslist[cnt] === 'object' && favoriteslist[cnt] instanceof Object) { let guid = Object.keys(favoriteslist[cnt])[0]; self.favorites[guid] = Object.assign({}, self.favorite); // copy empty favoritee object without reference self.favorites[guid].title = favoriteslist[cnt][guid]; self.favoriteslist.push(guid); } else if (typeof favoriteslist[cnt] === 'string') { // separator name self.favoriteslist.push(favoriteslist[cnt]); } } } }; self.storefavorites = function() { let storedata = []; // array of separator names and guid=>portal name for (let cnt = 0; cnt < self.favoriteslist.length; cnt++) { if (self.favoriteslist[cnt].match(/^[0-9a-f]{32}\.[0-9a-f]{2}$/)) { // guid let guid = self.favoriteslist[cnt]; let favorite = {}; favorite[guid] = self.favorites[guid].title; storedata.push(favorite); } else { // separator name storedata.push(self.favoriteslist[cnt]); } } localStorage[self.storagename] = JSON.stringify(storedata); }; self.requesttimeout = function() { self.requestrunning = false; self.requestlist = {}; self.updateselector(); }; self.requestnext = function() { if (Object.keys(self.requestlist).length === 0) { self.requestrunning = false; return; } var guid = Object.keys(self.requestlist)[0]; self.requestguid = guid; window.setTimeout(function() { self.requesttimerid = window.setTimeout(self.requesttimeout,10000); // 10 seconds window.portalDetail.request(self.requestguid); },1000); // keep a second between request }; self.requestall = function() { if (self.requestrunning) return; if (Object.keys(window.portals).length === 0) return; self.requestrunning = true; for (var guid in self.favorites) { self.requestlist[guid] = true; } self.requestnext(); }; self.focusportal = function(guid) { if (Object.keys(window.portals).length === 0) return; // cancel while no portals loaded yet; prevents an error inside the IITC core if (window.selectedPortal === guid && self.favorites[guid]) { map.setView([self.favorites[guid].lat,self.favorites[guid].lng]); } if (window.portals[guid]) { window.renderPortalDetails(guid); } else { self.requestguid = guid; window.portalDetail.request(guid); } }; self.closedialog = function() { if (self.dialogobject) { self.dialogobject.dialog('close'); self.dialogobject = null; } }; self.getdatetimestring = function(date1) { if (!(date1 instanceof Date)) { if (date1) { date1 = new Date(date1); } else { date1 = new Date(); } } return [date1.getFullYear(),date1.getMonth()+1,date1.getDate()].join('/') + ' ' + [date1.getHours(),('0' + date1.getMinutes()).slice(-2),('0' + date1.getSeconds()).slice(-2)].join(':'); }; self.resonatorshtml = function(resonators,portalowner) { var highlightowner = window.PLAYER.nickname; var resolist = []; resonators = resonators.sort( function(a,b) { if (a.level < b.level) return 1; if (a.level > b.level) return -1; let o1 = a.owner.toLowerCase(); let o2 = b.owner.toLowerCase(); if (o1 > o2) return 1; if (o1 < o2) return -1; return 0; }); // sort by resonator level and then by owner for (var cnt = 0; cnt < 8; cnt++) { var lvl = '-'; if (cnt < resonators.length && resonators[cnt]) { // {owner, level, energy} var resonatorowner = resonators[cnt].owner; lvl = resonators[cnt].level; if (resonatorowner === highlightowner) { lvl = '' + lvl + ''; // highlight reso's of current player } if (resonatorowner !== portalowner) { lvl = '' + lvl + ''; // underline reso's of other people then the portal owner } lvl = '' + lvl + ''; } resolist.push(lvl); } return resolist.join(''); }; self.modshtml = function(mods,portalowner) { var highlightowner = window.PLAYER.nickname; var modslist = []; for (var cnt = 0; cnt < mods.length; cnt++) { // {owner, name, rarity, stats: {…}} var mod; if (!mods[cnt]) { mod = ''; } else { mod = self.shortnames[mods[cnt].name]; if (mod === 'H' || mod === 'S' || mod === 'M') mod = self.shortrarities[mods[cnt].rarity] + mod; var modowner = mods[cnt].owner; if (modowner === highlightowner) { mod = '' + mod + ''; // highlight mods of current player } if (modowner !== portalowner) { mod = '' + mod + ''; // underline mods of other people then the portal owner } mod = '' + mod + ''; } modslist.push(mod); } return modslist.join(' '); }; self.favoriteshtml = function() { var highlightowner = window.PLAYER.nickname; var rows = []; var headers = []; headers.push('T'); headers.push('Title'); headers.push('Health'); headers.push('Lvl'); headers.push('Resonators'); headers.push('Mods'); headers.push('Owner'); headers.push('Checked'); rows.push('' + headers.join('') + ''); for (var cnt = 0; cnt < self.favoriteslist.length; cnt++) { var guid = self.favoriteslist[cnt]; if (self.favorites[guid]) { if (!self.favorites[guid].title && window.portals[guid]) { var portaldata = window.portals[guid].options.data; self.favorites[guid].title = (portaldata.title?portaldata.title:''); self.favorites[guid].team = portaldata.team; self.favorites[guid].level = (portaldata.level >= 0?(portaldata.team === 'N'?0:portaldata.level):''); } } if (self.favorites[guid]) { var columns = []; columns.push(self.favorites[guid].team); columns.push('' + (self.favorites[guid].title?self.favorites[guid].title:guid) + ''); columns.push((self.favorites[guid].team === 'N'?'-':self.favorites[guid].health + '%')); columns.push('L' + self.favorites[guid].level + ''); columns.push(self.resonatorshtml(self.favorites[guid].resonators,self.favorites[guid].owner)); columns.push(self.modshtml(self.favorites[guid].mods,self.favorites[guid].owner)); columns.push('' + self.favorites[guid].owner + ''); columns.push((self.requestlist[guid]?'updating...':(self.favorites[guid].timestamp == 0?'never':self.getdatetimestring(self.favorites[guid].timestamp)))); rows.push('' + columns.join('') + ''); } else { rows.push('' + self.favoriteslist[cnt] + ''); } } return '' + rows.join("\n") + '
'; }; self.orderup = function(cnt) { if (cnt === 0) return; var cntvalue = self.favoriteslist[cnt]; self.favoriteslist[cnt] = self.favoriteslist[cnt - 1]; self.favoriteslist[cnt - 1] = cntvalue; self.storefavorites(); self.updateselector(); }; self.orderdown = function(cnt) { if (cnt === self.favoriteslist.length - 1) return; var cntvalue = self.favoriteslist[cnt]; self.favoriteslist[cnt] = self.favoriteslist[cnt + 1]; self.favoriteslist[cnt + 1] = cntvalue; self.storefavorites(); self.updateselector(); }; self.removefavorite = function(cnt) { var guid = self.favoriteslist[cnt]; if (self.favorites[guid]) delete(self.favorites[guid]); self.favoriteslist.splice(cnt,1); self.storefavorites(); self.updateselector(); }; self.addtitle = function() { var newtitle = prompt('Enter a new separator label:'); if (newtitle === null || newtitle === '') return; self.favoriteslist.push(newtitle); self.storefavorites(); self.updateselector(); }; self.edittitle = function(cnt) { var newtitle = prompt('Edit separator label:',self.favoriteslist[cnt]); if (newtitle === null || newtitle === '') return; self.favoriteslist[cnt] = newtitle; self.storefavorites(); self.updateselector(); }; self.ordermenuhtml = function() { var rows = []; for (var cnt = 0; cnt < self.favoriteslist.length; cnt++) { var guid = self.favoriteslist[cnt]; var columns = []; var title = (self.favorites[guid]?(self.favorites[guid].title?self.favorites[guid].title:guid):self.favoriteslist[cnt]); columns.push('X'); columns.push(''); columns.push(''); if (self.favorites[guid]) { columns.push(title); } else { columns.push('' + title + ''); } rows.push('' + columns.join('') + ''); } return '' + rows.join("\n") + '
'; }; self.aboutmenu = function() { var html = '
' + '
' + '< Main menu
\n' + '
' + 'Add your favorite portals to a list.
\n' + '
\n' + 'Step 1: Select a portal and show the details pane.
\n' + 'Step 2: Click on the portal icon in front of the portal title:
\n' + ' It will change into your team color when selected.
\n' + '
\n' + 'Step 3: Open the ' + self.title + ' menu.
\n' + '
\n' + 'Now you can easily see all details about this portal: resonators and mods (' + self.ownercolor + ' are placed by you, underlined are placed by someone else then the portal owner, others are placed by the portal owner).
\n' + '
\n' + 'Step 4: Change order, remove favorites or add separator labels from the Change order menu.
\n' + '
\n' + 'Portal details are automatically updated when Intel is reloaded, or refreshed by clicking the "Check all" button, and also updated when a portal is selected.
\n' + 'Click on a title to focus the portal. Click again to move the map to the selected portal.
\n' + 'version ' + self.version + ' by ' + self.author + '' + '
'; if (window.useAndroidPanes()) { self.closedialog(); // close, if any $('#' + self.id + 'menu').remove(); $('
').append(html).appendTo(document.body); } else { self.dialogobject = window.dialog({ html: $('
').append(html), id: 'plugin-' + self.id + '-dialog', title: self.title + ' About', width: 400 }); } }; self.ordermenu = function() { var html = '
' + '' + '
' + self.ordermenuhtml() + '
' + '
'; if (window.useAndroidPanes()) { self.closedialog(); // close, if any $('#' + self.id + 'menu').remove(); $('
').append(html).appendTo(document.body); } else { self.dialogobject = window.dialog({ html: $('
').append(html), id: 'plugin-' + self.id + '-dialog', title: self.title + ' Change Order' }); } }; self.menu = function() { var html = '
' + '
' + 'Check all Change order About' + '' + '
' + '
' + self.favoriteshtml() + '
' + '
'; if (window.useAndroidPanes()) { self.closedialog(); // close, if any $('#' + self.id + 'menu').remove(); $('
').append(html).appendTo(document.body); } else { self.dialogobject = window.dialog({ html: $('
').append(html), id: 'plugin-' + self.id + '-dialog', dialogClass: 'ui-dialog-' + self.id + '-menu', title: self.title, width: 700 }); } }; self.toggleselection = function(guid) { if (!guid) guid = window.selectedPortal; if (!guid) return; var portaldata = {}; if (window.portals[guid]) portaldata = window.portals[window.selectedPortal].options.data; if (!self.favorites[guid]) { self.favorites[guid] = Object.assign({}, self.favorite); // copy object without reference self.favorites[guid].title = portaldata.title; self.favorites[guid].team = portaldata.team; self.favorites[guid].level = (portaldata.team === 'N'?0:portaldata.level); self.favoriteslist.push(guid); self.requestlist[guid] = true; // force request portal details self.requestnext(); } else { delete(self.favorites[guid]); var index = -1; for (var cnt = 0; cnt < self.favoriteslist.length && index === -1; cnt++) { if (guid === self.favoriteslist[cnt]) { index = cnt; } } if (index !== -1) self.favoriteslist.splice(index,1); } self.storefavorites(); self.updateselector(); }; self.updateselector = function() { var guid = window.selectedPortal; $('.' + self.id + 'Selector').removeClass('favorite'); if (guid && self.favorites[guid]) { $('.' + self.id + 'Selector').addClass('favorite'); //document.forms[self.id + 'form'][self.id + 'selector'].disabled = false; //document.forms[self.id + 'form'][self.id + 'selector'].checked = (self.favorites[guid] !== undefined); //} else { //document.forms[self.id + 'form'][self.id + 'selector'].disabled = true; //document.forms[self.id + 'form'][self.id + 'selector'].checked = false; } if ($('#' + self.id + 'list').length > 0) $('#' + self.id + 'list').html(self.favoriteshtml()); if ($('#' + self.id + 'orderlist').length > 0) $('#' + self.id + 'orderlist').html(self.ordermenuhtml()); }; self.onPortalDetailLoaded = function(data) { if (!(data instanceof Object)) return; if (!data.details || !data.details.title || !data.guid) { // console.log('FAVORITE PORTAL DETAILS onPortalDetailLoaded failed',data); return; } var guid = data.guid; if (guid in self.favorites) { if (self.favorites[guid].title != data.details.title) { self.favorites[guid].title = data.details.title; self.storefavorites(); } self.favorites[guid].team = data.details.team; self.favorites[guid].level = (data.details.team === 'N'?0:data.details.level); self.favorites[guid].resonators = []; for (let cnt = 0; cnt < 8; cnt++) { if (data.details.resonators[cnt] && data.details.resonators[cnt].owner) { self.favorites[guid].resonators[cnt] = Object.assign({},data.details.resonators[cnt]); } else { self.favorites[guid].resonators[cnt] = {owner:'',level:'',energy:''}; } } self.favorites[guid].mods = []; for (let cnt = 0; cnt < 4; cnt++) { if (data.details.mods[cnt]) { self.favorites[guid].mods[cnt] = Object.assign({},data.details.mods[cnt]); } else { self.favorites[guid].mods[cnt] = {owner:'',name:'',rarity:''}; } } self.favorites[guid].owner = data.details.owner; self.favorites[guid].health = data.details.health; self.favorites[guid].timestamp = new Date(); self.favorites[guid].lat = data.details.latE6 / 1E6; self.favorites[guid].lng = data.details.lngE6 / 1E6; } if (guid === self.requestguid) { window.clearTimeout(self.requesttimerid); self.requesttimerid = 0; delete(self.requestlist[self.requestguid]); self.updateselector(); window.setTimeout(self.requestnext,0); } }; self.onPortalSelected = function() { //$('input[name=' + self.id + 'selector]').attr('checked',false); //$('input[name=' + self.id + 'selector]').attr('disabled',true); //document.forms[self.id + 'form'][self.id + 'selector'].disabled = true; //document.forms[self.id + 'form'][self.id + 'selector'].checked = false; if (self.onPortalSelectedPending) return; $('.' + self.id + 'Selector').remove(); if (!window.selectedPortal) return; self.onPortalSelectedPending = true; window.setTimeout(function() { // the sidebar is constructed after firing the hook self.onPortalSelectedPending = false; $('.' + self.id + 'Selector').remove(); // just in case $('#portaldetails > h3.title').before(self.selector); self.updateselector(); },0); }; self.onPaneChanged = function(pane) { if (pane === self.panename) self.menu(); else $('#' + self.id + 'menu').remove(); }; self.refreshonload = function() { if (!self.refreshonload_runonce) return; if (Object.keys(window.portals).length > 0 && self.settings.refreshonstart) { self.requestall(); } self.refreshonload_runonce = false; }; self.setup = function() { if ('pluginloaded' in self) { console.log('IITC plugin already loaded: ' + self.title + ' version ' + self.version); return; } else { self.pluginloaded = true; } self.restoresettings(); self.restorefavorites(); self.storefavorites(); if (window.useAndroidPanes()) { android.addPane(self.panename, self.title, 'ic_action_view_as_list'); addHook("paneChanged",self.onPaneChanged); } $('#toolbox').append('' + self.title + ''); $('