gmx - show new unread

Mark folders with new unread messages (gmx / web.de)

// ==UserScript==
// @name         gmx - show new unread
// @name:fr      gmx - marquer nouveaux messages
// @name:de      gmx - zeige neues ungelesenes
// @name:es      gmx - mostrar nuevos no leídos
// @namespace    https://github.com/Procyon-b
// @version      0.6.2
// @description  Mark folders with new unread messages (gmx / web.de)
// @description:fr Marque les dossiers contenant de nouveaux messages (gmx / web.de)
// @description:de Markieren Sie Ordner mit neuen ungelesenen Nachrichten (gmx / web.de)
// @description:es Marcar carpetas con nuevos mensajes no leídos (gmx / web.de)
// @author       Achernar

// @match        https://3c.gmx.net/mail/client/*
// @match        https://3c-bap.gmx.net/mail/client/*
// @include      https://3c-bs.gmx.tld/mail/client/*
// @match        https://3c.web.de/mail/client/*
// @match        https://3c-bap.web.de/mail/client/*

// @match        https://navigator.gmx.net/*
// @match        https://bap.navigator.gmx.net/*
// @include      https://navigator-bs.gmx.tld/*
// @match        https://navigator.web.de/*
// @match        https://bap.navigator.web.de/*

// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==

(function() {
"use strict";

var h=location.host.split('.');

window.addEventListener('blur', function(ev) {
  window.top.postMessage({blur:1},'*');
  });
window.addEventListener('focus', function(ev) {
  window.top.postMessage({focus:1},'*');
  });

let f=document.hasFocus();
window.top.postMessage({focus:f, NOblur:!f},'*');


// main frame (top)
if (location.host.startsWith('navigator') || location.host.startsWith('bap.navigator') ) {
  var TO, marked, TOb, focus=false, mailFrame, data=document.getElementById('user-data');
  try {
  if (data) data=JSON.parse(data.text);
  }catch(e){};


  window.addEventListener('message', function(ev) {
    if (typeof ev.data != 'object') return;

    if (ev.data.getUserId) {
      ev.source.window.postMessage({userId:data.hashedUasAccountId}, ev.origin);
      mailFrame={w:ev.source.window, origin:ev.origin};
      return;
      }

    if (ev.data.focus) {
      focus=true;
      if (TOb) {clearTimeout(TOb); TOb=null;}
      else if (marked) onF();
      }
    if (ev.data.blur) {
      focus=false;
      if (TO) {
        TOb=setTimeout(function(){
          TOb=null;
          onB();
          }, 1000);
        }
      }

    mailFrame && mailFrame.w.postMessage({focus:focus, blur:!focus}, mailFrame.origin);

    function onF() {
      onB();
      TO=setTimeout( function(){
        TO=null;
        marked=false;
        document.title=document.title.replace(/^\(.*?\) */,'');
        }, 3000);
      }
    function onB() {
      if (TO) {clearTimeout(TO); TO=null;}
      }

    if (!focus && ev.data.new) {
      marked=true;
      document.title='(*) '+document.title.replace(/^\(.*?\) */,'');
      }
    }, false);

  return;
  }


// folders frame
if (location.pathname.startsWith('/mail/client/') ) {
  if (window.parent === window) return;

  var e=document.querySelector('#navigation');
  if (!e) return;

  // call top to get userid
  window.top.postMessage({getUserId:true},'*');

  var focus=false, userId, folders={}, ignore=['vfol3','vfol2'];

  window.addEventListener('message', function(ev){
    if (typeof ev.data != 'object') return;

    if (ev.data.userId) {
      userId=ev.data.userId;
      let sFolders={};
      try{sFolders=GM_getValue(userId,{});}catch(e){}
      folders={userId:userId};

      // find all folders
      let a=document.querySelectorAll('#navigation NOul, #navigation li > .folder'),
        lvl=[], L, t, id, panel, New=false;
      for (let i=0,e; e=a[i]; i++) {
        L= parseInt( (L=/lvl(\d+)/.exec(e.parentNode.parentNode.classList)) && L[1] );
        lvl[L]=id=e.id;
        if (L==1) {
          panel= (panel=e.closest('.navigation')) && (panel=panel.querySelector(':scope > .panel-head'));
          if (panel && panel.title && panel.querySelector('.badge') ) panel=panel.title;
          else panel='';
          }

        let badge=e.querySelector('.badge');
        folders[id]={lvl:L, ur:badge && parseInt(badge.innerText)};
        if (ignore.includes(id)) folders[id].ignore=1;
        // the 3 default folders without badge
        if (!badge) folders[id].nobadge=1;

        let label=e.querySelector('.label');
        if (label) {
          folders[id].name=label.innerText.trim();
          // fix for closed folders
          if ( /\((\d+)\/\d+\)$/.exec(label.title) ) folders[id].ur=parseInt(RegExp.$1);
          }

        // is part of panel with badge
        if (panel) folders[id].panel=panel;

        // is subfolder
        if (t=lvl[L-1]) {
          folders[id].p=t;
          folders[t].sub=1;
          }

        if (sFolders[id]) {
          if (t=sFolders[id].mark) folders[id].mark=t;
          if (folders[id].ur > sFolders[id].ur) {
            folders[id].mark=1;
            New=true;
            }
          if (!folders[id].ur || !badge) delete folders[id].mark;
          }
        }
      try{GM_setValue(userId, folders);}catch(e){}
      buildCSS();
      if (!f && New) window.top.postMessage({new:1},'*');
      return;
      }

    if (ev.data.focus) focus=true;
    if (ev.data.blur) focus=false;
    }, false);

  function buildCSS(ret) {
    var s='/*userscript test*/', fol={}, pan={}, i;
    for (i in folders) {
      let f=folders[i];
      if (f.ignore) continue;
      if (f.mark || f.fmark) {
        s+='div.nav-item[id="'+i+'"] .badge{background-color: red !important; color: white !important;}';
        while (f.p) {
          if (!f.ignore) fol[f.p]=1;
          f=folders[f.p];
          }
        if (f.panel) {
          pan[f.panel]=1;
          }
        }
      }
    for (i in fol) { s+='div.nav-item[id="'+i+'"]:not(.open) .badge{background-color: red;}'; }
    for (i in pan) { s+='div.navigation > div.panel-head[title="'+i+'"] .badge{background-color: red; color: white;}'; }
    if (ret) return s;
    style.innerText=s;
    }

  var st={}, options={attributes: false, subtree: true, childList: true };

  var style=document.createElement('style');
  if (style.styleSheet) style.styleSheet.cssText = '';
  else style.appendChild(document.createTextNode(''));
  (document.head || document.documentElement).appendChild(style);
  buildCSS();

  const obs = new MutationObserver(function(mutL){
    let n, o, t, save=false, New=false;
    for (let mut of mutL) {
      if ( (t=mut.target) && (t.className=='badge') ) {
        if ( (n=mut.addedNodes[0]) && (n.nodeType==3) ) {
          var div=t.closest('div.nav-item.folder'), id=div && (id=div.id), q=parseInt(n.data);
          if (!id || !folders[id]) continue;
          if (folders[id].sub) {
            let cl=div.classList.contains('open') ? false:true;
            if (cl) {
              var tit= (tit=div.querySelector('.label')) && tit.title;
              let qt=-1;
              if ( /\((\d+)\/\d+\)$/.exec(tit) ) qt=parseInt(RegExp.$1);
              if (qt>=0) q=qt;
              }
            }
          if (folders[id].ur == q) continue;
          if (q <= folders[id].ur) delete folders[id].mark;
          else {
            folders[id].mark=1;
            New=true;
            }
          folders[id].ur=q;
          save=true;
          }
        }
      }

    if (save) {
      buildCSS();
      try{GM_setValue(userId, folders);}catch(e){}
      }
    if (!focus && New) window.top.postMessage({new:1},'*');
    });
  obs.observe(e, options);

  e.addEventListener('click', function(ev){
    if (ev.ctrlKey) {
      if (ev.target.classList.contains('folder-config') || ev.target.parentNode.classList.contains('folder-config') ) {
        let fol=ev.target.closest('div.nav-item.folder');
        if (!fol || fol.classList.contains('has-open-flyout') || (!fol.classList.contains('open') && fol.firstElementChild.classList.contains('toggle')) ) return;
        ev.stopPropagation();
        if (folders[fol.id].mark) {
          delete folders[fol.id].mark;
          buildCSS();
          try{GM_setValue(userId, folders);}catch(e){}
          }
        return false;
        }
      }
    },true);

  }

})();