1. // ==UserScript==
  2. // @name Improve SCC
  3. // @description Adds shortcut buttons; Colour codes search results; additional package history information; and more…
  4. // @version 13.0.4
  5. // @author David King (dkingamz@)
  6. // Translator-de Stephanie Petke (petke@)
  7. // Translator-es Carlos de la Peña (cacalzad@)
  8. // Translator-fr Karl-Emmanuel Etifier (karleme@)
  9. // Translator-it Andrea La Rosa (anrosam@)
  10. // Thanks-cortex Dennis Leonhardt (leoden@)
  11. // @grant GM.xmlHttpRequest
  12. // @grant GM.info
  13. // @grant GM.setValue
  14. // @grant GM_getValue
  15. // @connect midway-auth.amazon.com
  16. // @connect routingtools-viz-dub.dub.proxy.amazon.com
  17. // @connect logistics.amazon.co.uk
  18. // @connect ie-logistics.amazon.co.uk
  19. // @connect logistics.amazon.de
  20. // @connect at-logistics.amazon.de
  21. // @connect logistics.amazon.es
  22. // @connect logistics.amazon.fr
  23. // @connect logistics.amazon.it
  24. // @connect logistics.amazon.nl
  25. // @connect logistics.amazon.sa
  26. // @connect logistics.amazon.com
  27. // @connect logistics.amazon.com.mx
  28. // @connect logistics.amazon.ca
  29. // @connect logistics.amazon.com.br
  30. // @connect logistics.amazon.in
  31. // @connect logistics.amazon.ae
  32. // @connect logistics.amazon.eg
  33. // @connect logistics.amazon.co.jp
  34. // @connect logistics.amazon.com.au
  35. // @connect gamtools-eu.aka.amazon.com
  36. // @connect gamtools-na.aka.amazon.com
  37. // @connect eagleeye-eu.amazon.com
  38. // @connect eagleeye-na.amazon.com
  39. // @connect geoweb-eu.amazon.com
  40. // @connect geoweb-na.amazon.com
  41. // @connect amazonlogistics-eu.com
  42. // @connect amazonlogistics.com
  43. // @connect pandash.amazon.com
  44. // @connect corp.amazon.com
  45. // @connect last-mile.amazon.dev
  46. // @connect trans-logistics-eu.amazon.com
  47. // @connect trans-logistics.amazon.com
  48. // @connect routingtools-viz-dub.dub.proxy.amazon.com
  49. // @connect routingtools-viz-iad.iad.proxy.amazon.com
  50. // @connect transportation-taxonomy-dub.aka.amazon.com
  51. // @connect transportation-taxonomy-iad.aka.amazon.com
  52. // @connect hero.eu.picking.aft.a2z.com
  53. // @connect hero.na.picking.aft.a2z.com
  54. // @connect trans-app-prod-eu.amazon.com
  55. // @connect trans-app-prod-na.amazon.com
  56. // @connect honeypot.amazon.dev
  57. // @run-at document-end
  58. // ==/UserScript==
  59.  
  60. //#region Utilities/Types
  61. /// <reference path="./GM.d.ts" />
  62. /// <reference path="./simple-dom.d.ts" />
  63. /// <reference path="./Improve-SCC.d.ts" />
  64. const { _, _svg, $, $$, wait$, delay, _css, O, dict, lock } = simpleDOM;
  65. const
  66. /** @type {<A extends unknown[]>(...bindArgs: A) => <R extends unknown>(cb: (...boundArgs: A) => R) => R} */
  67. bindWith = (...args) => (cb) => cb(...args),
  68. /** @type {(href:string, filename?:string)=>void} */
  69. download = (href, filename) => _('a', { href, download: filename ?? "" }).do('click').remove();
  70. //#endregion
  71.  
  72. //#region: Config data
  73. const
  74. debug = GM_getValue('debugMode', false),
  75. ver = GM.info.script.version,
  76. s = dict({
  77. en: {
  78. //Script Name broken into 3 pieces Text before phonetool link, phonetool link, and text after phonetool link
  79. scriptNameBefore: 'Improve SCC Script by ',
  80. scriptNameAfter: '',
  81. translatedBy: 'Translated by {{name}}',
  82. translatorName: 'David King',
  83. translatorAlias: 'dkingamz',
  84. formatTable: 'Format History Table:',
  85. onRoadOption: 'On Road',
  86. inStationOption: 'In Station',
  87. pleaseWait: 'Please Wait',
  88. EagleEye: 'EagleEye',
  89. EagleEye2: 'EagleEye 2.0',
  90. getContents: 'Get Package Contents',
  91. CSC: 'CSC',
  92. SIM: 'Search SIM Issues',
  93. COMP: 'COMP',
  94. Hero: 'Hero',
  95. SlamOps: 'SlamOps',
  96. DEXTER: 'DEXTER',
  97. FixIt: 'Fixit Details & History (Rodeo)',
  98. TT: 'Troubleshooting Tool',
  99. OBLT: 'Outbound Lookup Tool',
  100. GDE: 'Geo Data Editor',
  101. oId: 'Order ID: ',
  102. customerId: 'Customer ID: ',
  103. sameOrderID: 'Same Order ID: ',
  104. shipMethod: 'Ship Method: ',
  105. shipOption: 'Ship Option: ',
  106. serviceType: 'Service Type: ',
  107. lTId: 'Linked Tracking ID: ',
  108. AVD: 'AVD',
  109. OTP: 'OTP',
  110. SIG: 'Signature',
  111. HAZMAT: 'HAZMAT',
  112. HV: 'High Value',
  113. VH: 'Very Heavy',
  114. H: 'Heavy',
  115. LB: 'Letterbox Sized',
  116. OVER: 'Oversize',
  117. metPDD: 'Met Promise Date',
  118. metEAD: 'Met Estimated Date',
  119. missPDD: 'Missed Promise Date',
  120. missEAD: 'Missed Estimated Date',
  121. calcText: '(approx. {{ft#}} / {{m#}} from delivery location', // move the {{}} in the sentence, these will be filled with the correct numbers
  122. BH: 'Business hours: ',
  123. Driver: 'Driver: ',
  124. Route: 'Route Code: ',
  125. RouteVis: 'Route Visualisation',
  126. RouteDiver: 'Route Diver',
  127. RouteEagle: 'RouteIQ: Eagle',
  128. cortexItinerary: 'Driver itinerary (Cortex)',
  129. cortexLinkRoute: 'Cortex (Route)',
  130. cortexLinkStop: 'Cortex (Stop {{stop#}})', // move the {{}} in the sentence, this will be filled with the correct number
  131. GAM: 'GAM Tools',
  132. Hours: 'Hours:',
  133. LockerStatus: 'Locker Status',
  134. LockerReserve: 'Locker Reservation',
  135. Address: 'Address',
  136. Locker: 'Locker',
  137. Counter: 'Counter',
  138. LatLon: 'Lat Lon',
  139. GAM404: 'Address not in GAM',
  140. GAMnoHours: 'No Hours in GAM',
  141. FailedAttempt: 'Failed Attempts:',
  142. GAMhint: 'GAM DELIVERY_HINT',
  143. qrCode: 'QR Code',
  144. viewPOD: 'View POD Images',
  145. honeypot: 'Honeypot',
  146. GCRS: 'GCRS',
  147. Passage: 'Passage',
  148. RoyalMail: 'Royal Mail',
  149. TransTax: 'Transportation Taxonomy',
  150. plannedTime: 'Planned Delivery Time: {{time}}', // move the {{}} in the sentence, this will be filled with the correct time
  151. plannedStartTime: 'Planned Start Time: {{time}}', // move the {{}} in the sentence, this will be filled with the correct time
  152. windowTime: 'Time Window: {{time}}', // move the {{}} in the sentence, this will be filled with the correct time
  153. delTo: 'Delivered To',
  154. NoHistData: 'No History Data: {{reason}}', // move the {{}} in the sentence, this will be filled with the correct reason
  155. NoSiteData: 'No Sitemap Data: {{reason}}', // move the {{}} in the sentence, this will be filled with the correct reason
  156. NoPackData: 'No Package Data: {{reason}}', // move the {{}} in the sentence, this will be filled with the correct reason
  157. NoGeoData: 'No Geo Data: {{reason}}', // move the {{}} in the sentence, this will be filled with the correct reason
  158. NoGAMData: 'No GAM Data: {{reason}}', // move the {{}} in the sentence, this will be filled with the correct reason
  159. },
  160. 'en-US': {
  161. RouteVis: 'Route Visualization',
  162. },
  163. de: {
  164. scriptNameBefore: 'Improve SCC Script von ',
  165. scriptNameAfter: '',
  166. translatedBy: 'Translated von {{name}}',
  167. translatorName: 'Stephanie Petke',
  168. translatorAlias: 'petke',
  169. formatTable: 'Format History Tabelle:',
  170. onRoadOption: 'On Road',
  171. inStationOption: 'In der Station',
  172. pleaseWait: 'Bitte warten',
  173. getContents: 'Inhalt anzeigen',
  174. metPDD: 'PDD eingehalten',
  175. metEAD: 'EAD eingehalten',
  176. missPDD: 'PDD verfehlt',
  177. missEAD: 'EAD verfehlt',
  178. calcText: '(ca. {{ft#}} / {{m#}} vom Geopunkt',
  179. BH: 'Öffnungszeiten: ',
  180. Hours: 'Uhrzeit:',
  181. LockerReserve: 'Locker Reservierung',
  182. Address: 'Adresse',
  183. LatLon: 'Breiten/Längengrad',
  184. GAM404: 'Addresse nicht in GAM',
  185. GAMnoHours: 'Keine Öffnungszeiten in GAM',
  186. FailedAttempt: 'fehlgeschlagene Zustellversuche:',
  187. },
  188. es: {
  189. translatedBy: 'Traducido por {{name}}',
  190. translatorName: 'Carlos de la Peña',
  191. translatorAlias: 'cacalzad',
  192. formatTable: 'Formato de tabla:',
  193. onRoadOption: 'En carretera',
  194. inStationOption: 'En estación',
  195. pleaseWait: 'Por favor espere',
  196. getContents: 'Obtener productos',
  197. FixIt: 'Detalles & Histórico FixIt (Rodeo)',
  198. GDE: 'Editor Geo Data',
  199. oId: 'Número de Pedido: ',
  200. shipMethod: 'Método de envío: ',
  201. lTId: 'Código ID relacionado: ',
  202. HV: 'Alto Valor',
  203. VH: 'Muy Pesado',
  204. H: 'Pesado',
  205. LB: 'Sobre',
  206. OVER: 'Voluminoso (Oversize)',
  207. metPDD: 'Fecha Entrega Prometida Cumplida',
  208. metEAD: 'Fecha Llegada Estimada Cumplida',
  209. missPDD: 'Fecha Entrega Prometida Fallada',
  210. missEAD: 'Fecha Entrega Estimada Fallada',
  211. calcText: '(aprox. {{ft#}} / {{m#}} del punto de entrga',
  212. BH: 'Horario de Apertura: ',
  213. RouteVis: 'Visualizador de Ruta',
  214. cortexItinerary: 'Itinerario del Conductor (Cortex)',
  215. cortexLinkRoute: 'Cortex (Ruta)',
  216. cortexLinkStop: 'Cortex (Parada {{stop#}})',
  217. Hours: 'Horario:',
  218. LockerStatus: 'Estado del locker',
  219. Address: 'Dirección',
  220. LatLon: 'Lat y Long',
  221. GAM404: 'Dirección no en GAM',
  222. GAMnoHours: 'Horario no en GAM',
  223. FailedAttempt: 'Intento de Entrega Fallido:',
  224. qrCode: 'Código QR ',
  225. plannedTime: 'Hora Prevista de Reparto: {{time}}',
  226. plannedStartTime: 'Planned Start Time: {{time}}',
  227. },
  228. fr: {
  229. translatorName: 'etifier, karl-emmanuel',
  230. translatorAlias: 'karleme',
  231. },
  232. it: {
  233. translatorName: 'Andrea La Rosa',
  234. translatorAlias: 'anrosam',
  235. formatTable: 'Formato Tabella Cronologica:',
  236. onRoadOption: 'In Transito',
  237. inStationOption: 'In Station',
  238. pleaseWait: 'Prego Attendere',
  239. getContents: 'Vedi Contenuto Pacco',
  240. FixIt: 'Dettaglio e Cronologia da Fixit (Rodeo)',
  241. oId: 'ID Ordine: ',
  242. lTId: 'Tracking Id Collegato: ',
  243. HV: 'Alto Valore',
  244. VH: 'Molto Pesante',
  245. H: 'Pesante',
  246. LB: 'Tasca Di Libro',
  247. metPDD: 'Data Promessa Corretta',
  248. metEAD: 'Data Stimata Corretta',
  249. missPDD: 'Data Promessa Mancata',
  250. missEAD: 'Data Stimata Mancata',
  251. calcText: '(appross. {{ft#}} / {{m#}} dall\'indirizzo di spedizione',
  252. BH: 'Orario d\'Apertura: ',
  253. RouteVis: 'Visualizza Rotta',
  254. cortexLinkRoute: 'Cortex (Rotta)',
  255. cortexLinkStop: 'Cortex (Fermata {{stop#}})',
  256. Hours: 'Orario Tentativi:',
  257. LockerStatus: 'Stato del Locker',
  258. LockerReserve: 'Locker Reservation',
  259. Address: 'Indirizzo',
  260. LatLon: 'Lat e Lon',
  261. GAM404: 'Indirizzo non in GAM',
  262. GAMnoHours: 'Orario non in GAM',
  263. FailedAttempt: 'Tentativi di Consegna Falliti:',
  264. },
  265. jp: {
  266. translatedBy: 'Translated by {{name}}',
  267. translatorName: 'Saki Uchida',
  268. translatorAlias: 'duchidsa',
  269. }
  270. }),
  271. /** Strings that must match what's already in SCC so that the script can detect it. */
  272. searchStrings = {
  273. en: {
  274. //Search/Package details
  275. searchHeader: 'Search', //The header at the top of the search page
  276. packageHeader: 'Package', //(partial match)The header at the top of the detail (search results) page
  277. GAMdays: '|SUN|MON|TUE|WED|THU|FRI|SAT|', //Days as seen in GAM Business Hours separated by |. I've tested and it seems these will probably match english.
  278. ShipMethod: 'Ship Method', //Search results column header
  279. //Outbound > Pick/Stage tabs
  280. associate: 'Associate',
  281. cartID: 'Cart ID',
  282. driver: 'Driver',
  283. //Problem Solve tabs
  284. Reportedby: 'Reported by'
  285. },
  286. de: {
  287. //Search/Package details
  288. searchHeader: 'Suche',
  289. packageHeader: 'Paket',
  290. ShipMethod: 'Ship Method',
  291. //Outbound > Pick/Stage tabs
  292. associate: 'Mitarbeiter',
  293. cartID: 'Wagen-ID',
  294. //Problem Solve tabs
  295. Reportedby: 'Reported by'
  296. },
  297. es: {
  298. //Search/Package details
  299. searchHeader: 'Búsqueda',
  300. packageHeader: 'Paquete',
  301. ShipMethod: 'Ship Method',
  302. //Outbound > Pick/Stage tabs
  303. associate: 'Asociado',
  304. cartID: 'Código ID Carro',
  305. //Problem Solve tabs
  306. Reportedby: 'Reportado por'
  307. }, fr: {
  308. //Search/Package details
  309. searchHeader: 'Rechercher',
  310. packageHeader: 'Colis',
  311. ShipMethod: 'Ship Method',
  312. //Outbound > Pick/Stage tabs
  313. associate: 'Associé',
  314. cartID: 'ID de l\'itinéraire',
  315. //Problem Solve tabs
  316. Reportedby: 'Signalé par'
  317. },
  318. it: {
  319. //Search/Package details
  320. searchHeader: 'Cerca',
  321. packageHeader: 'Pacco',
  322. ShipMethod: 'Ship Method',
  323. //Outbound > Pick/Stage tabs
  324. associate: 'Associate',
  325. cartID: 'ID carrello',
  326. //Problem Solve tabs
  327. Reportedby: 'Reported by'
  328. },
  329. jp: {
  330. //Search/Package details
  331. searchHeader: '検索',
  332. packageHeader: 'パッケージ',
  333. ShipMethod: 'パッケージ概要',
  334. //Outbound > Pick/Stage tabs
  335. associate: 'アソシエイト',
  336. cartID: 'ルートID',
  337. driver: 'ドライバー',
  338. //Problem Solve tabs
  339. Reportedby: '修正対象'
  340. }
  341. },
  342. /** @type {string[]} */
  343. logData = [],
  344. /** @type {(...log: any[]) => void} */
  345. debuglog = debug ? (...log) => {
  346. logData.push(`${Date.now()}: ${log.map(line => {
  347. try { return JSON.stringify(line); } catch (e) { return line.toString(); }
  348. }).join('\n\t')}`);
  349. console.log(location.origin, '[Improve-SCC]', ...log);
  350. } : (...log) => { },
  351. superlog = debug ? debuglog : console.log,
  352. mutationComment = document.createComment("Mutate");
  353. //#endregion
  354.  
  355. //#region: Global Defaults, State, and Constants
  356. let
  357. /** @type {PageInfo} */ page,
  358. /** @type {SCCPromises} */ promises,
  359. /** @type {WindowProxy?} */ midwayWindow = null,
  360. /** @type {string?} */ globalOrigin,
  361. /** @type {Notification?} */ notification;
  362. function defaults() {
  363. debuglog('defaults()');
  364. page = { url: window.location.href.split('/') };
  365. promises = /** @type {SCCPromises} */ ({});
  366. midwayWindow = null;
  367. }
  368. const now = new Date().getTime();
  369. //#endregion
  370.  
  371. //#region: Prototypes and DOM Functions
  372. const
  373. /** @type {FormatterFunction} */
  374. dateTimeFormatter = options => {
  375. const /** @type {Record<string, Intl.DateTimeFormat>} */ cache = {};
  376. return (value, timeZone) => ((`${timeZone}`) in cache ? cache[`${timeZone}`] : (cache[`${timeZone}`] = new Intl.DateTimeFormat(navigator.languages, { ...options, timeZone }))).format((typeof value === 'string') ? new Date(value) : value);
  377. },
  378. toLocaleTZString = dateTimeFormatter({ hour: '2-digit', minute: '2-digit', second: '2-digit', }),
  379. toWeekdayDateString = dateTimeFormatter({ weekday: "short", year: "2-digit", month: "2-digit", day: "2-digit", }),
  380. toWeekdayDateTimeString = dateTimeFormatter({ weekday: "short", year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: '2-digit' });
  381. class ExtendedDate extends Date {
  382. toISODateString() { return `${this.getFullYear()}-${(this.getMonth() + 1).toString().padStart(2, "0")}-${this.getDate().toString().padStart(2, "0")}`; }
  383. toISODateTimeString() { return `${this.toISODateString()} ${this.getHours().toString().padStart(2, "0")}:${this.getMinutes().toString().padStart(2, "0")}:${this.getSeconds().toString().padStart(2, "0")}`; }
  384. /** @type {DateToString} */
  385. toYMDKey(timeZone) { return `${this.toLocaleString('en-GB', { timeZone, year: 'numeric' })}${this.toLocaleString('en-GB', { timeZone, month: '2-digit' })}${this.toLocaleString('en-GB', { timeZone, day: '2-digit' })}`; }
  386. toMidnightLocal() { return new Date(this.getFullYear(), this.getMonth(), this.getDate()).valueOf(); }
  387. };
  388. /** @type {(value?: number | string | Date) => ExtendedDate} */
  389. const D = (value) => value ? new ExtendedDate(value) : new ExtendedDate();
  390. class ExtendedString extends String {
  391. /** @type {TestString} */
  392. in(test) { return test.toLowerCase().includes(this.toLowerCase()); }
  393. /** @type {TestString} */
  394. contains(test) { return this.toLowerCase().includes(test.toLowerCase()); }
  395. /** @type {(...tests: string[]) => boolean} */
  396. containsAny(...tests) { return tests.reduce((bool, test) => bool || this.contains(test), false); }
  397. toSentenceCase() { return `${this.charAt(0).toUpperCase()}${this.slice(1).toLowerCase()}`; }
  398. toWordCase() { return this.toLowerCase().replace(/(^|\s)\S/g, letter => letter.toUpperCase()); }
  399. /** @type {TestString} */
  400. inDict(key) { return O(searchStrings).reduce((bool, [_, langDict]) => (bool || this.in(langDict[key])), false); }
  401. /** @type {TestString} */
  402. dictContains(key) { return O(searchStrings).reduce((bool, [_, langDict]) => (bool || this.contains(langDict[key])), false); };
  403. }
  404. /** @type {(value: any) => ExtendedString} */
  405. const S = (value) => new ExtendedString(value);
  406. const /** @type {(test: string)=>boolean} */ hrefContains = (test) => S(location.href).contains(test);
  407.  
  408. // Cortex auth/signing code provided by Dennis Leonhardt (leoden@)
  409. let /** @type {CortexSession | undefined} */ cortexAuthPromise = undefined;
  410. /** @type {()=>CortexSession} */
  411. const initializeCortexAuth = () => cortexAuthPromise ?? (cortexAuthPromise = XHR('GET', `${globalOrigin}/internal/operations/execution`).then(
  412. (response) => new Promise((resolve) => ([...new DOMParser().parseFromString(response.text, 'text/html').querySelectorAll('script[type="a-state"][data-a-state*="hmacSession"]')].reduce((flagFound, script) => {
  413. if (flagFound) return flagFound;
  414. const data = JSON.parse(script.textContent.trim());
  415. if (!(data.hmacSession && data.hmacSession.enabled)) return false;
  416. crypto.subtle.importKey(
  417. 'raw',
  418. new TextEncoder().encode(data.hmacSession.sessionKey),
  419. { name: 'HMAC', hash: 'SHA-256' },
  420. false,
  421. ['sign']
  422. ).then(cryptoKey => (debuglog(data.hmacSession), resolve({ sessionKey: data.hmacSession.sessionKey, sessionId: data.hmacSession.sessionId, cryptoKey })));
  423. return true;
  424. }, false)))
  425. ).catch(
  426. error => debuglog('Cortex authentication failed:', error)
  427. ));
  428. /** @type {(method: "GET"|"POST", url: string, json?: any) => Promise<XHRResponse>} */
  429. const XHR = (method, url, json) => (!globalOrigin || !url.includes(globalOrigin) || location.origin === globalOrigin) ? systemXHR(method, url, json) : new Promise((resolve, reject) => {
  430. debuglog(`${method} XHR Request: ${url}`, json);
  431. const id = `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 10)}`,
  432. /** @type {(data:any)=>void} */ handler = ({ data }) => {
  433. if (data.method !== 'XHR') return;
  434. if (data.id !== id) return;
  435. debuglog(`${data.response.status} XHR Response: ${url}`, data.response);
  436. window.removeEventListener("message", handler);
  437. (data.response.ok ? resolve : reject)({ ...data.response, json: () => JSON.parse(data.response.text) });
  438. };
  439. debuglog('Posting XHR', id, url);
  440. window.addEventListener("message", handler);
  441. window.parent.postMessage({ method: 'XHR', id, params: { method, url, json } }, globalOrigin ?? '');
  442. });
  443.  
  444. /** @type {(method: "GET"|"POST", url: string, json?: any) => Promise<XHRResponse>} */
  445. const systemXHR = (method, url, json) => new Promise((resolve, reject) => {
  446. debuglog(`${method} XHR Request: ${url}`, json);
  447. /** @type {XHROptions} */
  448. const options = {
  449. method, url, onload: (response) => {
  450. debuglog(`${response.status} XHR Response: ${url}`, response.responseText);
  451. const r = {
  452. status: response.status,
  453. statusText: response.statusText,
  454. text: response.responseText,
  455. ok: response.status >= 200 && response.status <= 299,
  456. json: () => JSON.parse(response.responseText)
  457. };
  458. (r.ok ? resolve : reject)(r);
  459. },
  460. headers: {}
  461. };
  462. json !== null && (options.headers['Content-Type'] = 'application/json', options.data = (typeof json) === 'string' ? json : JSON.stringify(json));
  463. // Cortex auth/signing code provided by Dennis Leonhardt (leoden@)
  464. (url.includes(`${globalOrigin}/internal/operations/execution/api`) ? initializeCortexAuth().then(({ sessionId, cryptoKey }) => {
  465. const { pathname, searchParams } = new URL(url), timestamp = Date.now().toString();
  466. return crypto.subtle.sign(
  467. 'HMAC',
  468. cryptoKey,
  469. new TextEncoder().encode([
  470. method,
  471. pathname,
  472. searchParams.toString(),
  473. `x-cortex-session:${sessionId}`,
  474. `x-cortex-timestamp:${timestamp}`,
  475. '',
  476. 'x-cortex-session;x-cortex-timestamp'
  477. ].join('\n'))
  478. ).then(signature => {
  479. options.headers['X-Cortex-Hmac-Signature'] = btoa(String.fromCharCode(...new Uint8Array(signature)));
  480. options.headers['X-Cortex-Session'] = sessionId;
  481. options.headers['X-Cortex-Timestamp'] = timestamp;
  482. }).finally(() => debuglog(`${method} XHR Headers: ${url}`, options.headers));
  483. }) : Promise.resolve()).then(() => {
  484. GM.xmlHttpRequest(options);
  485. delay(10).then(() => reject(`Timed-out: ${method} ${url}`));
  486. }, reject);
  487. });
  488. /** @type {<T>({json}:{json:()=>T})=>T} */
  489. const json = ({ json }) => json();
  490. const
  491. unitMap = { cm: 'centimeter', kg: 'kilogram' },
  492. /** @type {({value, unit}:{value:number, unit:string})=>string} */
  493. formatUnit = ({ value, unit }) => {
  494. try {
  495. if (unit in unitMap) unit = unitMap[unit];
  496. return new Intl.NumberFormat(navigator.languages, { style: 'unit', unit }).format(value).replace(/\s/g, '\u00A0');
  497. } catch (e) {
  498. debuglog(e);
  499. return `${value}_${unit}`;
  500. }
  501. },
  502. /** @type {({value, currency}:{value:number, currency:string})=>string} */
  503. formatCurrency = ({ value, currency }) => {
  504. try {
  505. return new Intl.NumberFormat(navigator.languages, { style: 'currency', currency }).format(value).replace(/\s/g, '\u00A0');
  506. } catch (e) {
  507. debuglog(e);
  508. return `${value}_${currency}`;
  509. }
  510. };
  511. //#endregion
  512.  
  513. //#region: Pages and Tabs
  514. function startup() {
  515. wait$('html').then(() => {
  516. if (hrefContains('logistics.amazon.')) return SCCStartup();
  517. document.documentElement.dataset.improveSccCssIframe = '';
  518. window.addEventListener("message", ({ data }) => {
  519. switch (data.method) {
  520. case 'sendDarkModeStatus': return void (document.documentElement.dataset.improveSccCssDarkMode = data.childData.capability);
  521. case 'sendOrigin': return void (globalOrigin = data.childData);
  522. case 'sendLocaleResponse': return;
  523. default:
  524. debuglog('postMessage', data);
  525. }
  526. });
  527. new Promise((resolve) => {
  528. const interval = setInterval(() => {
  529. if (!globalOrigin) return;
  530. clearInterval(interval);
  531. resolve(globalOrigin);
  532. debuglog('Origin:', globalOrigin);
  533. }, 500);
  534. }).then(initialise);
  535. });
  536. }
  537. function SCCStartup() {
  538. document.documentElement.dataset.improveSccCssNoframe = '';
  539. globalOrigin = location.origin;
  540. console.log(`%c${s('scriptNameBefore')} David King (dkingamz@) ${s('scriptNameAfter')} %c${ver}`, 'font-size: 200%; font-weight: bold;', 'font-size: 200%; color: red; text-decoration: underline;');
  541. console.log(`%c${s('translatedBy').replace('{{name}}', `${s('translatorName')} (${s('translatorAlias')}@)`)}`, 'font-size: 150%');
  542. console.log(`%cFormatting: ${new Intl.ListFormat(navigator.languages, { type: 'conjunction' }).format(navigator.languages.map(lang => new Intl.DisplayNames(navigator.languages, { type: 'language' }).of(lang)).filter(lang => lang !== undefined))}`, 'font-size: 125%; color: cyan;');
  543. wait$('.boson-meridian-frame-dimension', { style: { top: '' } });
  544. wait$('#main_row').then(mainRow => {
  545. const verdiv = _('div', { id: 'verdiv' })._(_('strong')._(
  546. s('scriptNameBefore'), _('a', { href: 'https://phonetool.amazon.com/users/dkingamz', target: '_blank' })._('David King (dkingamz@)'), s('scriptNameAfter'), `\u2003 ${ver}`,
  547. )).on('click', () => verdiv.classList.toggle('hidden'));
  548. mainRow._(verdiv);
  549. new MutationObserver(() => {
  550. }).observe(mainRow, { characterData: false, attributes: false, childList: true, subtree: true });
  551. });
  552. wait$('ul[role=tree]').then(ul => {
  553. $("#Help > div > span")?._(' (River)');
  554. const updateAvailableLi = _('li');
  555. let lastCheckDate, /** @type {string?}*/ master;
  556. const updateAvailable = () => {
  557. updateAvailableLi.__(master && GM.info.script.downloadURL && (ver == master ? null : _('a', { href: GM.info.script.downloadURL, target: '_blank', classList: ['css-11g0wa3'] })._(_('div', { classList: ['css-1u8dqdz'] })._(_('span', { classList: ['css-1bkd0di'] })._(`Update to version ${master}`)))));
  558. }, updateCheck = () => GM.info.script.downloadURL ? XHR('GET', GM.info.script.downloadURL).then(response => {
  559. master = (/@version\s+(.*)/i.exec(response.text) ?? '')[1];
  560. debuglog(`masterVer: ${master}`);
  561. if (!master) return;
  562. localStorage.setItem('ImproveSCCupdateTimestamp', `${Date.now()}`);
  563. localStorage.setItem('ImproveSCCmaster', master);
  564. }) : Promise.resolve();
  565. ((master = localStorage.getItem('ImproveSCCmaster')) && ((lastCheckDate = localStorage.getItem('ImproveSCCupdateTimestamp')) && Number(lastCheckDate) > Date.now() - 24 * 60 * 60 * 1000) ? () => Promise.resolve() : updateCheck)().then(updateAvailable);
  566. ul._(
  567. ($('#dkingamzNav') || _('div', { id: 'dkingamzNav' })).__(
  568. _('strong')._(s('scriptNameBefore'), _('a', { href: 'https://phonetool.amazon.com/users/dkingamz', target: '_blank' })._('David King (dkingamz@)'), s('scriptNameAfter'), `\u2003 ${ver}`,),
  569. _('li')._(_('button', { classList: ['css-11g0wa3'] })._(_('div', { classList: ['css-1u8dqdz'] })._(_('span', { classList: ['css-1bkd0di'] })._('Check For Updates'))).on('click', () => updateCheck().then(updateAvailable))),
  570. updateAvailableLi,
  571. _('li')._(_('a', { href: 'https://axzile.corp.amazon.com/-/carthamus/download_script/improve-scc-show-top-nav-menu.user.js', target: '_blank', classList: ['css-11g0wa3'] })._(_('div', { classList: ['css-1u8dqdz'] })._(_('span', { classList: ['css-1bkd0di'] })._('Show Top Nav Menu')))),
  572. _('li')._(_('button', { classList: ['css-11g0wa3'] })._(_('div', { classList: ['css-1u8dqdz'] })._(_('span', { classList: ['css-1bkd0di'] })._(`Toggle Debug Mode ${debug ? 'Off' : 'On'}`))).on('click', () => GM.setValue('debugMode', !debug).then(() => location.reload(true)))),
  573. debug && _('li')._(_('button', { classList: ['css-11g0wa3'] })._(_('div', { classList: ['css-1u8dqdz'] })._(_('span', { classList: ['css-1bkd0di'] })._('Export Improve-SCC Sidebar logs'))).on('click', () => {
  574. const modal = _('dialog', { style: { background: 'canvas' } })._(
  575. _('div', { id: 'debugModalButtons' })._(
  576. _('button')._('Export').on('click', () => bindWith(URL.createObjectURL(new Blob([logData.join('\n')], { type: 'text/plain' })))(blobUrl => (download(blobUrl, `Improve-SCC-Log-Export${Date.now()}.txt`), URL.revokeObjectURL(blobUrl)))),
  577. _('button')._('Close').on('click', () => modal.close()),
  578. ),
  579. ...logData.map(line => _('div')._(line)),
  580. ).on('close', () => modal.remove());
  581. $('body')?._(modal);
  582. queueMicrotask(() => modal.showModal());
  583. })),
  584. _('li')._(_('a', { href: 'https://sim.amazon.com/issues/create?template=5ee8e705-a508-4820-86f4-1d87b978d630', target: '_blank', classList: ['css-11g0wa3'] })._(_('div', { classList: ['css-1u8dqdz'] })._(_('span', { classList: ['css-1bkd0di'] })._('Create SIM', _('br'), _('small')._('Improve SCC Script'))))),
  585. )
  586. );
  587. });
  588. wait$('body').then(body => {
  589. (new MutationObserver(() => (document.documentElement.dataset.improveSccCssDarkMode = body.classList.contains('dark-mode') ? 'dark' : 'light'))).observe(body, { attributes: true, subtree: false, childList: false, characterData: false });
  590. debug && body._(_('iframe', {
  591. dataset: { debug: '' },
  592. srcdoc: `<html><head><meta name="color-scheme" content="light dark"><style>:root{background:canvas}</style></head><body>${Date.now()}<br/>SCC: ${globalOrigin}<br/>UA: ${navigator.userAgent}<br/>Default Language: ${navigator.language}<br/>All Languages: ${JSON.stringify(navigator.languages)}</body></html>`,
  593. style: { position: 'fixed', insetInlineEnd: '1rem', insetBlockEnd: '1rem', width: '36rem', height: '12rem' }
  594. }));
  595. });
  596. hrefContains('/search') && (document.documentElement.dataset.improveSccCssHideAlerts = '');
  597. navigator.languages.includes('en-GB') && Promise.all([wait$('title'), wait$('h4')]).then(texts => texts.forEach(text => (text.textContent = text.textContent?.replace('Center', 'Centre') ?? 'SCC')));
  598. const iframe = () => wait$('iframe', undefined, 2).then(iframe => ({ contentWindow: iframe.contentWindow, origin: new URL(iframe.src).origin })).catch((...e) => { throw debuglog('Selecting iFrame', ...e); });
  599. window.addEventListener("message", ({ data }) => {
  600. switch (data.method) {
  601. case 'XHR':
  602. Promise.all([systemXHR(data.params.method, data.params.url, data.params.json).catch(r => r), iframe()]).then(([{ json, ...response }, { contentWindow, origin }]) => contentWindow?.postMessage({ method: 'XHR', id: data.id, response }, origin));
  603. break;
  604. case 'requestUsername':
  605. // wait$('iframe').then(iframe => iframe.contentWindow?.postMessage({ method: "sendUsername", childData: sessionStorage.getItem('boson.tcpEmployeeLogin') }, new URL(iframe.src).origin));
  606. break;
  607. default:
  608. debuglog('postMessage', data);
  609. }
  610. });
  611. setInterval(() => iframe().then(({ contentWindow, origin }) => contentWindow?.postMessage({ method: "sendOrigin", childData: globalOrigin }, origin)), 1000);
  612.  
  613. }
  614. function initialise() {
  615. defaults();
  616. debuglog('initialise()');
  617. wait$('body').then(body => {
  618. const oldURL = window.location.href;
  619. new MutationObserver((_, observer) => {
  620. // debuglog(`${oldURL} - ${window.location.href}`);
  621. if (oldURL != window.location.href && !(hrefContains('receive'))) {//hrefContains('ui.package-summary-')) {
  622. observer.disconnect();
  623. window.ImproveSCC = false;
  624. startup();
  625. }
  626. }).observe(body, { characterData: true, attributes: false, childList: true, subtree: true });
  627. debug && body._(_('button', { id: 'exportLogs', style: { background: 'canvas' } })._('Export Improve-SCC Page logs').on('click', () => {
  628. const modal = _('dialog', { style: { background: 'canvas' } })._(
  629. _('div', { id: 'debugModalButtons' })._(
  630. _('button')._('Export').on('click', () => bindWith(URL.createObjectURL(new Blob([logData.join('\n')], { type: 'text/plain' })))(blobUrl => (download(blobUrl, `Improve-SCC-Log-Export${Date.now()}.txt`), URL.revokeObjectURL(blobUrl)))),
  631. _('button')._('Close').on('click', () => modal.close()),
  632. ),
  633. ...logData.map(line => _('div')._(line)),
  634. ).on('close', () => modal.remove());
  635. body._(modal);
  636. queueMicrotask(() => modal.showModal());
  637. }));
  638. });
  639. if (hrefContains('/settings')) return;
  640. if (hrefContains('ui.package-summary-')) {
  641. page.region = window.location.href.split('ui.package-summary-')[1].split('.')[0];
  642. page.region == 'in' && (page.region = 'eu');
  643. page.regionCode = { na: 'iad', eu: 'dub', fe: 'PDX', cn: 'PEK' }[page.region];
  644. if (!hrefContains('packageSearch') && (hrefContains('ageingBoard') || hrefContains('scrubBoard'))) return void debuglog('kill searchLoad()');
  645. sessionStorage.setItem('loadCount', `${Number(sessionStorage.getItem('loadCount')) + 1}`);
  646. if (!(document?.featurePolicy?.allowsFeature?.('clipboard-write') ?? true)) { if (Number(sessionStorage.getItem('loadCount')) < 5) { location.reload(); return; } } else { sessionStorage.setItem('loadCount', '0'); }
  647. wait$('h1, h2, h3, table', undefined, 12).then(h => {
  648. debuglog(`main(${h.innerText})`);
  649. if (S(h.innerText).inDict('searchHeader')) return queueMicrotask(searchPage);
  650. if (S(h.innerText).dictContains('packageHeader')) return queueMicrotask(detailPage);
  651. }).catch(() => (debuglog("Can't find header… Reloading"), (debug ? confirm("Can't find header\n\nReload") : true) && location.reload(true)));
  652. return;
  653. }
  654. if (hrefContains('associate')) return associate();
  655. if (hrefContains('ui.pvs') || hrefContains('ui.stagewebsite') || hrefContains('outbound')) return outbound();
  656. if (hrefContains('ui.ivs') || hrefContains('prod.svs')) return associate();
  657. if (hrefContains('node-exceptions')) return psTab();
  658. if (hrefContains('sort-paths')) return;
  659. }
  660. async function detailPage() {
  661. if (promises.detailPage) return;
  662. promises.detailPage = true;
  663. page.trackingId = await wait$('h1').then((h1) => wait$('h1 > span').then(() => h1.lastChild?.textContent?.trim()));
  664. superlog(`-${page.trackingId}-`);
  665. debuglog('detailPage()');
  666. document.documentElement.dataset.improveSccCssDetailsPage = '';
  667. $('ul.css-1duca4b')?.__(historyFormatMenu().menuElement);
  668. promises.topBar = new Promise(resolve => {
  669. const improveBox = $('div#improveRoot') || _('div', { id: 'improveRoot' }), improveBoxes = {};
  670. wait$('#root [role=tablist]').then(tabList => tabList.before(improveBox));
  671. improveBox.__(
  672. improveBoxes.head = _('div', { id: 'improveHead' }),
  673. improveBoxes.items = _('div'),
  674. improveBoxes.transport = _('div', { id: 'transport' })._(improveBoxes.van = _('div')),
  675. improveBoxes.gam = _('div', { id: 'gamrow' }),
  676. improveBoxes.buttons = _('div', { id: 'buttons' }),
  677. improveBoxes.podContainer = _('div', { id: 'podContainer' }),
  678. improveBoxes.taxonomyError = _('div', { id: 'taxonomyError', classList: ['errMsg'] }),
  679. improveBoxes.histTableError = _('div', { id: 'histTableError', classList: ['errMsg'] })
  680. );
  681. resolve(improveBoxes);
  682. });
  683. promises.sitemap = siteMap();
  684. promises.geo = getGeoData();
  685. promises.package = getPackageData();
  686. promises.gam = promises.package.then(getGAMData);
  687. // promises.oblt = getOBLT();
  688. promises.package.then(addrbuts);
  689. promises.boxes = Promise.all([promises.topBar, promises.package]).then(usePackageData);
  690. promises.topBar.then((improveBoxes) => {
  691. XHR('POST', `${globalOrigin}/station/proxyapigateway/data`, { resourcePath: '/os/batchGetPackageSummary', httpMethod: 'post', processName: 'oculus', requestBody: { idType: 'TRACKING_ID', identifiers: [page.trackingId], includeFields: [] } }).then(json).then(({ packageSummaryList }) => packageSummaryList[0]).then(packageDetailData => {
  692. improveBoxes.van.__(
  693. packageDetailData.driverId && _('strong')._(packageDetailData.driverId),
  694. packageDetailData.currentRouteCode && _('span')._(`Route: ${packageDetailData.currentRouteCode}`),
  695. packageDetailData.routeSequence && _('span')._(`Route Sequence: ${packageDetailData.routeSequence}`),
  696. (packageDetailData.vehicleStopNumber || packageDetailData.taskStopNumber) && _('span')._(`Van Stop: ${packageDetailData.vehicleStopNumber}-(${packageDetailData.taskStopNumber})`)
  697. );
  698. });
  699. });
  700. promises.topBar.then(() => {
  701. specialButtons();
  702. });
  703. Promise.all([promises.topBar, promises.package]).then(([_, packageData]) => addButton(`https://trans-logistics${(page.region != 'na' ? `-${page.region}` : '')}.amazon.com/sortcenter/tantei?nodeId=${packageData.routeInfo.stationCode}&searchType=Container&searchId=${page.trackingId}`, s('TT'), 2));
  704. Promise.all([promises.boxes, promises.geo]).then(useGeoData);
  705. Promise.all([promises.topBar, promises.gam, promises.histPromise]).then(useGAMData);
  706. // Promise.all([promises.topBar, promises.oblt]).then(useOBLTData);
  707. // promises.oblt.catch((e) => promises.boxes?.then(improveBoxes => improveBoxes.customerId.__('OBLT Error', e)));
  708. wait$('#root').then(root => {
  709. debuglog('DetailMutationObserver');
  710. new MutationObserver((_, observer) => {
  711. if ($('#SCC_Search_CONFIG') && $('h1, h2')) return void (($('table:not([data-improve-scc=true]')) && setTimeout(histTable));
  712. observer.disconnect();
  713. initialise();
  714. }).observe(root, { characterData: true, attributes: false, childList: true, subtree: true });
  715. }).then(() => {
  716. debuglog('clicking History button');
  717. Promise.all([wait$('.css-1gnngbt > .css-r8kn69'), wait$('[role=tablist] label:nth-of-type(4)')]).then(([_, label]) => label.do('click'));
  718. });
  719. }
  720. function searchPage() {
  721. debuglog('searchPage()');
  722. document.documentElement.dataset.improveSccCssSearchPage = '';
  723. wait$('#root .css-1gnngbt').then(searchArea => {
  724. debuglog('SearchMutationObserver');
  725. new MutationObserver(() => {
  726. const resultsTable = $('table');
  727. if (!resultsTable || resultsTable.dataset.improveScc == 'true' || resultsTable.$$('tr').length <= 1) return;
  728. resultsTable.dataset.improveScc = 'true';
  729. const /** @type {string[]} */trackingIdList = [];
  730. const /** @type {Record<String,Extended<HTMLTableRowElement>>} */ rowMap = {}, methodColumn = resultsTable.$$('th').reduce((/**@type {false | number}*/methodColumn, th, i) => methodColumn || S(th.innerText).dictContains('ShipMethod') && (i + 1), false);
  731. resultsTable.$$('tr').forEach(tr => {
  732. const a = tr.$('a');
  733. if (!a) return;
  734. debuglog(a.innerText);
  735. rowMap[a.innerText] = tr;
  736. trackingIdList.push(a.innerText);
  737. methodColumn && colourResult(tr.$(`td:nth-of-type(${methodColumn})`)?.innerText, tr);
  738. });
  739. if (methodColumn) return;
  740. XHR('POST', `${globalOrigin}/station/proxyapigateway/data`, {
  741. resourcePath: '/os/batchGetPackageSummary',
  742. httpMethod: 'post',
  743. processName: 'oculus',
  744. requestBody: {
  745. idType: 'TRACKING_ID',
  746. identifiers: trackingIdList,
  747. nodeId: (/.*stationCode=([A-Z0-9]+).*/.exec(page.url[3]) ?? '')[1],
  748. includeFields: []
  749. }
  750. }).then(json).then(searchResult => searchResult.packageSummaryList.forEach((resultRow) => {
  751. debuglog(`${resultRow.trackingId} - ${resultRow.shipMethod} - ${resultRow.shipOption}`);
  752. colourResult(resultRow.shipMethod, rowMap[resultRow.trackingId]);
  753. rowMap[resultRow.trackingId].dataset.exchangeId = resultRow.exchangeId;
  754. }));
  755. }).observe(searchArea, { characterData: true, attributes: false, childList: true, subtree: true });
  756. });
  757. }
  758. /** @type {(text: string, dateTime: string?, type?: string?) => void} */
  759. function newCustNote(text, dateTime, type) {
  760. promises.boxes?.then((improveBoxes) => {
  761. if (!S(improveBoxes.notes.textContent).contains(text)) {
  762. improveBoxes.notes._(custNoteDiv(text, dateTime, type));
  763. }
  764. });
  765. }
  766. /** @type {(text: string, dateTime: string?, type?: string?) => Extended<HTMLDivElement>} */
  767. function custNoteDiv(text, dateTime, type) {
  768. return _('div')._(S(text).contains('dog') && _('span', { style: { fontSize: '2em' } })._('\uD83D\uDC3A'), ...(dateTime ? [toWeekdayDateString(dateTime), '\u2003-\u2003'] : []), type ? `${type}\u2003-\u2003` : undefined, text);
  769. }
  770. const historyTableLock = lock('history-table'), histTable = () => historyTableLock(() => (debuglog('histTable()'), Promise.allSettled([
  771. wait$('table:not([data-improve-scc-table])', { dataset: { improveSccTable: '' } }, 20),
  772. getHistoryData(),
  773. promises.sitemap,
  774. promises.package,
  775. promises.geo,
  776. promises.gam
  777. ]).then(([tablePromise, histPromise, sitePromise, packPromise, geoPromise, gamPromise]) => {
  778. debuglog('histTable() allSettled', { status: tablePromise.status }, histPromise, sitePromise, packPromise, geoPromise, gamPromise);
  779. if (tablePromise.status == 'rejected') throw `Could not find history table\n\t${JSON.stringify(tablePromise.reason)}`;
  780. if (histPromise.status == 'rejected') throw (debuglog('History Promise Failed… Reloading'), /*(debug ? confirm('History Promise Failed\n\nReload') : true) && location.reload(true),*/ `Could not get history data\n\t${JSON.stringify(histPromise.reason)}`);
  781. if (packPromise.status == 'rejected') throw (debuglog('Package Promise Failed… Reloading'), /*(debug ? confirm('Package Promise Failed\n\nReload') : true) && location.reload(true),*/ `Could not get package data\n\t${JSON.stringify(packPromise.reason)}`);
  782. const historytable = tablePromise.value,
  783. historyData = histPromise.value,
  784. sitemap = sitePromise.status == 'fulfilled' ? sitePromise.value.data : undefined,
  785. packageData = packPromise.value,
  786. geoData = geoPromise.status == 'fulfilled' && geoPromise.value,
  787. gamData = gamPromise.status == 'fulfilled' && gamPromise.value;
  788. if (historytable.dataset.improveScc == 'true') return; historytable.dataset.improveScc = 'true';
  789. debuglog(historytable);
  790. const logBox = _('th', { colSpan: 99 });
  791. historytable._(
  792. _('thead')._(_('tr')._(_('th', { colSpan: 99 })._(
  793. sitePromise.status == 'rejected' && _('div')._(s('NoSiteData').replace('{{reason}}', JSON.stringify(sitePromise.reason))),
  794. geoPromise.status == 'rejected' && _('div')._(s('NoGeoData').replace('{{reason}}', JSON.stringify(geoPromise.reason))),
  795. // gamPromise.status == 'rejected' && _('div')._(s('NoGAMData').replace('{{reason}}', JSON.stringify(gamPromise.reason)))
  796. ))),
  797. _('tfoot')._(_('tr')._(logBox))
  798. );
  799. historytable.dataset.colours = JSON.parse(localStorage.getItem('SCC_Search_CONFIG') ?? '{}').colours ?? 'OnRoad';
  800. historytable.$$('tr:not(:first-child)').forEach((tr, i) => {
  801. const thisRow = tr.$$('td'), timestamp = D(historyData[i].stateTime);
  802. Object.assign(historyData[i], {
  803. timestamp,
  804. dateISO: timestamp.toISODateString(),
  805. YMDKey: timestamp.toYMDKey(),
  806. operation: historyData[i].operation?.[0] ?? '',
  807. reason: thisRow[5].innerText,
  808. user: thisRow[6].innerText,
  809. other: thisRow[10].innerText
  810. });
  811. });
  812. debuglog(historyData);
  813. historytable.$$('tr:not(:first-child)').forEach((tr, i) => setTimeout(() => {
  814. const newRow = {}, thisRow = tr.$$('td'), thisHistory = historyData[i];
  815. tr.$("td:nth-child(3) > span > p", { style: { width: 'max-content' } });
  816. tr.before(newRow.tr = _('tr', { classList: ['newRow'] })._(newRow.td = _('td', { colSpan: 99, style: { fontWeight: 'bold' } })));
  817. if (thisHistory.timestamp.toString() != 'Invalid Date') {
  818. (i == 0 || thisHistory.YMDKey != historyData[i - 1].YMDKey) && (tr.classList.add('newDay'), newRow.tr.classList.add('newDay'));
  819. thisRow[0].__(toWeekdayDateTimeString(thisHistory.timestamp, packageData.stationTimeZone));
  820. }
  821. if (thisHistory.deliveryLatitude && thisHistory.deliveryLongitude) {
  822. const copy = _('button', { title: `${thisHistory.deliveryLatitude} ${thisHistory.deliveryLongitude}`, classList: ['copyButton'] },)._('\uD83D\uDCCB').on('click', () => copyText(copy.title));
  823. thisRow[10].$('p')?.before(copy);
  824. }
  825. //if (thisHistory.stateTime == historyData[i - 1]?.stateTime && thisHistory.packageState == historyData[i - 1]?.packageState && thisHistory.reasonCode == historyData[i - 1]?.reasonCode) return void (tr.classList.add('die'), newRow.tr.remove());
  826. if (historytable.dataset.colours == 'OnRoad') {
  827. const serviceAreaId = sitemap && (thisHistory.destination && thisHistory.destination in sitemap ? sitemap[thisHistory.destination] : (thisHistory.source && thisHistory.source in sitemap ? sitemap[thisHistory.source] : undefined));
  828. if (thisHistory.packageState == 'DELIVERED' && thisHistory.operation == 'PACKAGE_STATE_UPDATE') {
  829. tr.classList.add('delSuc'); newRow.tr.classList.add('delSuc');
  830. newRow.td._(
  831. ...(thisHistory.recipientName ? [_('span')._(`${s('delTo')}: ${thisHistory.recipientName} (${S(thisHistory.reasonCode.replace('DELIVERED_TO_', '').replace('_', ' ')).toWordCase().trim()})`, _('br'))] : []),
  832. packageData.pddDate && (packageData.pddYMDKey >= thisHistory.YMDKey ? _('span', { classList: ['goodTime'] })._(`\u2713 ${s('metPDD')}`) : _('span', { classList: ['badTime'] })._(`\u2717 ${s('missPDD')}`)),
  833. packageData.eadDate && (packageData.eadYMDKey >= thisHistory.YMDKey ? _('span', { classList: ['goodTime'] })._(`\u2713 ${s('metEAD')}`) : _('span', { classList: ['poorTime'] })._(`\u2717 ${s('missEAD')}`)),
  834. geoPromise.status == 'fulfilled' && thisHistory.deliveryLatitude && thisHistory.deliveryLongitude && distanceNode(thisHistory.deliveryLatitude, thisHistory.deliveryLongitude)
  835. );
  836. }
  837. if (thisHistory.packageState == 'DELIVERY_FAILED' && thisHistory.operation == 'PACKAGE_STATE_UPDATE') {
  838. tr.classList.add("delFail"); newRow.tr.classList.add('delFail');
  839. newRow.td._(
  840. _('span')._(thisHistory.reasonCode == 'BUSINESS_CLOSED' && gamPromise.status == 'fulfilled' && gamData && gamData.hourText ? `${s('BH')}\u2002${gamData.hourText}` : thisHistory.reason),
  841. geoPromise.status == 'fulfilled' && thisHistory.deliveryLatitude && thisHistory.deliveryLongitude && distanceNode(thisHistory.deliveryLatitude, thisHistory.deliveryLongitude)
  842. );
  843. }
  844. if (thisHistory.operation == 'ROUTE_ASSIGNMENT') {
  845. const buttons = _('span'), startTime = _('span'), deliveryTime = _('span');
  846. newRow.td._(
  847. _('span')._(_('strong')._(`${s('Route')} ${thisHistory.routeCode}`)),
  848. buttons, startTime, deliveryTime
  849. );
  850. // serviceAreaId && buttons._(_('button')._(s('cortexLinkRoute')).on('click', () => buttonclick(`${globalOrigin}/internal/operations/execution/dv/routes?provider=ALL_DRIVERS&selectedDay=${thisHistory.dateISO}&serviceAreaId=${serviceAreaId}&summariesText=${thisHistory.routeCode}`)));
  851. new Promise((ok, x) => {
  852. if (!serviceAreaId) { x('No serviceAreaId'); throw 'No serviceAreaId'; };
  853. if (!(thisHistory.timestamp.getTime() > (now - 14 * 24 * 60 * 60 * 1000))) { x('Too old'); throw 'Too old'; };
  854. XHR('GET', `${globalOrigin}/internal/operations/execution/api/route-summaries?localDate=${thisHistory.dateISO}&serviceAreaId=${serviceAreaId}`).then(json).then(cortex => {
  855. debuglog('cortex XHR()', cortex);
  856. cortex.rmsRouteSummaries.filter((route) => route.routeCode == thisHistory.routeCode).forEach((route) => {
  857. debuglog(route);
  858. routeVisButton(route.routeId, buttons, thisHistory.timestamp);
  859. return XHR('GET', `${globalOrigin}/internal/operations/execution/api/routes/${route.routeId}`).then(json).then(cortex => {
  860. debuglog('inner cortex XHR()', cortex);
  861. if (cortex.routePlan.plannedStartTime) { startTime.__(`${s('plannedStartTime').replace('{{time}}', toLocaleTZString(cortex.routePlan.plannedStartTime, packageData.stationTimeZone))}`); }
  862. cortex.routePlan.stopList.filter((stop) => stop.stopPlanContext.stopType == 'DropOff').forEach((stop, i) => {
  863. stop.stopDetails.packageList.filter((package) => package.scannableId == page.trackingId).forEach((package) => {
  864. if (package.plannedDeliveryTime) { deliveryTime.__(`${s('plannedTime').replace('{{time}}', toLocaleTZString(package.plannedDeliveryTime, packageData.stationTimeZone))}`); }
  865. return XHR('POST', `${globalOrigin}/internal/operations/execution/api/trs/trDetails`, { "trAndObjectState": [{ "trId": package.trId, "objectState": package.trObjectState }] }).then(json).then(packData => {
  866. debuglog('package cortex XHR()', packData);
  867. packData.trStateDetails.filter((stateDetails) => stateDetails.packageNotes).forEach((stateDetails) => stateDetails.packageNotes.forEach((notes) => {
  868. debuglog(notes);
  869. notes.noteContent.forEach((/** @type {string} */ noteText) => newCustNote(noteText.replace(/\\\\\\\\r/gim, '\r').replace(/\\\\\\\\n/gim, '\n'), toWeekdayDateString(thisHistory.timestamp), `Cortex ${notes.packageNoteType}`));
  870. }));
  871. ok('Got Cortex Data');
  872. }).catch(x);
  873. });
  874. });
  875. }).catch(x);
  876. });
  877. }).catch(x);
  878. }).then(debuglog, (e) => {
  879. debuglog('Skipping Route Cortex: ', e, thisHistory);
  880. logBox._(_('span')._('Skipping Route Cortex: ', JSON.stringify(e)));
  881. debuglog('routeVisXHR()');
  882. routeVisButton(response.json()?.routes?.[0]?.unitPlanIdentifier, buttons, thisHistory.timestamp);
  883. }).catch(debuglog);
  884. });
  885. }
  886. if (thisHistory.operation == 'DRIVER_ASSIGNMENT') {
  887. const amConsoleBut = _('button')._(thisHistory.driverId),
  888. dspSpan = _('span'), cortexButtons = _('span'), plannedTime = _('span'), windowTime = _('span');
  889. newRow.td._(
  890. thisHistory.driverName && _('span')._(`${s('Driver')} ${thisHistory.driverName}`),
  891. thisHistory.driverId && [amConsoleBut, dspSpan], cortexButtons, plannedTime, windowTime
  892. );
  893. if (thisHistory.driverId) {
  894. amConsoleBut.on('click', () => buttonclick(`${globalOrigin}/amconsole/transporter/${thisHistory.driverId}`));
  895. XHR('GET', `${globalOrigin}/amconsole/da/dsp-info/template?id=${thisHistory.driverId}&type=transporter`).then(response => {
  896. if (response.text && S(response.text).contains('Business Name:')) {
  897. dspSpan.__(response.text.split('Business Name:')?.[1]?.split('>')?.[1]?.split('</')?.[0]);
  898. }
  899. }, debuglog);
  900. // serviceAreaId && cortexButtons._(_('button')._(s('cortexItinerary')).on('click', () => buttonclick(`${globalOrigin}/internal/operations/execution/dv/routes?provider=ALL_DRIVERS&selectedDay=${thisHistory.dateISO}&serviceAreaId=${serviceAreaId}&summariesText=${thisHistory.driverId}`)));
  901. new Promise((ok, x) => {
  902. if (!serviceAreaId) { x('No serviceAreaId'); throw 'No serviceAreaId'; };
  903. XHR('GET', `${globalOrigin}/internal/operations/execution/api/summaries?localDate=${thisHistory.dateISO}&serviceAreaId=${serviceAreaId}`).then(json).then(cortex => {
  904. cortex.itinerarySummaries?.filter((itinerary) => itinerary.transporterId == thisHistory.driverId).forEach((itinerary) => {
  905. cortexButtons._(_('button')._(s('cortexItinerary')).on('click', () => buttonclick(`${globalOrigin}/internal/operations/execution/itineraries/${itinerary.itineraryId}/documentType/Itinerary?operationView=true&selectedDay=${thisHistory.dateISO}&serviceAreaId=${serviceAreaId}`)));
  906. return XHR('GET', `${globalOrigin}/internal/operations/execution/api/itineraries/${itinerary.itineraryId}?documentType=Itinerary&itineraryId=${itinerary.itineraryId}`).then(json).then(cortex => {
  907. let offset = 0;
  908. cortex.itineraryDetails.stops.forEach((stop) => {
  909. if (!stop.sequenceNumber) { offset++; }
  910. stop.tasks.filter((task) => task.taskType == 'DROP_OFF' && task.domainMap.scannableId == page.trackingId).forEach((task) => {
  911. if (stop.sequenceNumber) cortexButtons._(_('button')._(s('cortexLinkStop').replace('{{stop#}}', stop.sequenceNumber)).on('click', () => buttonclick(`${globalOrigin}/internal/operations/execution/itineraries/${itinerary.itineraryId}/documentType/Itinerary/stops/${stop.sequenceNumber - 1 + offset}?operationView=true&selectedDay=${thisHistory.dateISO}&serviceAreaId=${serviceAreaId}`)));
  912. if (stop.plannedStartTime || stop.plannedEndTime) {
  913. plannedTime._(`\u2003${s('plannedTime').replace('{{time}}', `${toLocaleTZString(stop.plannedStartTime, packageData.stationTimeZone)} - ${toLocaleTZString(stop.plannedEndTime, packageData.stationTimeZone)}`)}`);
  914. } else if (task.expectedExecutionTime) {
  915. plannedTime._(`\u2003${s('plannedTime').replace('{{time}}', toLocaleTZString(task.expectedExecutionTime, packageData.stationTimeZone))}`);
  916. }
  917. if (task.timeWindowed && (task.windowStartTime || task.windowEndTime)) {
  918. windowTime._(`\u2003${s('windowTime').replace('{{time}}', `${toLocaleTZString(1000 * task.windowStartTime, packageData.stationTimeZone)} - ${toLocaleTZString(1000 * task.windowEndTime, packageData.stationTimeZone)}`)}`);
  919. }
  920. return ok('Got Cortex Data');
  921. });
  922. });
  923. }).catch(x);
  924. });
  925. }).catch(x);
  926. }).catch(e => {
  927. debuglog('Skipping Driver Cortex: ', e, thisHistory);
  928. logBox._(_('span')._('Skipping Driver Cortex: ', JSON.stringify(e)));
  929. });
  930. }
  931. }
  932. S(thisHistory.destination).contains('CUSTOMER_ADDRESS') && (thisHistory.operation == 'PACKAGE_STATE_UPDATE' || thisHistory.operation == 'ASSOCIATE_DEBRIEF') && (thisHistory.packageState == 'RECEIVED' || thisHistory.packageState == 'MARKED_FOR_REPROCESS') && tr.classList.add("rts");
  933. }
  934. if (historytable.dataset.colours == 'InStation') {
  935. if (thisHistory.operation == 'PACKAGE_STATE_UPDATE') {
  936. thisHistory.packageState == 'INDUCTED' && tr.classList.add('delSuc');
  937. thisHistory.packageState == 'STOWED' && tr.classList.add('delFail');
  938. thisHistory.packageState == 'PICKED' || thisHistory.packageState == ('STAGED') && tr.classList.add('rts');
  939. S(thisHistory.destination).contains('CUSTOMER_ADDRESS') && (thisHistory.packageState == 'RECEIVED' || thisHistory.packageState == 'MARKED_FOR_REPROCESS') && tr.classList.add('delSuc');
  940. thisHistory.reasonCode == 'STOW_INVALID_SCAN_DONE' && tr.classList.add('endFail');
  941. } else if (thisHistory.operation == 'SIDELINED') { tr.classList.add('delFail', 'bold4'); }
  942. }
  943. if (thisHistory.packageState == 'IN_TRANSIT' && thisHistory.operation == 'PACKAGE_STATE_UPDATE') { tr.classList.add("oor"); thisRow[2].$('div')?._(svg(S(thisHistory.destination).contains('CUSTOMER_ADDRESS') ? 'van' : thisHistory.destination?.[0] == 'D' ? 'in_trailer' : 'trailer')); }
  944. thisHistory.packageState == 'INDUCTED' && thisHistory.operation == 'PACKAGE_STATE_UPDATE' && thisRow[2].$('div')?._(svg('avery', thisHistory.scanLocation));
  945. if (thisHistory.operation == 'CANCELLED_BY_CUSTOMER' || (thisHistory.packageState == 'MARKED_FOR_REPROCESS' && thisHistory.destination == 'MIDDLE_MILE_NODE')
  946. || ['MARKED_FOR_PROBLEM', 'DELIVERY_REJECTED', 'MARKED_AS_MISSING', 'MARKED_AS_LOST', 'DISPOSED'].includes(thisHistory.packageState)
  947. || ['WRONG_NODE', 'OBJECT_MISSING', 'DAMAGED_FC_RETURN', 'LOCALLY_DISPOSED'].includes(thisHistory.reasonCode)) { tr.classList.add('endFail'); newRow.tr.classList.add('endFail'); }
  948. if (S(thisHistory.user).contains('@')) {
  949. const username = thisHistory.user.split('@')[0];
  950. thisRow[6].$('div')?._(_('a', { href: `https://fclm-portal.amazon.com/employee/ppaTimeDetails?employeeId=${username}`, target: '_blank', classList: ['minibadge'] })._(_('img', { src: `https://internal-cdn.amazon.com/badgephotos.amazon.com/?login=${username}`, loading: 'lazy', classList: ['minibadge'] },)));
  951. }
  952. if (thisHistory.scanContainer && S(thisHistory.scanContainer).contains('EU_')) {
  953. thisRow[2].$('div')?._(svg('bag', thisHistory.scanContainer, thisHistory.scanContainer.slice(-3)));
  954. }
  955. if ((S(thisHistory.scanContainer).containsAny('OVER-', 'CRT2-', '_DOLLY_')) || (thisHistory.packageState == 'PICKED' && S(thisHistory.scanLocation).contains('CRT2-'))) { thisRow[2].$('div')?._(svg('cart', thisHistory.scanLocation)); }
  956. if (thisHistory.imageUrl) {
  957. const photoImg = _('img', { src: thisHistory.imageUrl, loading: 'lazy', classList: ['photoImg', 'hide'] });
  958. newRow.td._(_('button')._('Show/Hide Photo').on('click', () => { photoImg.classList.toggle('hide'); }), photoImg);
  959. }
  960. setTimeout(() => newRow.td.textContent == '' && newRow.tr.remove());
  961. }));
  962. }).catch((e) => promises.topBar.then(improveBoxes => { debuglog('histTableError', e); improveBoxes.histTableError.__(`${e}`); }))));
  963. function outbound() {
  964. debuglog('outbound()');
  965. wait$('#root').then(root => {
  966. document.documentElement.dataset.improveSccCssOutboundPage = '';
  967. new MutationObserver(() => queueMicrotask(() => {
  968. const thead = $('thead'), tbody = $('tbody');
  969. if (!thead || !tbody) return;
  970. const [badgeCol, cartCol, driverCol] = thead?.$$('th').map(th => S(th?.innerText)).reduce(([badgeCol, cartCol, driverCol], th, i) => [
  971. th.dictContains('associate') ? i + 1 : badgeCol,
  972. th.dictContains('cartID') ? i + 1 : cartCol,
  973. th.dictContains('driver') ? i + 1 : driverCol
  974. ], [0, 0, 0]);
  975. debuglog(badgeCol, cartCol, driverCol);
  976. tbody?.$$('tr').forEach(tr => {
  977. if (badgeCol) {
  978. tr.$$(`td:nth-child(${badgeCol}) a`).forEach(a => {
  979. if (a?.$('input')) return;
  980. if (a?.$('img') || a?.parentNode?.querySelector('img')) { a?.classList.add('badge'); return; }
  981. [...a?.parentNode?.childNodes ?? []].filter((text) => (text.nodeName == '#text')).forEach((text) => text.remove());
  982. miniBadge(a, 1);
  983. });
  984. }
  985. if (driverCol) {
  986. const driver = tr.$(`td:nth-child(${driverCol}) span > p[mdn-text][title]`);
  987. if (driver?.classList) {
  988. driver.parentNode?.append(_('a', { href: `${globalOrigin}/amconsole/transporter/${driver.title}`, target: '_blank' })._(driver));
  989. }
  990. }
  991. });
  992. })).observe(root, { characterData: true, attributes: false, childList: true, subtree: true });
  993. });
  994. }
  995. function associate() {
  996. debuglog('associate()');
  997. document.documentElement.dataset.improveSccCssBadgesPage = '';
  998. if (hrefContains('receive')) {
  999. const tabs = $$("[role=tablist] label");
  1000. if (!tabs.length) { setTimeout(associate, 500); return; }
  1001. debuglog(tabs);
  1002. tabs.at(-1)?.do('click');
  1003. page.region = window.location.href.split('ivs')[1].split('.')[0];
  1004. document.documentElement.dataset.improveSccCssReceivePage = '';
  1005. wait$('#root').then(root => new MutationObserver(() => setTimeout(() => {
  1006. $$('#root table tbody > tr > [scope=row]:first-child:not(.fmcload)').forEach(td => {
  1007. debuglog(td);
  1008. td.classList.add('fmcload');
  1009. const vrId = td.textContent;
  1010. XHR('GET', `https://trans-logistics${page.region == 'na' ? '' : `-${page.region}`}.amazon.com/fmc/api/v2/execution/load/${vrId}/mapFeature/stops`).then(json).then(responseJSON => {
  1011. td.title = responseJSON.reduce((prev, next) => `${prev}${next.timelineEvent.title}:\n${next.timelineEvent.stopActions.reduce((prev, next) => `${prev}${next.actualCompletedTime?.utcMillis ? `\u2003${next.yardActionType} - ${toWeekdayDateTimeString(next.actualCompletedTime?.utcMillis)}\n` : ''}`, '')}\n`, '');
  1012. td.classList.add('fmcdone');
  1013. });
  1014. });
  1015. })).observe(root, { characterData: true, attributes: false, childList: true, subtree: true }));
  1016. return;
  1017. }
  1018. // if (hrefContains('induct')) return;
  1019. if (hrefContains('packageList')) { packageList(); return; }// WIP does not work yet.
  1020. wait$('#root').then(root => {
  1021. setInterval(() => root.append(mutationComment), 6000);
  1022. new MutationObserver(() => {
  1023. setTimeout(() => {
  1024. const badgeCol = $$('thead th').reduce((/**@type {false | number}*/bool, th, i) => bool || S(th?.innerText).dictContains('associate') && i + 1, false);
  1025. if (!badgeCol) return;
  1026. $$(`tbody tr :is(th, td):nth-child(${badgeCol}) span`).forEach(a => {
  1027. if (a?.$('input')) return;
  1028. if (a?.$('img')) { a?.classList.add('badge'); return; }
  1029. miniBadge(a, 0);
  1030. });
  1031. });
  1032. }).observe(root, { characterData: true, attributes: false, childList: true, subtree: true });
  1033. });
  1034. }
  1035. function packageList() {
  1036. debuglog('packageList()');
  1037. wait$('tbody').then(tbody => {
  1038. $('thead tr')?._(_('th', { classList: ['css-1j7l84d'] })._(_('span')._('PDD')));
  1039. packageListXHR();
  1040. new MutationObserver(packageListXHR).observe(tbody, { characterData: true, attributes: false, childList: true, subtree: true });
  1041. });
  1042. }
  1043. function psTab() {
  1044. debuglog('psTab()');
  1045. document.documentElement.dataset.improveSccCssBadgesPage = '';
  1046. wait$('body').then(body => {
  1047. new MutationObserver(() => setTimeout(() => {
  1048. const badgeCol = $('thead')?.$$('th').reduce((/**@type {false | number}*/badgeCol, th, i) => badgeCol || S(th?.innerText).dictContains('Reportedby') && (i + 1), false);
  1049. badgeCol && $('tbody')?.$$('tr').forEach(tr => {
  1050. debuglog(tr);
  1051. const a = tr.$(`td:nth-child(${badgeCol}) span`);
  1052. if (a?.$('img')) { a.classList.add('badge'); return; }
  1053. a && miniBadge(a, 0);
  1054. });
  1055. })).observe(body, { characterData: true, attributes: false, childList: true, subtree: true });
  1056. });
  1057. debuglog('psTab - MO - Start');
  1058. }
  1059. //#endregion
  1060.  
  1061. //#region: XHR Functions
  1062. /** @type {() => Promise<PackageData>} */
  1063. function getPackageData() {
  1064. debuglog('getPackageData()');
  1065. return XHR('POST', `${globalOrigin}/station/proxyapigateway/data`, { "resourcePath": "/os/getPackageDetailData", "httpMethod": "get", "processName": "oculus", "requestParams": { "trackingId": [page.trackingId] } }).then(response => {
  1066. debuglog('getPackageDataXHR()');
  1067. const packageData = response.json().packageDetail.packageData;
  1068. packageData.amxl = S(packageData.shipInfo.shipMethod).contains('_SH');
  1069. packageData.pddDate = packageData.shipInfo.promisedDeliveryTime ? D(packageData.shipInfo.promisedDeliveryTime) : undefined;
  1070. packageData.eadDate = packageData.shipInfo.estimatedArrivalTime ? D(packageData.shipInfo.estimatedArrivalTime) : undefined;
  1071. packageData.pddYMDKey = packageData.pddDate?.toYMDKey();
  1072. packageData.eadYMDKey = packageData.eadDate?.toYMDKey();
  1073. packageData.oversize = ((m, l, w, h) => {
  1074. packageData.volume = l * w * h;
  1075. packageData.letterbox = (h <= 3.8 && w <= 25.4);
  1076. return (m > 5
  1077. || packageData.volume > 50000
  1078. || l > 73 || w > 73 || h > 73
  1079. || (l > 53 && (w > 40 || h > 40))
  1080. || (w > 53 && (l > 40 || h > 40))
  1081. || (h > 53 && (w > 40 || l > 40))
  1082. );
  1083. })(
  1084. packageData.packageDimensions.packageWeight.unit == 'ounces' ? 0.02834952 * packageData.packageDimensions.packageWeight.value : packageData.packageDimensions.packageWeight.value,
  1085. packageData.packageDimensions.packageLength.value,
  1086. packageData.packageDimensions.packageWidth.value,
  1087. packageData.packageDimensions.packageHeight.value
  1088. );
  1089. packageData.pcd = packageData.customerInfo.customerAddress?.split(',')?.slice(-1)[0];
  1090. superlog('package', packageData);
  1091. return packageData;
  1092. });
  1093. }
  1094. /** @type {([improveRoot, packageData]:[ImproveRoot, PackageData]) => ImproveBoxes} */
  1095. function usePackageData([improveRoot, packageData]) {
  1096. debuglog('usePackageData()', improveRoot, packageData);
  1097. const improveBoxes = /** @type {ImproveBoxes} */(improveRoot);
  1098. document.documentElement.dataset.improveSccXhr = '';
  1099. if (packageData.shipInfo.itemDescription) { improveBoxes.items.__(packageData.shipInfo.itemDescription); };
  1100. if (packageData.amxl && page.region == 'na') { addButton(`http://amxl-ba.corp.amazon.com/Passage/#${packageData.trackingId}`, s('Passage'), 2); }
  1101. colourResult(packageData.shipInfo.shipMethod, improveBoxes.head);
  1102. improveBoxes.head.__(
  1103. _('div')._(
  1104. improveBoxes.trackingId = _('h1')._(page.trackingId).on('click', () => copyText(page.trackingId)),
  1105. _('div')._(_('strong')._(s('shipMethod')), packageData.shipInfo.shipMethod),
  1106. _('div')._(_('strong')._(s('shipOption')), packageData.shipInfo.shipOption),
  1107. // packageData.serviceType?.length && _('div')._(_('strong')._(s('serviceType')), packageData.serviceType) : undefined,
  1108. packageData.orderInfo.orderId && (improveBoxes.orderId = _('div', { id: 'oid', classList: [S(packageData.orderInfo.orderId).containsAny('VRET', 'S02-') ? 'vret' : undefined] })._(_('strong')._(s('oId')), packageData.orderInfo.orderId).on('click', () => copyText(packageData.orderInfo.orderId))),
  1109. // improveBoxes.customerId = _('div')._(_('strong')._(s('customerId')), improveBoxes.customerIdText = _('span')._('…')).on('click', () => copyText(improveBoxes.customerIdText.innerText)),
  1110. packageData.linkedTrackingIds.map((linktid) => _('div', { classList: ['linktid'] })._(_('strong')._(s('lTId')), _('a', { href: `${globalOrigin}/station/dashboard/search?shareableLink=detailPage%2F${linktid}`, target: '_blank' })._(linktid))),
  1111. improveBoxes.multiPart = _('div'),
  1112. ),
  1113. _('div', { id: 'flags' })._(
  1114. improveBoxes.OTP = packageData.hasOtp ? _('strong', { id: 'OTP', style: { color: 'red' } })._(s('OTP')) : undefined,
  1115. (S(packageData.shipInfo.shipMethod).contains('AGEVER') || packageData.isAvdPackage) && _('strong', { style: { color: 'red' } })._(s('AVD')),
  1116. packageData.isHazmat && _('strong', { style: { color: 'darkorange' } })._(s('HAZMAT')),
  1117. packageData.orderInfo.orderAmount >= ((cur) => cur == 'INR' ? 10000 : cur == 'JPY' ? 20000 : cur == 'BRL' ? 500 : 100)(packageData.orderInfo.orderCurrency) && _('strong', { style: { color: 'forestgreen' } })._(s('HV').replace(/ /g, '\u00a0')),
  1118. (packageData.amxl ? [_('strong', { style: { color: 'royalblue' } })._('AMXL')] : [
  1119. packageData.letterbox && _('strong', { style: { color: 'royalblue' } })._(s('LB')),
  1120. (packageData.oversize || packageData.isOv) && _('strong', { style: { color: 'royalblue' } })._(s('OVER')),
  1121. packageData.packageDimensions.packageWeight.value > 20 ? _('strong', { style: { color: 'royalblue' } })._(s('VH').replace(/ /g, '\u00a0')) : packageData.packageDimensions.packageWeight.value > 10 ? _('strong', { style: { color: 'royalblue' } })._(s('H')) : undefined
  1122. ])
  1123. ),
  1124. _('div')._(
  1125. _('div')._(`${formatUnit(packageData.packageDimensions.packageLength)} x ${formatUnit(packageData.packageDimensions.packageWidth)} x ${formatUnit(packageData.packageDimensions.packageHeight)}`),
  1126. _('div', { classList: ['spaced'] })._(
  1127. _('span')._(`${packageData.volume.toFixed(0)}\u00a0cm³`),
  1128. _('span')._(`${formatUnit(packageData.packageDimensions.packageWeight)}`),
  1129. _('span')._(formatCurrency({ currency: packageData.orderInfo.orderCurrency, value: packageData.orderInfo.orderAmount }))
  1130. ),
  1131. improveBoxes.fc = _('div', { id: 'fcBox' }),
  1132. packageData.eadDate && _('div')._(_('strong')._('EAD:'), `\u2002${toWeekdayDateTimeString(packageData.eadDate)}`),
  1133. packageData.pddDate && _('div')._(_('strong')._('PDD:'), `\u2002${(['AMZN_DE_SAME_SD', ' AMZN_ES_SAME_SD', 'AMZN_FR_SAME_SD', 'AMZN_IT_SAME_SD', 'AMZN_UK_SAME_SD'].includes(packageData.shipInfo.shipMethod) && packageData.shipInfo.shipOption == 'rush' ? toWeekdayDateString : toWeekdayDateTimeString)(packageData.pddDate)}`),
  1134. !!(packageData.shipInfo.scheduledDeliveryTime && packageData.shipInfo.scheduledDeliveryTimeEnd) && _('div', { classList: ['simple-flex'] })._(_('strong')._('Scheduled:'), _('span')._(_('div')._(toWeekdayDateTimeString(packageData.shipInfo.scheduledDeliveryTime, packageData.stationTimeZone)), _('div')._(toWeekdayDateTimeString(packageData.shipInfo.scheduledDeliveryTimeEnd, packageData.stationTimeZone))))
  1135. ),
  1136. improveBoxes.customerCol = _('div', { id: 'customerColumn' })._(
  1137. _('div')._(packageData.customerInfo.customerName),
  1138. _('div')._(packageData.customerInfo.customerAddress.replace(/,/g, ', ').replace(/\s+/g, ' ')),
  1139. _('strong')._(packageData.customerInfo.customerAddressType),
  1140. improveBoxes.nak = _('div', { id: 'NAK' }),
  1141. improveBoxes.phr = _('div', { id: 'PHR' }),
  1142. _('strong')._('Customer Notes:'),
  1143. improveBoxes.notes = _('div', { id: 'custNotes' })._(...packageData.customerNotes.map((note) => custNoteDiv(note.customerNoteContent, note.dateAdded)))
  1144. )
  1145. );
  1146. if (packageData.amxl) {
  1147. XHR('POST', `${globalOrigin}/station/proxyapigateway/data`, {
  1148. "resourcePath": "/os/batchGetPackageSummary",
  1149. "httpMethod": "post",
  1150. "processName": "oculus",
  1151. "requestBody": { "idType": "TRACKING_ID", "identifiers": [packageData.orderInfo.orderId], "nodeId": packageData.routeInfo.stationCode, "includeFields": [] }
  1152. }).then(json).then(searchResult => {
  1153. const items = searchResult.packageSummaryList.filter((resultRow) => (page.trackingId != resultRow.trackingId)).map((resultRow) => _('a', { href: `${globalOrigin}/station/dashboard/search?shareableLink=detailPage%2F${resultRow.trackingId}`, target: '_blank' })._(resultRow.trackingId));
  1154. if (items.length) improveBoxes.multiPart.__(s('sameOrderID'), items);
  1155. });
  1156. }
  1157. return improveBoxes;
  1158. }
  1159. /** @type {() => Promise<SiteMap>} */
  1160. function siteMap() {
  1161. let sitemap;
  1162. if ((sitemap = localStorage.getItem('sitemap')) && (sitemap = JSON.parse(sitemap)) && (sitemap.timestamp > now - 7 * 24 * 60 * 60 * 1000) && sitemap.hasOwnProperty('data')) { return Promise.resolve(sitemap); }
  1163. return XHR('GET', `${globalOrigin}/flex/api/getOperationalRegions`).then(response => {
  1164. debuglog('sitemap', response);
  1165. const sitemap = { timestamp: now, data: {} };
  1166. response.json().forEach((region) => region.basicServiceAreas.forEach((site) => (sitemap.data[site.defaultStationCode] = site.serviceAreaID)));
  1167. localStorage.setItem('sitemap', JSON.stringify(sitemap));
  1168. return sitemap;
  1169. }, error => {
  1170. if ('text' in error) delete error.text;
  1171. throw error;
  1172. });
  1173. }
  1174. /** @type {() => Promise<GeoData>} */
  1175. function getGeoData() {
  1176. debuglog('getGeoData()');
  1177. return XHR('POST', `https://api-gam.${page.region}.prod.geostudio.last-mile.amazon.dev/api/getAddressInfo`, { "id": page.trackingId, "idType": "Auto", "gdeView": "" }).then(json, json).then(geoData => {
  1178. debuglog('geoData', geoData);
  1179. const message = (geoData.message ?? geoData.Message); if (message == 'Unauthenticated' || S(message)?.contains('not authorized')) throw message;
  1180. /**@type {GeoData} */
  1181. const returnGeoData = Object.assign({
  1182. deliveryHint: geoData.deliveryHint,
  1183. nak: (geoData?.geocodingServiceResult ?? geoData?.geospatialData)?.scope10NAK ?? geoData?.geocodingServiceResult?.scope10NAK,
  1184. phr: geoData?.preferredDeliveryLocations?.length && geoData?.preferredDeliveryLocations?.reduce((a, b) => `${a}, ${b}`),
  1185. }, ((geoData?.geocodingServiceResult ?? geoData?.geospatialData)?.bestDeliveryPoint ?? geoData?.addressServiceGeocode)?.coordinates);
  1186. returnGeoData.validLatLon = !(returnGeoData.latitude === undefined || returnGeoData.latitude === null || returnGeoData.longitude === undefined || returnGeoData.longitude === null);
  1187. if (returnGeoData.validLatLon) { return returnGeoData; }
  1188. throw `Geo data has invalid lat/lon: ${JSON.stringify(geoData)}`;
  1189. }).catch(e => { throw e; });
  1190. }
  1191. /** @type {([improveBoxes, geoData]:[ImproveBoxes, GeoData]) => void} */
  1192. function useGeoData([improveBoxes, geoData]) {
  1193. superlog('useGeoData()', geoData);
  1194. if (geoData.nak) { improveBoxes.nak._(_('strong')._('NAK:\u2002'), geoData.nak.replace(/(\!+)/g, ' $1 ')); }
  1195. if (geoData.phr) { improveBoxes.phr._(_('strong')._('Safe Place(s):\u2002'), geoData.phr); }
  1196. if (!geoData.validLatLon) return;
  1197. improveBoxes.buttons._(($('#latlonbuts') || _('div', { id: 'latlonbuts', style: { order: `${4}` } })).__(
  1198. s('LatLon'),
  1199. miniButton(`https://bing.com/maps?style=a&where1=${geoData.latitude} ${geoData.longitude}`),
  1200. ));
  1201. }
  1202. /** @type {(packageData:PackageData) => Promise<GAMData>} */
  1203. function getGAMData(packageData) {
  1204. debuglog('getGAMData()');
  1205. return Promise.reject('GAM requests disabled');
  1206. const countryCode = packageData.shipInfo.shipMethod.split('_')[1];
  1207. return XHR('GET', `https://gamtools-${page.region}.aka.amazon.com/searchResults?searchId=${page.trackingId}&idType=TRACKING_ID&countryCode=${countryCode == 'UK' ? 'GB' : countryCode}`).then(response => { try { return response.json(); } catch (e) { throw response.text; } }).then(json => {
  1208. debuglog('gam', json);
  1209. if (!('step_up_methods' in json)) { throw 'Unknown JSON'; }
  1210. if (midwayWindow == null || midwayWindow == undefined || midwayWindow.closed) {
  1211. midwayWindow = window.open(json.step_up_methods[0].cap_url, 'midwayAuth', 'width=1200, height=800');
  1212. if (midwayWindow == null || midwayWindow == undefined || midwayWindow.closed) { throw 'Midway Popup Blocked'; }
  1213. }
  1214. midwayWindow.focus(); throw (json.step_up_methods[0].cap_display_string || 'Please login with Midway');
  1215. }, text => {
  1216. debuglog('gam', text);
  1217. if (text.contains("Oops, something has gone wrong") || text.contains("not authorized")) { return { hourText: undefined }; }
  1218. if (text.contains('is not yet in GAM. Addresses may take a few days to reflect.')) { return { hourText: s('GAM404') }; }
  1219. const gam = _('html', { innerHTML: text.replace(/\<script.*\<\/script\>/gim, '') });
  1220. let hoursList = []; hoursList.push(...gam.$$("#canonicalAttributes div.canonical-attribute-values > b"), ...gam.$$("#Business-Hours-canonical-values > b"));
  1221. return {
  1222. unitImg: gam.$("img.unit-icon")?.src,
  1223. hourText: hoursList.filter(b => S(b.innerText.replace(/ /gim, '').slice(0, 3)).inDict('GAMdays') && Number.isNaN(Number(b.innerText.slice(0, 4)))).map(b => b.innerText).join('\u2003') || s('GAMnoHours'),
  1224. hintText: gam.$$('#canonicalAttributes li').reduce((hintText, li) => `${hintText}${S(li.innerText).contains('Delivery Hint') && li.$('b') ? li.$('b')?.innerHTML : ''}`, '')
  1225. };
  1226. });
  1227. }
  1228. /** @type {([improveBoxes, gamData, historyData]:[ImproveRoot, GAMData, HistDataRow[]]) => void} */
  1229. function useGAMData([improveBoxes, gamData, historyData]) {
  1230. superlog('useGAMData()', gamData);
  1231. const bizClosed = historyData.filter((row) => row.reasonCode == 'BUSINESS_CLOSED');
  1232. gamData.hourText !== null && improveBoxes.gam.__(`${s('Hours')} ${gamData.hourText}`, !!bizClosed.length && _('div', { id: 'gamfail' })._(`${s('FailedAttempt')}\u2003`, bizClosed.map(row => toWeekdayDateString(row.stateTime)).join(' | ')));
  1233. gamData.hintText && newCustNote(gamData.hintText, null, s('GAMhint'));
  1234. }
  1235. // function getTaxonomy([improveBoxes, eeData]) {
  1236. // debuglog(responseText);
  1237. // if (responseText.contains('Content is hidden because of Access control on Transportation Taxonomy')) { throw 'No Access to Transportation Taxonomy'; }
  1238. // try {
  1239. // let tempText = responseText.split('"transactionType":')[1].split(',')[0].replaceAll('\\', '').replaceAll('"', '').trim();
  1240. // if (tempText == 'null') { throw ''; }
  1241. // improveBoxes.transactionType.__(tempText);
  1242. // } catch (e) { improveBoxes.transactionType.remove(); }
  1243. // try {
  1244. // improveBoxes.replacementType.__(responseText.split('"replacementType":')[1].split(',')[0].replaceAll('\\', '').replaceAll('"', '').trim());
  1245. // } catch (e) { improveBoxes.replacementType.remove(); }
  1246. // const fragments = responseText.split('java.util.HashMap');
  1247. // new Promise((resolve, reject) => { fragments.filter((fragment) => fragment.contains('OTPRequired')).forEach((fragment) => fragment.split(',').filter((HashMap) => HashMap.contains('OTPRequired') && HashMap.contains('true')).forEach(resolve)); reject(); }).then(
  1248. // () => { improveBoxes.OTP.__(s('OTP')); improveBoxes.SIG.remove(); },
  1249. // () => new Promise((resolve, reject) => { fragments.filter((fragment) => fragment.contains('SignatureRequired')).forEach((fragment) => fragment.split(',').filter((HashMap) => HashMap.contains('SignatureRequired') && HashMap.contains('true')).forEach(resolve)); reject(); }).then(
  1250. // () => { improveBoxes.SIG.__(s('SIG')); improveBoxes.OTP.remove(); },
  1251. // () => new Promise((resolve, reject) => { responseText.split('"name":"ServiceOfferings"').filter((fragment) => fragment.split('"targetEntityName":')[1]?.contains('"OTP"')).forEach(resolve); reject(); }).then(
  1252. // () => { improveBoxes.OTP.__(s('OTP')); improveBoxes.SIG.remove(); },
  1253. // () => { improveBoxes.OTP.remove(); improveBoxes.SIG.remove(); }
  1254. // )
  1255. // )
  1256. // );
  1257. // }).catch(e => {
  1258. // improveBoxes.OTP.remove();
  1259. // improveBoxes.SIG.remove();
  1260. // improveBoxes.taxonomyError._('No OTP or Sig Data: ', e);
  1261. // });
  1262. // }
  1263. // function getOBLT() {
  1264. // debuglog('getOBLT()');
  1265. // const oblt_row = _('html', { innerHTML: html.replace(/\<script.*\<\/script\>/gim, '') }).$$('table#resultsTable tr[rownum="0"] td');
  1266. // return {
  1267. // fulfillmentShipmentId: oblt_row[3]?.textContent,
  1268. // orderingShipmentId: oblt_row[5]?.textContent,
  1269. // customerId: oblt_row[9]?.textContent,
  1270. // FC: oblt_row[10]?.textContent
  1271. // };
  1272. // });
  1273. // }
  1274. // function useOBLTData([improveBoxes, obltData]) {
  1275. // debuglog('useOBLTData()', obltData);
  1276. // improveBoxes.customerIdText.__(obltData.customerId);
  1277. // response.json().EventList.filter((event) => event.eventType == 'COMPLETE_PACKAGE').forEach((event) => {
  1278. // if (!response.text.contains('boxRecommendation=')) return;
  1279. // improveBoxes.fc.__(_('strong')._('FC Box Recommendation: '), response.text.split('boxRecommendation=')[1].split(',')[0]);
  1280. // });
  1281. // });
  1282. // });
  1283. // }
  1284. /** @type {() => Promise<HistDataRow[]>} */
  1285. function getHistoryData() {
  1286. debuglog('getHistoryData()');
  1287. return XHR('POST', `${globalOrigin}/station/proxyapigateway/data`, { "resourcePath": "/os/getPackageHistoryData", "httpMethod": "post", "processName": "oculus", "requestBody": { "packageId": page.trackingId, "pageSize": 100, "pageToken": null, "startTime": null, "endTime": null } }).then(json).then(({ packageHistory }) => packageHistory);
  1288. }
  1289. /** @type {(routeId: string, node: Extended<HTMLElement>, timestamp: ExtendedDate) => void} */
  1290. function routeVisButton(routeId, node, timestamp) {
  1291. debuglog('routeVisButton()', routeId, node, timestamp);
  1292. node._(
  1293. (timestamp.toMidnightLocal() < D().toMidnightLocal()) && _('button')._(s('RouteDiver')).on('click', () => buttonclick(`https://routediver-${page.regionCode}.${page.regionCode}.proxy.amazon.com/#?routeId=${routeId}`)),
  1294. );
  1295. }
  1296. function packageListXHR() {
  1297. const searchXHRpayload = {
  1298. "resourcePath": "/os/batchGetPackageSummary",
  1299. "httpMethod": "post",
  1300. "processName": "oculus",
  1301. "requestBody": {
  1302. "idType": "TRACKING_ID",
  1303. /** @type {(string | undefined)[]} */
  1304. "identifiers": [],
  1305. "nodeId": (/.*stationCode=([A-Z0-9]+).*/.exec(page.url[3]) || '')[1],
  1306. "includeFields": []
  1307. }
  1308. }, trMap = {};
  1309. $$('tbody tr').forEach(tr => {
  1310. const tId = tr.$('td span')?.innerText;
  1311. if (tr.tId == tId) return;
  1312. tr.tId = tId;
  1313. trMap[tId] = tr;
  1314. searchXHRpayload.requestBody.identifiers.push(tId);
  1315. tr.$('.pdd')?.remove?.();
  1316. tr.classList.remove('AVD', 'SWA', 'LOCK', 'SAME');
  1317. });
  1318. debuglog(searchXHRpayload);
  1319. if (searchXHRpayload.requestBody.identifiers.length) {
  1320. XHR('POST', `${globalOrigin}/station/proxyapigateway/data`, searchXHRpayload).then(json).then(searchResult => {
  1321. searchResult.packageSummaryList.forEach((resultRow) => {
  1322. const td = $(`row_${resultRow.trackingId} .pdd`) || _('td', { classList: ['pdd'] });
  1323. if (resultRow.promisedDeliveryDate) { td.__(_('div', { style: { display: 'inline-block', width: '140px' } })._(toWeekdayDateString(resultRow.promisedDeliveryDate))); }
  1324. trMap[resultRow.trackingId]._(td);
  1325. colourResult(resultRow.shipMethod, trMap[resultRow.trackingId]);
  1326. });
  1327. });
  1328. }
  1329. }
  1330. const /** @type {Record<string, string>} */ AWS_POD_REGION_MAPPING = { eu: "eu-west-1", na: "us-east-1", fe: "us-west-2" };
  1331. function getPODData() {
  1332. promises.topBar.then(improveBoxes => {
  1333. improveBoxes.podContainer.__('POD: Please wait…');
  1334. XHR('GET', `https://honeypot.amazon.dev/api/searchImageByIndex?input=${encodeURIComponent(JSON.stringify({ "imageIndex": page.trackingId, "imageType": "PHOTO_ON_DELIVERY", "searchBy": "TRACKING_ID", "region": AWS_POD_REGION_MAPPING[page.region ?? ''] }))}`).then(r => r.json(), r => r.json()).then((/** @type {PODResponse} */ data) => {
  1335. improveBoxes.podContainer.__(
  1336. _('div', { classList: ['button-align'] })._(
  1337. _('button', { classList: ['close'] })._('X').on('click', () => improveBoxes.podContainer.__()),
  1338. ),
  1339. _('section')._(
  1340. ('error' in data) ? data.error : data.result.data.imageList.length ? data.result.data.imageList.map(
  1341. ({ imageInfo, imagePresignedUrl }) => _('div', { classList: ['pod-item'] })._(
  1342. _('img', { src: imagePresignedUrl, classList: ['pod-img'] }),
  1343. _('span')._(imageInfo.imageAttributeData.imageType),
  1344. _('span')._(D(imageInfo.photographedDate * 1e3).toISODateTimeString()),
  1345. _('span')._(imageInfo.metadataMap.REASON_CODE),
  1346. _('div')._(
  1347. _('span')._(`${formatUnit({ value: imageInfo.geocode.latitude, unit: 'degree' })}, ${formatUnit({ value: imageInfo.geocode.longitude, unit: 'degree' })}`),
  1348. distanceNode(imageInfo.geocode.latitude, imageInfo.geocode.longitude))
  1349. )
  1350. ) : 'No Results Found'
  1351. )
  1352. );
  1353. }).catch(e => improveBoxes.podContainer.__(JSON.stringify(e)));
  1354. });
  1355. }
  1356.  
  1357. //#endregion
  1358.  
  1359. //#region: Mini Functions
  1360. /** Add svg css */
  1361. _css(`svg[data-improve-scc-svg] {
  1362. path { stroke-linecap: round; stroke-linejoin: round; }
  1363. path.bag { stroke: black; fill-opacity: 0.75; }
  1364. path.bag.out { fill-opacity: 1; }
  1365. path.box { fill: #FF7F00; }
  1366. path.tape { fill: #000000; }
  1367. path.label { fill: #FFFFFF; }
  1368. &.cart {
  1369. path.cage { stroke: yellow; fill: none; }
  1370. path.cage.floor { fill: #000000; }
  1371. circle.wheel { fill: #7F7F7F; }
  1372. }
  1373. &.van {
  1374. .van { fill: #232F3E; stroke: #0096D6; }
  1375. .window { fill: white; stroke: #0096D6; }
  1376. .smile { fill: #0096D6; stroke: #0096D6; }
  1377. .wheel { fill: white; stroke: white; }
  1378. path.wheelnut { fill: #0096D6; stroke: #0096D6; }
  1379. }
  1380. &:is(.trailer, .in_trailer) {
  1381. path.cab { fill: #232f3e; stroke: black; }
  1382. path.trailer { fill: #0096d6; stroke: black; }
  1383. path.window { fill: #afafFF; stroke: black; }
  1384. path.tsmile { fill: #ffffff; stroke: #ffffff; }
  1385. circle.wheel { fill: #404040; stroke: black; }
  1386. }
  1387. &:is(.avery){
  1388. path.print { fill: black; }
  1389. path.deco { fill: #bb0000; }
  1390. path.clip { fill: #bbbb00; }
  1391. }
  1392. }`);
  1393. /** @type {(type: string, title?: string?, colour?: string) => Extended<SVGSVGElement>} */
  1394. function svg(type, title, colour) {
  1395. const svg = _svg('svg', { dataset: { improveSccSvg: '' }, attributeList: { viewBox: '0 0 60 60' }, classList: [type, 'bag', colour] })._(title && _svg('title')._(title));
  1396. switch (type) {
  1397. case 'bag':
  1398. svg.setAttribute('viewBox', '0 10 60 40');
  1399. svg._(
  1400. _svg('path', { classList: ['bag', 'colour'], attributeList: { d: 'M 20,15 h 40 v 20 h -40 z' } }),
  1401. _svg('path', { classList: ['bag', 'colour'], attributeList: { d: 'M 0,45 L 20,35 h 40 L 40,45 z' } }),
  1402. _svg('path', { classList: ['bag', 'colour'], attributeList: { d: 'M 0,25 L 20,15 v 20 L 0,45 z' } }),
  1403. _svg('path', { classList: ['bag', 'out', 'colour'], attributeList: { d: 'M 0,25 L 20,15 h 40 L 40,25 z' } }),
  1404. _svg('path', { classList: ['bag', 'out', 'colour'], attributeList: { d: 'M 40,25 L 60,15 v 20 L 40,45 z' } }),
  1405. _svg('path', { classList: ['box'], attributeList: { d: 'M 15,34 h 15 v 10 h -15 z' } }),
  1406. _svg('path', { classList: ['box'], attributeList: { d: 'M 15,34 l 5,-5 h 15 l -5,5 z' } }),
  1407. _svg('path', { classList: ['box'], attributeList: { d: 'M 30,34 l 5,-5 v 10 l -5,5 z' } }),
  1408. _svg('path', { classList: ['tape'], attributeList: { d: 'M 21,34 h 3 v 10 h -3 z' } }),
  1409. _svg('path', { classList: ['tape'], attributeList: { d: 'M 21,34 l 5,-5 h 3 l -5,5 z' } }),
  1410. _svg('path', { classList: ['label'], attributeList: { d: 'M 19,33 l 3,-3 h 5 l -3,3 z' } }),
  1411. _svg('path', { classList: ['label'], attributeList: { d: 'M 0,25 h 40 v 20 h -40 z' }, style: { stroke: '#000000', fillOpacity: '0.25' } }),
  1412. );
  1413. break;
  1414. case 'cart':
  1415. svg._(
  1416. _svg('title')._(title),
  1417. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '3', cy: '56' } }),
  1418. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '37', cy: '56' } }),
  1419. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '57', cy: '47' } }),
  1420. _svg('path', { classList: ['cage'], attributeList: { d: 'M 20,45 v-40 h 40 v 40 z h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40 h 4 v -40 v 40z' } }),
  1421. _svg('path', { classList: ['cage', 'floor'], attributeList: { d: 'M 0,55 l 20,-10 h 40 l -20,10 z' } }),
  1422. _svg('path', { classList: ['cage'], attributeList: { d: 'M 0,15 l 20,-10 v 40 l -20,10 z l 4,-2 v 40 v -40 l 4,-2 v 40 v -40 l 4,-2 v 40 v -40 l 4,-2 v 40 v -40 z' } }),
  1423. _svg('path', { classList: ['box'], attributeList: { d: 'M 25,44 h 15 v 10 h -15 z' } }),
  1424. _svg('path', { classList: ['box'], attributeList: { d: 'M 25,44 l 5,-5 h 15 l -5,5 z' } }),
  1425. _svg('path', { classList: ['box'], attributeList: { d: 'M 40,44 l 5,-5 v 10 l -5,5 z' } }),
  1426. _svg('path', { classList: ['tape'], attributeList: { d: 'M 31,44 h 3 v 10 h -3 z' } }),
  1427. _svg('path', { classList: ['tape'], attributeList: { d: 'M 31,44 l 5,-5 h 3 l -5,5 z' } }),
  1428. _svg('path', { classList: ['label'], attributeList: { d: 'M 29,43 l 3,-3 h 5 l -3,3 z' } }),
  1429. _svg('path', { classList: ['cage'], attributeList: { d: 'M 40,15 l 20,-10 v 40 l -20,10 z l 4,-2 v 40 v -40 l 4,-2 v 40 v -40 l 4,-2 v 40 v -40 l 4,-2 v 40 v -40 z' } }),
  1430. );
  1431. break;
  1432. case 'van':
  1433. svg.setAttribute('viewBox', '-30 -30 60 60');
  1434. svg._(
  1435. _svg('circle', { classList: ['van'], attributeList: { r: '6', cx: '-17.5', cy: '15' } }),
  1436. _svg('circle', { classList: ['wheel'], attributeList: { r: '3', cx: '-17.5', cy: '15' } }),
  1437. _svg('path', { classList: ['wheelnut'], attributeList: { d: 'M -17.5,15 l-0.5,-0.5 l 0.5,0.5 l 0.5,-0.5 l -0.5,0.5 l 0.5,0.5 l -0.5,-0.5 l -0.5,0.5 l 0.5,-0.5 z' } }),
  1438. _svg('circle', { classList: ['van'], attributeList: { r: '6', cx: '17.5', cy: '15' } }),
  1439. _svg('circle', { classList: ['wheel'], attributeList: { r: '3', cx: '17.5', cy: '15' } }),
  1440. _svg('path', { classList: ['wheelnut'], attributeList: { d: 'M 17.5,15 l-0.5,-0.5 l 0.5,0.5 l 0.5,-0.5 l -0.5,0.5 l 0.5,0.5 l -0.5,-0.5 l -0.5,0.5 l 0.5,-0.5 z' } }),
  1441. _svg('path', { classList: ['van'], attributeList: { d: 'M -27.5,15 v -15 l 15,-15 h 40 v 30 h -4 q 0,-6 -6,-6 q -6,0 -6,6 h -23 q 0,-6 -6,-6 q -6,0 -6,6 z' } }),
  1442. _svg('path', { classList: ['window'], attributeList: { d: 'M -25,0 l 12.5,-12.5 v 12.5 z' } }),
  1443. _svg('path', { classList: ['smile'], attributeList: { d: 'M -10,-7 q 15,10 30,2 q -15,12 -30,-2 m 28,0 q 7,-3 4,4 q 2,-6 -4,-4' } }),
  1444. );
  1445. break;
  1446. case 'in_trailer':
  1447. svg._(
  1448. _svg('path', { classList: ['cab'], attributeList: { d: 'M 60,45 v -10 l -15,-15 h -3 v 18 h -10 v 7 h 1 q 0,-3 3,-3 q 3,0 3,3 h 1 q 0,-3 3,-3 q 3,0 3,3 h 7 q 0,-3 3,-3 q 3,0 3,3 z' } }),
  1449. _svg('path', { classList: ['trailer'], attributeList: { d: 'M 30.5,45 v -8.5 h 10 v -20.5 h -40 v 29 h 1 q 0,-3 3,-3 q 3,0 3,3 h 1 q 0,-3 3,-3 q 3,0 3,3 h 7 q 0,-3 3,-3 q 3,0 3,3 z' } }),
  1450. _svg('path', { classList: ['window'], attributeList: { d: 'M 57.5,35 l -12.5,-12.5 v 12.5 z' } }),
  1451. _svg('path', { classList: ['tsmile'], attributeList: { d: 'M 3,25 q 15,10 30,2 q -15,12 -30,-2 m 28,0 q 7,-3 4,4 q 2,-6 -4,-4' } }),
  1452. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '56', cy: '45.5' } }),
  1453. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '43', cy: '45.5' } }),
  1454. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '36', cy: '45.5' } }),
  1455. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '24.5', cy: '45.5' } }),
  1456. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '11.5', cy: '45.5' } }),
  1457. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '4.5', cy: '45.5' } }),
  1458. );
  1459. break;
  1460. case 'trailer':
  1461. svg._(
  1462. _svg('path', { classList: ['cab'], attributeList: { d: 'M 0,45 v -10 l 15,-15 h 3 v 18 h 10 v 7 h -1 q 0,-3 -3,-3 q -3,0 -3,3 h -1 q 0,-3 -3,-3 q -3,0 -3,3 h -7 q 0,-3 -3,-3 q -3,0 -3,3 z' } }),
  1463. _svg('path', { classList: ['trailer'], attributeList: { d: 'M 29.5,45 v -8.5 h -10 v -20.5 h 40 v 29 h -1 q 0,-3 -3,-3 q -3,0 -3,3 h -1 q 0,-3 -3,-3 q -3,0 -3,3 h -7 q 0,-3 -3,-3 q -3,0 -3,3 z' } }),
  1464. _svg('path', { classList: ['window'], attributeList: { d: 'M 2.5,35 l 12.5,-12.5 v 12.5 z' } }),
  1465. _svg('path', { classList: ['tsmile'], attributeList: { d: 'M 23,25 q 15,10 30,2 q -15,12 -30,-2 m 28,0 q 7,-3 4,4 q 2,-6 -4,-4' } }),
  1466. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '4', cy: '45.5' } }),
  1467. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '17', cy: '45.5' } }),
  1468. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '24', cy: '45.5' } }),
  1469. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '35.5', cy: '45.5' } }),
  1470. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '48.5', cy: '45.5' } }),
  1471. _svg('circle', { classList: ['wheel'], attributeList: { r: '2', cx: '55.5', cy: '45.5' } }),
  1472. );
  1473. break;
  1474. case 'avery':
  1475. svg._(
  1476. _svg('g', { attributeList: { transform: 'rotate(-30 30 30)' } })._(
  1477. _svg('path', { classList: ['print'], attributeList: { d: 'M 20,12 l 24,-6 q 10,25 0,25 v 20 h 5 v 10 h -20 v -10 h 10 v -20 l -16,-4 q -4,-6 -4,-10 z' } }),
  1478. _svg('path', { classList: ['deco'], attributeList: { d: 'M 28,15 l 12,-3 q 0,6 -6,6 h -6 z' } }),
  1479. _svg('path', { classList: ['clip'], attributeList: { d: 'M 21,17 l 6,-2 v 3 h -6 z' } }),
  1480. ),
  1481. _svg('path', { classList: ['box'], attributeList: { d: 'M 5,44 h 15 v 10 h -15 z' } }),
  1482. _svg('path', { classList: ['box'], attributeList: { d: 'M 5,44 l 5,-5 h 15 l -5,5 z' } }),
  1483. _svg('path', { classList: ['box'], attributeList: { d: 'M 20,44 l 5,-5 v 10 l -5,5 z' } }),
  1484. _svg('path', { classList: ['tape'], attributeList: { d: 'M 11,44 h 3 v 10 h -3 z' } }),
  1485. _svg('path', { classList: ['tape'], attributeList: { d: 'M 11,44 l 5,-5 h 3 l -5,5 z' } }),
  1486. _svg('path', { classList: ['label'], attributeList: { d: 'M 9,43 l 3,-3 h 5 l -3,3 z' } }),
  1487. );
  1488. break;
  1489. default:
  1490. break;
  1491. }
  1492. return svg;
  1493. }
  1494. /** @type {(method: string | undefined | null, node: HTMLElement) => void} */
  1495. function colourResult(method, node) {
  1496. if (!method) return;
  1497. const m = S(method);
  1498. m.contains('AGEVER') && node.classList.add('AVD');
  1499. m.contains('OUT') && node.classList.add('SWA');
  1500. m.contains('LOCKER') && node.classList.add('LOCK');
  1501. m.contains('SAME') && node.classList.add('SAME');
  1502. }
  1503. /** @type {(node: HTMLElement | ParentNode | null, count?: number) => ParentNode?} */
  1504. const parentNode = (node, count = 1) => node && count ? (--count ? parentNode(node.parentNode, count) : node.parentNode) : node;
  1505. /** @type {(node: HTMLElement, parents?: number) => void} */
  1506. function miniBadge(node, parents = 1) {
  1507. if (['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'Total', '...', '-'].includes(node?.innerText) || node?.classList.contains('badge') || !node?.innerText) return;
  1508. node?.classList.add('badge');
  1509. const oldInnerText = node.innerText.split('@')[0];
  1510. parentNode(node, parents)?.append(_('a', { href: `https://fclm-portal.amazon.com/employee/ppaTimeDetails?employeeId=${oldInnerText}`, target: '_blank', classList: ['minibadge'] })._(_('img', { src: `https://internal-cdn.amazon.com/badgephotos.amazon.com/?login=${oldInnerText}`, loading: 'lazy', classList: ['minibadge'] })));
  1511. }
  1512. /** @type {(url: ExtendedString) => Extended<HTMLImageElement> | undefined} */
  1513. const _buttonImg = (url) => {
  1514. if (url.contains('royalmail')) return _('img', { src: 'https://www.royalmail.com/themes/custom/rmlcwr/favicon.ico' });
  1515. if (url.contains('maps.google.com')) return _('img', { src: 'https://www.google.com/images/branding/product/ico/maps15_bnuw3a_32dp.ico' });
  1516. if (url.contains('bing.com')) return _('img', { src: 'https://www.bing.com/sa/simg/favicon-2x.ico' });
  1517. if (url.contains('duckduckgo')) return _('img', { src: 'https://duckduckgo.com/favicon.ico' });
  1518. if (url.contains('gamtools')) return _('img', { src: 'https://m.media-amazon.com/images/G/01/ate/gam/tools/favicon.png' });
  1519. if (url.contains('eagleeye.amazon.dev')) return _('img', { src: 'https://eagleeye.amazon.dev/favicon.ico' });
  1520. if (url.contains('eagleeye')) return _('img', { src: 'data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsSAAALEgHS3X78AAADRElEQVQ4jYXTT2gcVRwH8O9v/tiNO5NkdhqNDVm3Zktg4qZqiKK4hcJGkBpYXBExIRjIRS8lnoRqIYcYySGXeiiIR9lK6CU5mIQcTIuwQQSFENvaCEm2lWRhszvzZgMh+35edpekTtffaXiP3+d9md97hIBi5ogQ4iMAKWa+CMAmIh/AAwB3ANwyTfPPoF56AtJ8378qpbwOoDWooVZVIroF4HPTNPcDQWY2hBC3mfmdJtDpNESPiOiKYRh/nAKZWRNC/MTMqZMNBwcHWF9fx+7uLjzPQ0dHB3p7ezE4OAgiqqMFXdffDIVCWw1QCPGFlHKmDm1ubmJ2dhaLi4uQUsJxHDiOA8uyUC6Xsb+/j+HhYYyPj4OIQER3DcO4TERVxfO856SU12pJcePbG0gmk1hYWMDIyAhyuRyWl5eRTqdx6Puw83kMDAzAdV2Mjo4im82iVColhRAfoJbuquu67LouT09Ps33W5mg0yisrK1wqldh1Xd7Y2OC1tTX+4eZNXotG+axtc39/P4+NjbFlWRyPx3lpaWkVAEgIsSilfO/erz9j8quvIZkwNzeHvr6+wEHk83nout74x0dHR2hpacHMzMzh/Px8hyalTMjjI7y6/hl+fKOIh5e/g+M4gVihUEAmk0E+n0cikUB3dzd830elUkFra2uL7/txDUD7/b+28Is/DKf9H7z+9ruNCZ4sZsbU1BT29vagqipSqRQmJiZgmiYKhQKy2SxUVW3TiKhcLpfbnnmhD1tKAm9p2lPv3dDASyjci+DDT68hk8k01js7OzE5OQlVVV2NmTfOhM5Eu7q6YNv2UzEASF+M4OXHOmLp9H/2iOhQ1/WHiqIoqxfiF9DT04Nnw+Gm4N/tl7Aafh+qqgZt54hIKACy4XC4EovF0HXuXFMwdv482jpfBDMHJfweqL0Uz/OuM/NUM4yZQUTwPA+hUKhxdWpYzjCMJBEdKwCwvb39DYC7zcD65A3DgHZ6cEVd1z8houNGQgAoFottmqYtALjUDH7ikD1FUa6Ew+Hf6mtK/SMSiZR3dnaGVFX9kpkr/2NJIrqtKMprJ7FTCU+WEOJ5AB8DGGLmV4jIYuZDAPeZ+U61Ws1alvV7UO+/+k9aMjlySqQAAAAASUVORK5CYII=' });
  1521. if (url.contains('hero')) return _('img', { src: 'https://hero.eu.picking.aft.a2z.com/favicon.ico' });
  1522. if (url.contains('sim.amazon.com')) return _('img', { src: 'https://sim.amazon.com/images/sim-favicon-new.ico' });
  1523. if (url.contains('ordnancesurvey.co.uk')) return _('img', { src: 'https://osmaps.ordnancesurvey.co.uk/bundles/app/css/favicon.ico' });
  1524. if (url.contains('explore.osmaps.com')) return _('img', { src: 'https://explore.osmaps.com/images/logo/osmaps-logo-triangle.svg' });
  1525. };
  1526. /** @type {(url: string, buttonText: string, order: number, windowProps?: {id?: string} & WindowProps) => void} */
  1527. function addButton(url, buttonText, order, { id, name, ...windowProps } = {}) {
  1528. id ??= `${name ?? buttonText}_button`.replace(/[^a-zA-Z0-9\-_]/gm, '');
  1529. promises.topBar.then((improveBoxes) => improveBoxes.buttons._(($(`button#${id}`, { style: { order: `${order}` } }) ?? _('button', { id, style: { order: `${order}` } })).on('click', () => buttonclick(url, { name, ...windowProps })).__(_buttonImg(S(url)), buttonText)));
  1530. }
  1531. /** @type {() => void} */
  1532. function specialButtons() {
  1533. const
  1534. qrButton = ($(`button#QR`) ?? _('button', { id: 'QR', style: { order: '-1' } })).__(s('qrCode')),
  1535. podButton = ($(`button#POD`) ?? _('button', { id: 'POD', style: { order: '-1' } })).__(s('viewPOD'));
  1536. qrButton.onclick = showQR; podButton.onclick = getPODData;
  1537. promises.topBar.then((improveBoxes) => improveBoxes.buttons._(qrButton, podButton));
  1538. }
  1539. function showQR() {
  1540. const contentSection = _('section'),
  1541. dialog = _('dialog', { classList: ['QR'] })._(_('button', { classList: ['close'] })._('X').on('click', () => { dialog.close(); contentSection.remove(); dialog.remove(); }), contentSection);
  1542. document.body.append(dialog);
  1543. queueMicrotask(() => {
  1544. dialog.showModal();
  1545. new QRCode(contentSection, page.trackingId);
  1546. });
  1547. }
  1548. /** @type {(url: string, windowProps?: WindowProps) => Extended<HTMLImageElement>} */
  1549. function miniButton(url, windowProps) {
  1550. const img = _buttonImg(S(url));
  1551. if (!img) { throw 'Unable to generate minibut img for url'; }
  1552. img.classList.add('minibut');
  1553. return img.on('click', () => buttonclick(url, windowProps));
  1554. }
  1555. function addrbuts(packageData) {
  1556. debuglog('addrbuts()');
  1557. if (packageData.customerInfo.customerAddress) {
  1558. if (/^[A-Z]{1,2}[0-9]{1,2}[A-Z]{0,1} ?[0-9][A-Z]{2}$/i.test(packageData.pcd.trim().toUpperCase()) || S(packageData.customerInfo.customerAddress).contains('United Kingdom')) { addButton(`https://www.royalmail.com/find-a-postcode#${packageData.customerInfo.customerAddress}`, s('RoyalMail'), 3, { name: 'RMpostcode' }); }
  1559. addAddrButs(s('Address'), packageData.customerInfo.customerAddress.replace(/\//g, '-').replace(/&/gi, 'and').replace('#', ''), 3);
  1560. }
  1561. if (/Amazon.*Locker/i.test(packageData.customerInfo.customerName)) {
  1562. addButton(`https://locker${page.region == 'na' ? '' : `-${page.region}`}.amazon.com/search?searchType=kioskPostalCode&param=${packageData.pcd}`, s('LockerStatus'), 2);
  1563. addButton(`https://locker${page.region == 'na' ? '' : `-${page.region}`}.amazon.com/search?searchType=externalReferenceID&param=${page.trackingId}`, s('LockerReserve'), 2);
  1564. addAddrButs(s('Locker'), `${packageData.customerInfo.customerName} ${packageData.pcd}`, 5);
  1565. }
  1566. if (/Amazon.*Counter/i.test(packageData.customerInfo.customerName)) {
  1567. addAddrButs(s('Counter'), `${packageData.customerInfo.customerName.replace(/.* - /i, '')} ${packageData.customerInfo.customerAddress.replace(/\//g, '-')}`, 5);
  1568. }
  1569. }
  1570. /** @type {(buttonText: string, mapSearchString: string, order: number) => void} */
  1571. function addAddrButs(buttonText, mapSearchString, order) {
  1572. const id = `${buttonText}_button`.replace(/[^a-zA-Z0-9\-_]/gm, '');
  1573. promises.topBar?.then((improveBoxes) => {
  1574. improveBoxes.buttons._(($(`div.button_group#${id}`) ?? _('div', { id, style: { order: `${order}` }, classList: ['button_group'] })).__(
  1575. buttonText,
  1576. ));
  1577. });
  1578. }
  1579. /** @type {(url: string, windowProps?: WindowProps) => void} */
  1580. function buttonclick(url, { w = 1200, h = 800, name = '' } = {}) { window.open(encodeURI(url).replace(/'/g, '%27'), name, `width=${w}, height=${h}, noopener, noreferrer`); }
  1581. /** @type {(lat: string|number, lon: string|number) => Extended<HTMLSpanElement>} */
  1582. function distanceNode(lat, lon) {
  1583. debuglog('distanceNode()');
  1584. const distSpan = _('span', { classList: ['dist'] })._('…');
  1585. promises.geo?.then((geoData) => {
  1586. if (!geoData.validLatLon) { return distSpan.remove(); }
  1587. debuglog(`${lat} - ${lon} - ${geoData.latitude} - ${geoData.longitude}`);
  1588. const clat_1 = (90.0 - Number(lat)) * Math.PI / 180.0,
  1589. clon_1 = (Number(lon)) * Math.PI / 180.0,
  1590. clat_2 = (90.0 - Number(geoData.latitude)) * Math.PI / 180.0,
  1591. clon_2 = Number(geoData.longitude) * Math.PI / 180.0,
  1592. d = Math.acos(Math.sin(clat_1) * Math.sin(clat_2) * Math.cos(clon_1 - clon_2) + Math.cos(clat_1) * Math.cos(clat_2));
  1593. if (d >= 0.5 / 6370) { distSpan.classList.add('longDistance'); }
  1594. distSpan._(
  1595. `${s('calcText').replace('{{ft#}}', formatUnit({ value: (d * 3960 * 5280), unit: 'foot' })).replace('{{m#}}', formatUnit({ value: (d * 6370 * 1000), unit: 'meter' }))})\u2002`,
  1596. );
  1597. }, () => distSpan.remove());
  1598. return distSpan;
  1599. }
  1600. /** @type {(text?: string) => void} */
  1601. function copyText(text) {
  1602. text && navigator.clipboard.writeText(text).then(() => notify("Copied to clipboard", text)).catch(e => { notify('Copy Failed', e); console.warn(e); });
  1603. }
  1604. function closeNotification() { notification?.close(); }
  1605. /** @type {(title: string, content: string, closeDelay?:number) => void} */
  1606. function notify(title, content, closeDelay = 3) {
  1607. if (Notification.permission !== "denied" && Notification.permission !== "granted") { Notification.requestPermission(); }
  1608. if (Notification.permission !== "granted") { alert(`${title}\n\n${content}`); return; }
  1609. closeNotification();
  1610. notification = new Notification(title, { body: content, silent: true });
  1611. delay(closeDelay).then(closeNotification);
  1612. }
  1613. function historyFormatMenu() {
  1614. const
  1615. localOption = { colours: 'OnRoad', ...JSON.parse(localStorage.getItem('SCC_Search_CONFIG') ?? '{}') },
  1616. onChange = (newValue) => {
  1617. const tabs = $$("[role=tablist] label");
  1618. localOption.colours = newValue;
  1619. localStorage.setItem('SCC_Search_CONFIG', JSON.stringify(localOption));
  1620. tabs[0].do('click'); tabs[3].do('click');
  1621. };
  1622. return {
  1623. colourValue: localOption.colours,
  1624. menuElement: _('div', { id: 'SCC_Search_CONFIG' })._(
  1625. _('strong')._(s('formatTable')),
  1626. [{ label: s('onRoadOption'), value: 'OnRoad' }, { label: s('inStationOption'), value: 'InStation' }].map(({ label, value }) => _('label')._(label, _('input', { type: 'radio', name: 'colours', value, checked: value == localOption.colours }).on('change', () => onChange(value))))
  1627. )
  1628. };
  1629. }
  1630. //#endregion
  1631.  
  1632. //#region: CSS
  1633. function addCSS() {
  1634. wait$('head').then(head => head._(_('meta', { name: 'theme-color', content: '#ffffff' })));
  1635. _css(`
  1636. /* All Pages */
  1637. :root {
  1638. color-scheme: light;
  1639. --redBG: #FFAAAA;
  1640. --yellowBG: #FFFFAA;
  1641. --greenBG: #AAFFAA;
  1642. --blueBG: #AAFFFF;
  1643. --matchBG: white;
  1644. --greenTableBG: #96FF96;
  1645. --yellowTableBG: #FFFF96;
  1646. --redTableBG: #FF9696;
  1647. --blueTableBG: #9696FF;
  1648. --cyanTableBG: #96FFFF;
  1649. --greenText: forestgreen;
  1650. --orangeText: darkorange;
  1651. --redText: firebrick;
  1652. --smile: #FF9900;
  1653. --squidInk: #232F3E;
  1654. --primeBlue: #0096D6;
  1655. }
  1656. :root[data-improve-scc-css-dark-mode="dark"] {
  1657. color-scheme: dark;
  1658. --redBG: #640000;
  1659. --yellowBG: #646400;
  1660. --greenBG: #006400;
  1661. --blueBG: #00004B;
  1662. --matchBG: #232F3E;
  1663. --greenTableBG: #003200;
  1664. --yellowTableBG: #323200;
  1665. --redTableBG: #320000;
  1666. --blueTableBG: #000032;
  1667. --cyanTableBG: #006464;
  1668. --greenText: #00FF00;
  1669. --orangeText: #FFFF00;
  1670. --redText: #FF0000;
  1671. & :is(.fp-navigation-container, .fp-navigation-container .fp-nav-menu, .fp-page-template, .css-avryxx, .css-m7c1h1, .css-1ekky2x, #verdiv, #changelog ul, #changelog li, .css-sdn4iy:hover) {
  1672. background-color: var(--squidInk)!important;
  1673. }
  1674. }
  1675. *, *:before, *:after {
  1676. box-sizing: border-box;
  1677. font-family: "Amazon Ember", Sans-serif;
  1678. margin: 0;
  1679. padding: 0;
  1680. border-collapse: collapse;
  1681. vertical-align: middle;
  1682. }
  1683. ::-webkit-scrollbar, ::-webkit-scrollbar-track, ::-webkit-scrollbar-corner, ::-webkit-scrollbar-thumb {
  1684. width: 1rem;
  1685. height: 1rem;
  1686. border: 0.25rem solid transparent;
  1687. }
  1688. ::-webkit-scrollbar, ::-webkit-scrollbar-corner {
  1689. background: inherit;
  1690. }
  1691. ::-webkit-scrollbar-track {
  1692. box-shadow: inset 0 0 0 0.0625rem var(--primeBlue);
  1693. border-radius: 1rem;
  1694. }
  1695. ::-webkit-scrollbar-thumb {
  1696. box-shadow: inset 0 0 0 1rem var(--primeBlue);
  1697. border-radius: 1rem;
  1698. }
  1699. @supports not selector(::-webkit-scrollbar) {
  1700. * {
  1701. scrollbar-color: var(--primeBlue) transparent;
  1702. }
  1703. }
  1704. dialog {
  1705. max-height: 60vh;
  1706. max-height: 60svh;
  1707. padding-block-start: 3rem;
  1708. & #debugModalButtons {
  1709. position: absolute;
  1710. inset-block-start: 1rem;
  1711. inset-inline-end: 1rem;
  1712. font-size: 200%;
  1713. & > button {
  1714. padding: 0.25rem;
  1715. margin-inline: 0.25rem;
  1716. }
  1717. }
  1718. }
  1719. :root[data-improve-scc-css-noframe] {
  1720. zoom: 0.9;
  1721. &:not([data-improve-scc-show-top-menu]) {
  1722. display: flex;
  1723. flex-flow: column nowrap;
  1724. height: 100%;
  1725. overflow: auto;
  1726. & :is(header.fp-header, div.fp-navigation-container) {
  1727. display: none;
  1728. }
  1729. & .is-it-down-stripe {
  1730. flex: 0 0 0;
  1731. z-index: unset;
  1732. position: unset;
  1733. overflow: unset;
  1734. }
  1735. & body {
  1736. flex: 1 1 100%;
  1737. overflow: auto;
  1738. margin: 0;
  1739. padding: 0;
  1740. height: unset;
  1741. min-height: unset;
  1742. & > div {
  1743. display: flex;
  1744. flex-flow: column nowrap;
  1745. height: 100%;
  1746. overflow: auto;
  1747. }
  1748. & .boson-meridian-frame-dimension {
  1749. position: unset;
  1750. top: unset;
  1751. bottom: unset;
  1752. flex: 1 1 100%;
  1753. }
  1754. }
  1755. }
  1756. & #scc_alert {
  1757. padding: 0;
  1758. :root[data-improve-scc-css-hide-alerts] &, & .boson-display-none {
  1759. display: none!important;
  1760. }
  1761. }
  1762. & #dkingamzNav {
  1763. color: canvastext;
  1764. text-align: center;
  1765. border-top: 1px solid #BBC0C1;
  1766. & li {
  1767. list-style: none;
  1768. & button {
  1769. background: transparent;
  1770. border: none;
  1771. }
  1772. }
  1773. }
  1774. & #main_row {
  1775. position: relative;
  1776. }
  1777. & #taskContainer {
  1778. background: var(--matchBG);
  1779. border-block-end: 1px solid #C0C0C0;
  1780. }
  1781. & #verdiv {
  1782. position: absolute;
  1783. top: -1px;
  1784. right: -0.125rem;
  1785. color: canvastext;
  1786. background: var(--matchBG);
  1787. padding: 0.5rem;
  1788. border-radius: 0 0 0 1em;
  1789. border: 1px solid #C0C0C0;
  1790. font-size: 1.2rem;
  1791. &.hidden {
  1792. transform: translate(100%) translate(-2rem);
  1793. &::before {
  1794. content: '<\u2003';
  1795. }
  1796. }
  1797. }
  1798. & :is(#dkingamzNav, #verdiv) a {
  1799. color: inherit;
  1800. text-decoration: underline;
  1801. & strong {
  1802. padding: 0.25em;
  1803. }
  1804. }
  1805. }
  1806. :root[data-improve-scc-css-iframe] {
  1807. margin-block-start: 0.5rem;
  1808. & img.minibadge {
  1809. border: 0.125rem ridge silver;
  1810. border-radius: 0.5rem;
  1811. &:not(.pkmn) {
  1812. max-height: 80px;
  1813. max-width: 60px;
  1814. }
  1815. }
  1816. & :has( > a.minibadge) {
  1817. display: inline-flex;
  1818. flex-flow: column-reverse nowrap;
  1819. align-items: center;
  1820. align-content: center;
  1821. padding: 0.375rem;
  1822. & a {
  1823. padding: 0!important;
  1824. margin: 0!important;
  1825. }
  1826. }
  1827. & #exportLogs {
  1828. position: fixed;
  1829. inset-block-start: 3rem;
  1830. inset-inline-end: 2rem;
  1831. background: canvas;
  1832. padding: 0.5rem;
  1833. font-size: 200%;
  1834. }
  1835. }
  1836. :root:is([data-improve-scc-css-search-page],[data-improve-scc-css-details-page]) {
  1837. & #root#root {
  1838. & .AVD {
  1839. &, & th, & td {
  1840. background: var(--redBG);
  1841. }
  1842. }
  1843. & .LOCK {
  1844. &, & th, & td {
  1845. background: var(--yellowBG);
  1846. }
  1847. }
  1848. & .SAME {
  1849. &, & th, & td {
  1850. background: var(--greenBG);
  1851. }
  1852. }
  1853. & .SWA {
  1854. &, & th, & td {
  1855. background: var(--blueBG);
  1856. }
  1857. }
  1858. }
  1859. }
  1860. :root[data-improve-scc-css-search-page] {
  1861. & :is(tr, th, td) {
  1862. background-color: inherit;
  1863. border-block-end: 1px outset;
  1864. }
  1865. & tr[data-row-number]::before {
  1866. content: attr(data-row-number);
  1867. display: table-cell;
  1868. color: canvastext;
  1869. padding: 8px;
  1870. border-block-end: 1px outset;
  1871. }
  1872. & tr[data-vrid]::after {
  1873. content: attr(data-vrid);
  1874. display: table-cell;
  1875. color: canvastext;
  1876. padding: 8px;
  1877. border-block-end: 1px outset;
  1878. }
  1879. & tr[data-exchange-id] td:last-child::after {
  1880. content: attr(data-exchange-id);
  1881. display: table-cell;
  1882. color: canvastext;
  1883. padding: 8px;
  1884. border-block-end: 1px outset;
  1885. }
  1886. & .css-vps56m {
  1887. margin-block-end: 0!important;
  1888. padding: 0;
  1889. }
  1890. }
  1891. :root[data-improve-scc-css-details-page] {
  1892. /*main box below root*/
  1893. & .css-xx5nu9 {
  1894. padding: 2px!important;
  1895. /*main rows below main box*/
  1896. & > :is(div,ul) {
  1897. margin: 2px!important;
  1898. padding: 0 2px!important;
  1899. }
  1900. }
  1901. &[data-improve-scc-xhr] :is(.css-rx4572, .css-rx4572 + .css-14fuupd) {
  1902. display: none!important;
  1903. }
  1904. & #SCC_Search_CONFIG {
  1905. display: flex;
  1906. gap: 0.5rem;
  1907. & label {
  1908. display: inline-flex;
  1909. gap: 0.5rem;
  1910. }
  1911. }
  1912. & #improveRoot {
  1913. margin-block-start: -1px;
  1914. & > div {
  1915. border-block-end: 2px groove;
  1916. border-radius: 0;
  1917. }
  1918. & #improveHead {
  1919. font-family: "Amazon Ember", "Amazon Ember Arabic", Arial, sans-serif;
  1920. color: canvastext;
  1921. display: grid;
  1922. grid-template-columns: max-content min-content max-content 1fr;
  1923. column-rule: 2px solid currentcolor;
  1924. column-rule-outset: 0;
  1925. gap: 4px;
  1926. align-items: center;
  1927. border-radius: 1em 1em 0 0;
  1928. & h1 {
  1929. font-weight: 600;
  1930. font-size: 36px;
  1931. line-height: 48px;
  1932. margin: 0;
  1933. }
  1934. & > div {
  1935. display: flex;
  1936. flex-flow: column nowrap;
  1937. &:not(#customerColumn) * {
  1938. text-align: center;
  1939. }
  1940. &:first-of-type > * {
  1941. padding-inline-start: 0;
  1942. }
  1943. &:last-of-type > * {
  1944. padding-inline-end: 0;
  1945. }
  1946. }
  1947. & div {
  1948. & > * {
  1949. padding: 0 0.25em;
  1950. }
  1951. &.spaced {
  1952. display:flex;
  1953. flex-flow: row nowrap;
  1954. justify-content: space-around;
  1955. & > * {
  1956. margin: 0 0.5em;
  1957. &:first-of-type {
  1958. margin-left: 0;
  1959. }
  1960. &:last-of-type {
  1961. margin-right: 0;
  1962. }
  1963. }
  1964. }
  1965. &.simple-flex {
  1966. display:flex
  1967. }
  1968. }
  1969. & #oid {
  1970. &.vret {
  1971. background: var(--yellowTableBG);
  1972. }
  1973. &.covid {
  1974. background: var(--redTableBG);
  1975. }
  1976. }
  1977. & #flags {
  1978. justify-content: space-evenly;
  1979. align-self: stretch;
  1980. padding-inline: 0.25rem;
  1981. & > strong {
  1982. margin: 0;
  1983. font-size: 1.2rem;
  1984. }
  1985. }
  1986. & #customerColumn {
  1987. flex-grow: 1;
  1988. flex-shrink: 1;
  1989. & > div > strong {
  1990. padding: 0;
  1991. }
  1992. & #custNotes {
  1993. padding-left: 3em!important;
  1994. & > div {
  1995. text-indent: -1.5em;
  1996. }
  1997. }
  1998. }
  1999. }
  2000. & #podContainer {
  2001. padding:0.125rem;
  2002. &:not(:has(>*)) {
  2003. display:none;
  2004. }
  2005. & section {
  2006. min-height: 2rem;
  2007. display: grid;
  2008. grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  2009. grid-auto-rows: max-content;
  2010. & .pod-item {
  2011. display:grid;
  2012. grid-template-columns: 1fr;
  2013. text-align: center;
  2014. & .pod-img {
  2015. max-width: 100%;
  2016. margin-inline: auto;
  2017. }
  2018. & :has(> .minibut) {
  2019. padding-block: 0.6rem;
  2020. }
  2021. }
  2022. }
  2023. }
  2024. & #transport {
  2025. &, & > div {
  2026. display: flex;
  2027. flex-flow: row wrap;
  2028. justify-content: space-around;
  2029. }
  2030. & > div {
  2031. flex: 1 1 auto;
  2032. border-radius: 0;
  2033. &:not(:first-child) {
  2034. border-inline-start: 1px groove currentcolor;
  2035. }
  2036. }
  2037. }
  2038. & #gamrow {
  2039. text-align: center;
  2040. }
  2041. & #buttons {
  2042. display: flex;
  2043. flex-flow: row wrap;
  2044. align-content: space-around;
  2045. justify-content: space-around;
  2046. padding-block: 0.375rem;
  2047. & > :is(button, div) {
  2048. appearance: none;
  2049. display: flex;
  2050. flex-flow: row nowrap;
  2051. align-items: center;
  2052. justify-content: center;
  2053. font-family: "Amazon Ember";
  2054. font-size: 1.1rem;
  2055. line-height: 1.1rem;
  2056. background: #077398;
  2057. color: white;
  2058. margin: 0.375rem;
  2059. padding: 0.125rem 0.5rem;
  2060. border: none;
  2061. border-radius: 0.25rem;
  2062. &, & img {
  2063. box-shadow: 0 0 0.25rem 0 #077398;
  2064. }
  2065. }
  2066. & button {
  2067. margin-inline-start: 0.5rem;
  2068. cursor: pointer;
  2069. &:hover {
  2070. &, & img {
  2071. box-shadow: inset 0 0 0.25rem 0 canvas, 0 0 0.5rem 0 canvastext;
  2072. }
  2073. }
  2074. & img {
  2075. margin-inline-start: -1rem;
  2076. margin-inline-end: 0.25rem;
  2077. }
  2078. }
  2079. }
  2080. & .errMsg {
  2081. color: var(--redText);
  2082. font-size: small;
  2083. }
  2084. & :is(.AVD, .LOCK, .SAME, .SWA) {
  2085. padding-left: 12px;
  2086. border-radius: 1em 1em 0 0;
  2087. }
  2088. }
  2089. & :is(#buttons button img, .minibut) {
  2090. height: 2.2rem;
  2091. padding: 0.25rem;
  2092. background: white;
  2093. border-radius: 100%;
  2094. overflow: hidden;
  2095. cursor: pointer;
  2096. margin-block: -1.5rem;
  2097. }
  2098. & .minibut{
  2099. margin-inline: 0.125rem;
  2100. margin-block: -0.6rem;
  2101. vertical-align: middle;
  2102. &:hover {
  2103. box-shadow: inset 0 0 0.25rem 0 canvas, 0 0 0.5rem 0 canvastext;
  2104. }
  2105. &:first-of-type {
  2106. margin-inline-start: 0.75rem;
  2107. }
  2108. &:last-of-type {
  2109. margin-inline-end: -0.25rem;
  2110. }
  2111. }
  2112. & table[data-improve-scc=true] {
  2113. & button {
  2114. border-radius: 0.375rem;
  2115. margin-inline: 0.125rem;
  2116. padding-inline: 0.25rem;
  2117. padding-block: 0.125rem;
  2118. &.copyButton {
  2119. display: inline-block;
  2120. }
  2121. }
  2122. & :is(thead, tfoot) th {
  2123. text-align: start;
  2124. padding: 0;
  2125. &:not(:has( > *)) {
  2126. border: none;
  2127. }
  2128. & > * {
  2129. margin: 0.5rem;
  2130. }
  2131. }
  2132. & td:first-child {
  2133. white-space: nowrap;
  2134. }
  2135. & :is(.newRow, .newRow td, .newRow th) {
  2136. border: 2px inset;
  2137. border-block-end: none;
  2138. }
  2139. & tr.newRow {
  2140. & td > * {
  2141. margin-inline-end: 0.75rem;
  2142. }
  2143. & td > .photoImg {
  2144. display: block;
  2145. width: 100%;
  2146. max-width: calc(120vw - 1rem);
  2147. max-height: calc(120vh - 1rem);
  2148. object-fit: contain;
  2149. margin-block-start: 0.75rem;
  2150. &.hide {
  2151. display: none;
  2152. }
  2153. }
  2154. }
  2155. & tr.newRow + tr {
  2156. border: 2px inset;
  2157. border-top: none;
  2158. &, & td, & th {
  2159. border-block-end: 2px outset;
  2160. }
  2161. & :is(td:first-of-type, th:first-of-type) {
  2162. border-left: 2px inset;
  2163. }
  2164. & :is(td:last-of-type, th:last-of-type) {
  2165. border-right: 2px inset;
  2166. }
  2167. }
  2168. & .newDay.newRow:not(.newRow + tr) {
  2169. &, & td, & th {
  2170. border-top: 3px solid currentcolor;
  2171. }
  2172. }
  2173. & :is(.delSuc, .delSuc td, .delSuc th) {
  2174. background: var(--greenTableBG)!important;
  2175. }
  2176. & :is(.delFail, .delFail td, .delFail th) {
  2177. background: var(--yellowTableBG)!important;
  2178. }
  2179. & :is(.endFail, .endFail td, .endFail th) {
  2180. background: var(--redTableBG)!important;
  2181. }
  2182. & :is(.rts, .rts td, .rts th) {
  2183. background: var(--blueTableBG)!important;
  2184. }
  2185. & :is(.oor, .oor td, .oor th) {
  2186. background: var(--cyanTableBG)!important;
  2187. }
  2188. & .goodTime {
  2189. color: var(--greenText)!important;
  2190. }
  2191. & .poorTime {
  2192. color: var(--orangeText)!important;
  2193. }
  2194. & :is(.badTime, .longDistance) {
  2195. color: var(--redText)!important;
  2196. }
  2197. & svg.bag {
  2198. background: silver;
  2199. border-radius: 0.75rem;
  2200. max-height: 100%;
  2201. position: absolute;
  2202. inset-block: 0;
  2203. inset-inline-end: 0;
  2204. margin-block: auto;
  2205. max-width: 3rem;
  2206. &.BLK .colour{
  2207. fill: black;
  2208. }
  2209. &.GRN .colour{
  2210. fill: green;
  2211. }
  2212. &.NVY .colour{
  2213. fill: blue;
  2214. }
  2215. &.YLO .colour{
  2216. fill: yellow;
  2217. }
  2218. &.ORG .colour{
  2219. fill: orange;
  2220. }
  2221. }
  2222. & .die {
  2223. display: none;
  2224. }
  2225. }
  2226. & [role=tablist] label {
  2227. line-height: 1rem;
  2228. flex: 1 1 auto;
  2229. & > div:last-child {
  2230. margin-block: 0;
  2231. }
  2232. & > span > div {
  2233. padding-block: 0.125rem;
  2234. padding-inline: 1rem;
  2235. }
  2236. }
  2237. & :is(#flags > strong, .goodTime, .poorTime, .badTime, .longDistance) {
  2238. background: canvas;
  2239. border-radius: 0.5rem;
  2240. padding: 0.0625em 0.25rem;
  2241. margin-block: 0.125rem;
  2242. }
  2243. & div > a.minibadge {
  2244. position: absolute;
  2245. display: none;
  2246. right: 8px;
  2247. top: -32px;
  2248. z-index: 10;
  2249. }
  2250. & div:hover > .minibadge {
  2251. display: initial;
  2252. }
  2253. & :is(b,strong) > .minibadge {
  2254. display: none;
  2255. position: relative;
  2256. background: white;
  2257. border: 3px ridge silver;
  2258. border-radius: 8px;
  2259. margin: -3em 0;
  2260. height: 7em;
  2261. z-index: 10;
  2262. }
  2263. & :is(b,strong):hover > .minibadge {
  2264. display: block;
  2265. }
  2266. }
  2267. :root[data-improve-scc-css-receive-page] {
  2268. & .fmcdone {
  2269. text-decoration: underline;
  2270. }
  2271. }
  2272. :root[data-improve-scc-css-outbound-page] {
  2273. font-size: 0.8rem;
  2274. & * {
  2275. font-size: inherit!important;
  2276. line-height: inherit!important;
  2277. }
  2278. & div:has( table) {
  2279. overflow-x: unset!important;
  2280. }
  2281. & th {
  2282. padding-inline-end: 0.25rem;
  2283. }
  2284. & :is(th, td) {
  2285. padding-inline-start: 0.25rem;
  2286. padding-block: 0.25rem;
  2287. }
  2288. & #root div.highlighted-warning {
  2289. color: black;
  2290. text-align: center;
  2291. }
  2292. }
  2293. :root[data-improve-scc-css-badges-page] {
  2294. & :is(tr, th, td) {
  2295. border-block-end: 2px solid silver;
  2296. }
  2297. & :is(span, a).badge {
  2298. display: flex;
  2299. align-content: center;
  2300. justify-content: center;
  2301. align-items: center;
  2302. justify-items: center;
  2303. flex-flow: column-reverse nowrap;
  2304. }
  2305. }
  2306. dialog {
  2307. inset: 50vh 50vw;
  2308. inset: 50dvh 50dvw;
  2309. transform: translate(-50%, -50%);
  2310. background: canvas;
  2311. color: canvastext;
  2312. border: 2px ridge #0096D6;
  2313. border-radius: 1em;
  2314. padding: 3rem;
  2315. width: max-content;
  2316. &.QR {
  2317. color-scheme: light;
  2318. }
  2319. & button.close {
  2320. position: absolute;
  2321. inset: 0.5rem 0.5rem auto auto;
  2322. margin: 0;
  2323. }
  2324. }
  2325. div.button-align:has(> button.close) {
  2326. width: 100%;
  2327. height: 0;
  2328. overflow: visible;
  2329. display: flex;
  2330. flex-flow: row-reverse nowrap;
  2331. margin: 0.125rem;
  2332. }
  2333. button.close {
  2334. cursor: pointer;
  2335. background: firebrick;
  2336. color: white;
  2337. border: 0.125rem ridge red;
  2338. border-radius: 1rem;
  2339. padding: 0;
  2340. width: 2em;
  2341. height: 2em;
  2342. z-index: 3;
  2343. &:is(:hover, :focus, :active) {
  2344. box-shadow: inset 0 0 0.125rem 0.125rem red;
  2345. background: firebrick;
  2346. color: white;
  2347. }
  2348. }
  2349. ::backdrop {
  2350. backdrop-filter: blur(5px) brightness(50%);
  2351. }
  2352. @-moz-document url-prefix() {
  2353. ::backdrop {
  2354. background: #00000088;
  2355. }
  2356. }
  2357. `);
  2358. }
  2359. //#endregion
  2360.  
  2361. //#region: non-SCC pages | start SCC
  2362. (() => {
  2363. debuglog(`multiple check - ${window.ImproveSCC}`);
  2364. if (window.ImproveSCC) return;
  2365. window.ImproveSCC = true;
  2366. if (hrefContains('midway-auth')) {
  2367. if (window.name == 'midwayAuth') {
  2368. debuglog('midwayAuthPage');
  2369. wait$('#auth-success-view').then(success => {
  2370. new MutationObserver(() => {
  2371. if (success?.style?.display == 'block') { window.close(); }
  2372. }).observe(success, { characterData: false, attributes: true, childList: false, subtree: false });
  2373. });
  2374. }
  2375. return;
  2376. }
  2377. if (hrefContains('siw-eu-dub.dub.') || hrefContains('siw-na-iad.iad')) {
  2378. debuglog('GCRSsearchPage');
  2379. const tId = window.location.hash.slice(1);
  2380. debuglog(tId);
  2381. return tId && wait$('[ng-model="vm.queryItems"]', { customProps: { input: tId } }).then(input => {
  2382. debuglog(input);
  2383. input.do('input');
  2384. wait$('[ng-click="vm.query()"]:not([disabled])').then(button => button.do('click'));
  2385. });
  2386. }
  2387. if (hrefContains('royalmail')) {
  2388. debuglog('RMpostcodePage');
  2389. const addr = decodeURI(window.location.hash.slice(1));
  2390. debuglog(addr);
  2391. return addr && wait$('#edit-rml-postcode-finder-postal-code').then(input => {
  2392. debuglog(input);
  2393. input.focus();
  2394. input.value = addr;
  2395. input.do('input');
  2396. });
  2397. }
  2398. if (GM.info.scriptHandler == 'Greasemonkey') {
  2399. alert('Improve SCC Search\n\nThis script will not run in Greasemonkey due to a limitation with how the extension handles iframes.\nPlease install with Tampermonkey.');
  2400. return;
  2401. }
  2402. addCSS();
  2403. startup();
  2404. })();
  2405. //#endregion