Greasy Fork is available in English.

gmx - show new unread

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

  1. // ==UserScript==
  2. // @name gmx - show new unread
  3. // @name:fr gmx - marquer nouveaux messages
  4. // @name:de gmx - zeige neues ungelesenes
  5. // @name:es gmx - mostrar nuevos no leídos
  6. // @namespace https://github.com/Procyon-b
  7. // @version 0.6.2
  8. // @description Mark folders with new unread messages (gmx / web.de)
  9. // @description:fr Marque les dossiers contenant de nouveaux messages (gmx / web.de)
  10. // @description:de Markieren Sie Ordner mit neuen ungelesenen Nachrichten (gmx / web.de)
  11. // @description:es Marcar carpetas con nuevos mensajes no leídos (gmx / web.de)
  12. // @author Achernar
  13.  
  14. // @match https://3c.gmx.net/mail/client/*
  15. // @match https://3c-bap.gmx.net/mail/client/*
  16. // @include https://3c-bs.gmx.tld/mail/client/*
  17. // @match https://3c.web.de/mail/client/*
  18. // @match https://3c-bap.web.de/mail/client/*
  19.  
  20. // @match https://navigator.gmx.net/*
  21. // @match https://bap.navigator.gmx.net/*
  22. // @include https://navigator-bs.gmx.tld/*
  23. // @match https://navigator.web.de/*
  24. // @match https://bap.navigator.web.de/*
  25.  
  26. // @grant GM_setValue
  27. // @grant GM_getValue
  28. // ==/UserScript==
  29.  
  30. (function() {
  31. "use strict";
  32.  
  33. var h=location.host.split('.');
  34.  
  35. window.addEventListener('blur', function(ev) {
  36. window.top.postMessage({blur:1},'*');
  37. });
  38. window.addEventListener('focus', function(ev) {
  39. window.top.postMessage({focus:1},'*');
  40. });
  41.  
  42. let f=document.hasFocus();
  43. window.top.postMessage({focus:f, NOblur:!f},'*');
  44.  
  45.  
  46. // main frame (top)
  47. if (location.host.startsWith('navigator') || location.host.startsWith('bap.navigator') ) {
  48. var TO, marked, TOb, focus=false, mailFrame, data=document.getElementById('user-data');
  49. try {
  50. if (data) data=JSON.parse(data.text);
  51. }catch(e){};
  52.  
  53.  
  54. window.addEventListener('message', function(ev) {
  55. if (typeof ev.data != 'object') return;
  56.  
  57. if (ev.data.getUserId) {
  58. ev.source.window.postMessage({userId:data.hashedUasAccountId}, ev.origin);
  59. mailFrame={w:ev.source.window, origin:ev.origin};
  60. return;
  61. }
  62.  
  63. if (ev.data.focus) {
  64. focus=true;
  65. if (TOb) {clearTimeout(TOb); TOb=null;}
  66. else if (marked) onF();
  67. }
  68. if (ev.data.blur) {
  69. focus=false;
  70. if (TO) {
  71. TOb=setTimeout(function(){
  72. TOb=null;
  73. onB();
  74. }, 1000);
  75. }
  76. }
  77.  
  78. mailFrame && mailFrame.w.postMessage({focus:focus, blur:!focus}, mailFrame.origin);
  79.  
  80. function onF() {
  81. onB();
  82. TO=setTimeout( function(){
  83. TO=null;
  84. marked=false;
  85. document.title=document.title.replace(/^\(.*?\) */,'');
  86. }, 3000);
  87. }
  88. function onB() {
  89. if (TO) {clearTimeout(TO); TO=null;}
  90. }
  91.  
  92. if (!focus && ev.data.new) {
  93. marked=true;
  94. document.title='(*) '+document.title.replace(/^\(.*?\) */,'');
  95. }
  96. }, false);
  97.  
  98. return;
  99. }
  100.  
  101.  
  102. // folders frame
  103. if (location.pathname.startsWith('/mail/client/') ) {
  104. if (window.parent === window) return;
  105.  
  106. var e=document.querySelector('#navigation');
  107. if (!e) return;
  108.  
  109. // call top to get userid
  110. window.top.postMessage({getUserId:true},'*');
  111.  
  112. var focus=false, userId, folders={}, ignore=['vfol3','vfol2'];
  113.  
  114. window.addEventListener('message', function(ev){
  115. if (typeof ev.data != 'object') return;
  116.  
  117. if (ev.data.userId) {
  118. userId=ev.data.userId;
  119. let sFolders={};
  120. try{sFolders=GM_getValue(userId,{});}catch(e){}
  121. folders={userId:userId};
  122.  
  123. // find all folders
  124. let a=document.querySelectorAll('#navigation NOul, #navigation li > .folder'),
  125. lvl=[], L, t, id, panel, New=false;
  126. for (let i=0,e; e=a[i]; i++) {
  127. L= parseInt( (L=/lvl(\d+)/.exec(e.parentNode.parentNode.classList)) && L[1] );
  128. lvl[L]=id=e.id;
  129. if (L==1) {
  130. panel= (panel=e.closest('.navigation')) && (panel=panel.querySelector(':scope > .panel-head'));
  131. if (panel && panel.title && panel.querySelector('.badge') ) panel=panel.title;
  132. else panel='';
  133. }
  134.  
  135. let badge=e.querySelector('.badge');
  136. folders[id]={lvl:L, ur:badge && parseInt(badge.innerText)};
  137. if (ignore.includes(id)) folders[id].ignore=1;
  138. // the 3 default folders without badge
  139. if (!badge) folders[id].nobadge=1;
  140.  
  141. let label=e.querySelector('.label');
  142. if (label) {
  143. folders[id].name=label.innerText.trim();
  144. // fix for closed folders
  145. if ( /\((\d+)\/\d+\)$/.exec(label.title) ) folders[id].ur=parseInt(RegExp.$1);
  146. }
  147.  
  148. // is part of panel with badge
  149. if (panel) folders[id].panel=panel;
  150.  
  151. // is subfolder
  152. if (t=lvl[L-1]) {
  153. folders[id].p=t;
  154. folders[t].sub=1;
  155. }
  156.  
  157. if (sFolders[id]) {
  158. if (t=sFolders[id].mark) folders[id].mark=t;
  159. if (folders[id].ur > sFolders[id].ur) {
  160. folders[id].mark=1;
  161. New=true;
  162. }
  163. if (!folders[id].ur || !badge) delete folders[id].mark;
  164. }
  165. }
  166. try{GM_setValue(userId, folders);}catch(e){}
  167. buildCSS();
  168. if (!f && New) window.top.postMessage({new:1},'*');
  169. return;
  170. }
  171.  
  172. if (ev.data.focus) focus=true;
  173. if (ev.data.blur) focus=false;
  174. }, false);
  175.  
  176. function buildCSS(ret) {
  177. var s='/*userscript test*/', fol={}, pan={}, i;
  178. for (i in folders) {
  179. let f=folders[i];
  180. if (f.ignore) continue;
  181. if (f.mark || f.fmark) {
  182. s+='div.nav-item[id="'+i+'"] .badge{background-color: red !important; color: white !important;}';
  183. while (f.p) {
  184. if (!f.ignore) fol[f.p]=1;
  185. f=folders[f.p];
  186. }
  187. if (f.panel) {
  188. pan[f.panel]=1;
  189. }
  190. }
  191. }
  192. for (i in fol) { s+='div.nav-item[id="'+i+'"]:not(.open) .badge{background-color: red;}'; }
  193. for (i in pan) { s+='div.navigation > div.panel-head[title="'+i+'"] .badge{background-color: red; color: white;}'; }
  194. if (ret) return s;
  195. style.innerText=s;
  196. }
  197.  
  198. var st={}, options={attributes: false, subtree: true, childList: true };
  199.  
  200. var style=document.createElement('style');
  201. if (style.styleSheet) style.styleSheet.cssText = '';
  202. else style.appendChild(document.createTextNode(''));
  203. (document.head || document.documentElement).appendChild(style);
  204. buildCSS();
  205.  
  206. const obs = new MutationObserver(function(mutL){
  207. let n, o, t, save=false, New=false;
  208. for (let mut of mutL) {
  209. if ( (t=mut.target) && (t.className=='badge') ) {
  210. if ( (n=mut.addedNodes[0]) && (n.nodeType==3) ) {
  211. var div=t.closest('div.nav-item.folder'), id=div && (id=div.id), q=parseInt(n.data);
  212. if (!id || !folders[id]) continue;
  213. if (folders[id].sub) {
  214. let cl=div.classList.contains('open') ? false:true;
  215. if (cl) {
  216. var tit= (tit=div.querySelector('.label')) && tit.title;
  217. let qt=-1;
  218. if ( /\((\d+)\/\d+\)$/.exec(tit) ) qt=parseInt(RegExp.$1);
  219. if (qt>=0) q=qt;
  220. }
  221. }
  222. if (folders[id].ur == q) continue;
  223. if (q <= folders[id].ur) delete folders[id].mark;
  224. else {
  225. folders[id].mark=1;
  226. New=true;
  227. }
  228. folders[id].ur=q;
  229. save=true;
  230. }
  231. }
  232. }
  233.  
  234. if (save) {
  235. buildCSS();
  236. try{GM_setValue(userId, folders);}catch(e){}
  237. }
  238. if (!focus && New) window.top.postMessage({new:1},'*');
  239. });
  240. obs.observe(e, options);
  241.  
  242. e.addEventListener('click', function(ev){
  243. if (ev.ctrlKey) {
  244. if (ev.target.classList.contains('folder-config') || ev.target.parentNode.classList.contains('folder-config') ) {
  245. let fol=ev.target.closest('div.nav-item.folder');
  246. if (!fol || fol.classList.contains('has-open-flyout') || (!fol.classList.contains('open') && fol.firstElementChild.classList.contains('toggle')) ) return;
  247. ev.stopPropagation();
  248. if (folders[fol.id].mark) {
  249. delete folders[fol.id].mark;
  250. buildCSS();
  251. try{GM_setValue(userId, folders);}catch(e){}
  252. }
  253. return false;
  254. }
  255. }
  256. },true);
  257.  
  258. }
  259.  
  260. })();