EnstylerJS

MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect

当前为 2016-12-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name EnstylerJS
  3. // @namespace Enstyler
  4. // @description MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect
  5. // @include https://www.preisjaeger.at/*
  6. // @include https://www.mydealz.de/*
  7. // @include https://userstyles.org/styles/128262/*
  8. // @include https://www.amazon.*/gp/aw/*
  9. // @version 2.12.052
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @require http://code.jquery.com/jquery-3.1.1.min.js
  13. // @require http://cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.min.js
  14. // @require http://openuserjs.org/src/libs/sizzle/GM_config.js
  15. // ==/UserScript==
  16.  
  17. // ========== INIT EnstylerJS =====================================
  18. // init Enstyler environment
  19. var enUserLogin = false;
  20. var enUserName = '';
  21. var enSection = '';
  22. var EnstylerStartTime=Date.now();
  23.  
  24. // Parse location for later use
  25. var enLocParser= location;
  26.  
  27. var DEBUG=false;
  28.  
  29. // add actions to tread overview @ some places ==================================
  30.  
  31. // code used for MyDealz Dealz actions, thanks to mydealz :-)
  32. var myOuterHtml = [ '<span class="js-options bg--em bRad--a space--h-3 space--v-3 space--mt-3 text--b">', '</span>'];
  33. var enDealAction = [ '<a title="Sag was dazu" class="link ico ico--pos-l ico--type-comment-blue space--mr-3"'+ // comment 0+1+3
  34. 'href="<ENSTYLER-HREF-HERE>#comment-form" data-handler="track" data-track="{&quot;action&quot;:&quot;scroll_to_comment_add_form&quot;,&quot;label&quot;:&quot;engagement&quot;}">',
  35. 'Sag was dazu', '', '</a>',
  36. '<a title="Von Liste entfernen" class="link text--color-blue ico ico--type-bookmark-blue ico--pos-l space--mr-3"' + //un-bookmark 4+5+7
  37. 'data-handler="track replace" data-replace="{&quot;endpoint&quot;:&quot;https:\/\/www.mydealz.de\/threads\/<ENSTYLER-THREADID-HERE>/remove&quot;,&quot;method&quot;:&quot;post&quot;}" data-track="{&quot;action&quot;:&quot;save_thread&quot;,&quot;label&quot;:&quot;engagement&quot;}">',
  38. 'Von Liste entfernen', '', '</a>',
  39. '<a title="Bearbeiten" class="link text--color-blue ico ico--type-pencil-blue ico--pos-l space--mr-3"'+ // edit 8+9+11
  40. 'href="<ENSTYLER-HREF-HERE>/edit" data-handler="track" data-track="{&quot;action&quot;:&quot;goto_thread_edit_form&quot;,&quot;beacon&quot;:true}">',
  41. 'Bearbeiten', '', '</a>',
  42. '<a title="Abgelaufen?" class="thread-expire link ico ico--type-clock-blue ico--pos-l space--mr-3"'+ // expiried not working :-(
  43. 'href="<ENSTYLER-HREF-HERE>/expire/report" rel="nofollow" data-handler="track replace" data-track="{&quot;action&quot;:&quot;report_expired_thread&quot;,&quot;label&quot;:&quot;contribution&quot;}" data-replace="{&quot;endpoint&quot;:&quot;https:\/\/www.mydealz.de\/<ENSTYLER-HREF-HERE>/expire\/report&quot;}">',
  44. 'Abgelaufen?', '', '</a>',
  45. ];
  46. var PATTERN = [ /<ENSTYLER-HREF-HERE>/g, // pattern to replace by deal link ...
  47. /<ENSTYLER-THREADID-HERE>/g // pattern to replace ID
  48. ];
  49. var enDealAdd='';
  50.  
  51. enDealMarker='thread_'
  52.  
  53. function EnstylerDealActions(){
  54. // use parsed location
  55. var pathname = enLocParser.pathname;
  56. var myText=0;
  57. // no username ??
  58. if (enUserName != "") {
  59. pathname = pathname.replace(enUserName + '/',''); // remove username if path is longer
  60. }
  61. // display short/no text?
  62. if ($('.ico--type-grid-subNavActive').length) { myText=1; }
  63. // default for all Dealz: comment
  64. enDealAdd = enDealAction[0]+ enDealAction[1+myText] + enDealAction[3];
  65.  
  66. // Action for special locations only ===========
  67. // saved Dealz panel
  68. if (pathname.endsWith('profile/saved-deals') ){
  69. // add for user saved-dealz: un-bookmark
  70. enDealAdd += enDealAction[4]+ enDealAction[5+myText]; + enDealAction[7]
  71. }
  72. if (pathname.endsWith('profile/diskussion') || pathname.endsWith(enUserName)){
  73. // add user dealz and discussions: comment edit
  74. enDealAdd += enDealAction[8]+ enDealAction[9+myText] + enDealAction[11];
  75. }
  76. EnstylerDealActionsDo()
  77. }
  78.  
  79.  
  80. function EnstylerDealActionsDo() {
  81. // if logged in and enabled ...
  82. if (enUserLogin && GM_config.get('enConfMoreDeal')) {
  83. // every thread on thread page ...
  84. $('article').each(function () {
  85. // get ThreadID, Link to Deal and DealID
  86. var myThread = $(this).attr('id');
  87. if (!myThread.startsWith(enDealMarker)) {return;}
  88. var myDealHref = $('#' + myThread + ' .thread-title a').attr('href');
  89. var myDealID = myThread.replace(enDealMarker,'');
  90. if (myThread.startsWith('To be')) {return;}
  91. // compose final HTML
  92. var newHtml= myOuterHtml[0] + enDealAdd + myOuterHtml[1];
  93. newHtml = newHtml.replace(PATTERN[0], myDealHref);
  94. newHtml = newHtml.replace(PATTERN[1], myDealID);
  95. // append HTML to Deal
  96. $('#' + myThread +' .thread-infoRow').each(function () {
  97. $(this).append(newHtml);
  98. $(this).removeClass('thread-infoRow');
  99. });
  100. });
  101.  
  102. // actions for somewhere ===========
  103. // remove unwanted HTML from deal description
  104. $('.thread--type-detail .userHtml').each(function () {
  105. // userhtml code from mydalz, need to find jafascript to save automatically :-(
  106. var myUserhtml = ['<div class="userHtml overflow--wrap-break space--t-3" data-handler="lightbox-xhr" data-lightbox-xhr="{&quot;name&quot;:&quot;threads&quot;}">',
  107. '</div>', // sourround deal description
  108. ];
  109. // get inner html
  110. var myHtml = $(this).html();
  111.  
  112. // remove unwanted Stuff: combined <div><br><br> stuff, created by cut'npaste html
  113. // not elegant, but works ...
  114. var newHtml = myHtml.replace(/<div>|<div><br>|<\/br>|<\/div>/gi,'');
  115. newHtml = myHtml.replace(/<p>|<p><br>/gi,'<br><br>');
  116. newHtml = newHtml.replace(/<br><br><br>|<br><br><br><br>|<br><br><br><br><br>/gi,'<br><br>');
  117. // replace original with modifyed html
  118. $(this).replaceWith(myUserhtml[0] + newHtml + myUserhtml[1]);
  119. });
  120.  
  121. } // END enabled
  122. }
  123.  
  124.  
  125. // show popup user info while click on avatar ... ======================
  126. function EnstylerAvatarPopup(){
  127. if (enUserLogin && GM_config.get('enConfUser')) {
  128. // remove second image from cardview
  129. addStyleString('.thread-footer-cell a img.avatar.vAlign--all-m.space--mr-1.thread-avatar {display: none;}');
  130. EnstylerAvatarPopupDo();
  131. }
  132. }
  133.  
  134. // code used for MyDealz avatar popup, thanks to mydealz :-)
  135. var enPopupUser = ['<button data-handler="track popover" data-track="{&quot;action&quot;:&quot;show_short_user_profile&quot;,&quot;label&quot;:&quot;engagement&quot;}" data-popover="{&quot;endpoint&quot;:&quot;',
  136. '/short&quot;,&quot;target&quot;:&quot;#template-popoverLoader&quot;,&quot;layout&quot;:[{&quot;preset&quot;:&quot;e&quot;,&quot;y&quot;:&quot;50%&quot;,&quot;left&quot;:{&quot;offset&quot;:0},&quot;width&quot;:300,&quot;maxWidth&quot;:&quot;100%&quot;}]}">',
  137. '</button>',
  138. ];
  139.  
  140.  
  141. function EnstylerAvatarPopupDo() {
  142. // login needed ... (Error in Popup without login ...)
  143. // replace every avatar link without popup
  144. if (enUserLogin && GM_config.get('enConfUser')) {
  145. $('.thread-footer-cell a.user.linkPlain, .user.linkPlain.thread-user').each(function () {
  146. // get inner html and link to user profile
  147. var myHtml = $(this).html();
  148. var mysrc = $(this).attr('href');
  149. // seperate user name from image and add class user
  150. var myAvatar1 = myHtml.replace(/<span.*/,'');
  151. var myAvatar2 = myHtml.replace(/.*<span class=".* space--mr-1">/,'<span class=" space--mr-1 user link-plain">');
  152.  
  153. // show small / medium sized Avatar
  154. myAvatar1 = myAvatar1.replace('avatar--type-s','avatar--type-m'); //in Dealz
  155. if (GM_config.get('enConfAvatar')) { myAvatar1 = myAvatar1.replace('thread-avatar','avatar--type-m'); }
  156. // compose popup
  157. var myPopup = enPopupUser[0] + mysrc + enPopupUser[1] + myAvatar1 + enPopupUser[2] + '<a href="' + mysrc + '">'+ myAvatar2 + '</a>';
  158. $(this).replaceWith(myPopup);
  159. });
  160. }
  161. }
  162.  
  163.  
  164. // create select page or scrollwheel for page navigation =============
  165. var EnstylerPageEnum='enPageEnum';
  166. var selectList = document.createElement("select");
  167. selectList.id = EnstylerPageEnum;
  168. selectList.setAttribute('class', EnstylerPageEnum);
  169. selectList.onchange = EnstylerPageAction;
  170.  
  171. var EnstylerPageEnumDown='enPageEnumDown';
  172. var selectListDown = document.createElement("select");
  173. selectListDown.id = EnstylerPageEnumDown;
  174. selectListDown.onchange = EnstylerPageAction;
  175.  
  176. function EnstylerPagePickerCreate() {
  177. // revome existing picker
  178. EnstylerPagePickerRemove();
  179. // if enabled
  180. if (GM_config.get('enConfPagePicker')) {
  181. // init values and clear select list
  182. var page=1;
  183. var min=1;
  184. var max=1;
  185. $(selectList).empty();
  186. // get page and max from pagenav
  187. if ( $('.text--color-charcoalTint').length ) {
  188. // remove linebreaks
  189. var pageHtml = $('.text--color-charcoalTint').html().replace(/\r?\n|\r/g);
  190. //locate actual page and last page
  191. page = pageHtml.replace( /.*>Seite /i ,''); page = page.replace( /<.*/i , '');
  192. max = pageHtml.replace( /.*page=/ ,''); max = max.replace( /[^0-9].*/i , '');
  193. if (page == '') {page=1;}
  194. if (max == '') {max=page;}
  195. }
  196.  
  197. // create page select element
  198. var x; var last; var option;
  199.  
  200. for (x = min; x <= max; ) {
  201. option = document.createElement("option");
  202. option.text = x;
  203. selectList.add(option); //selectListDown.add(option);
  204. last = x;
  205. // non linear increment
  206. var diff = Math.abs(x-page);
  207.  
  208. if ( x < 10 || diff < 5) { x++; }
  209. else if ( x < 1000 && diff > 600) { x += Math.floor(diff/100); }
  210. else { x += Math.floor(diff/2); }
  211. }
  212. // add last page
  213. if (page > max) { max=page;}
  214. if (last < max ) {
  215. option = document.createElement("option");
  216. option.text = max;
  217. selectList.add(option); //selectListDown.add(option);
  218. }
  219. // set default value
  220. selectList.value = page; //selectListDown.value = page;
  221.  
  222. // Deal Page
  223. if ($('.voteBar').length) {
  224. selectList.setAttribute('class', EnstylerPageEnum +' subNavMenu-link subNavMenu-btn voteBar--sticky-off--hide');
  225. $('.voteBar--sticky-off--hide:last').before(selectList);
  226. } else {
  227. // overwiew page
  228. if (GM_config.get('enConfBtn')) {
  229. // Place Picker in subnav
  230. selectList.setAttribute('class', EnstylerPageEnum+' box--all-i subNavMenu-link subNavMenu-btn');
  231. $('.subNav-label:last').before(selectList);
  232. } else {
  233. //place picker in MainNav
  234. selectList.setAttribute('class', EnstylerPageEnum+' js-navDropDown-messages vAlign--all-m');
  235. if ($('.test-loginButton').length) {
  236. $('.test-loginButton').before(selectList);
  237. } else {
  238. $('.js-navDropDown-messages').before(selectList);
  239. }
  240. }
  241. }
  242. }
  243. }
  244.  
  245. function EnstylerPagePickerCreateDo() {
  246. // get page and max from pagenav
  247. if ( $('.js-sticky .text--color-charcoalTint').length ) {
  248. // remove linebreaks
  249. var pageHtml = $('.js-sticky .text--color-charcoalTint').html().replace(/\r?\n|\r/g);
  250. //locate actual page and last page
  251. var page = pageHtml.replace( /.*>Seite /i ,''); page = page.replace( /<.*/i , '');
  252. if (page == '') {page=1;}
  253. // set default value
  254. selectList.value = page; //selectListDown.value = page;
  255. }
  256. }
  257.  
  258. // goto selected Page
  259. function EnstylerPageAction() {
  260. var enPage = 'page=' + $(this).val();
  261.  
  262. // remove page= and everthing behind
  263. var enUrl = enLocParser.toString().replace( /page=.*|#.*/ ,'');
  264. // add new page parameter
  265. if ( enUrl.endsWith('?') || enUrl.endsWith('&')) {
  266. enUrl += enPage;
  267. } else {
  268. enUrl += '?'+enPage;
  269. }
  270. // add #thread-comments for deal
  271. if (enSection == '/deals/') { enUrl += '#thread-comments';}
  272. window.location = enUrl;
  273. }
  274.  
  275. function EnstylerPagePickerRemove() {
  276. // Removes pagepicker from the document
  277. $('.'+ EnstylerPageEnum).remove();
  278. }
  279.  
  280.  
  281.  
  282. // blacklist do not show dealz containing blacklistet words ==========================
  283. // search in kategorie, dealtitle, and username
  284. var enClassHidden = 'enClassHidden';
  285. var enClassBlackDone = 'enClassBlackDone';
  286. var enBlacklisted=0;
  287.  
  288. var unwantedRegex = [ /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\ ]/g, // in White/Backlist
  289. /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\,]/g // in Dealtext
  290. ];
  291. var enBlack;
  292. var enWhite;
  293. var enBlackTemp;
  294. function EnstylerBlacklist() {
  295. // if logged in and user is not in whitelist
  296. if (enUserLogin && ! GM_config.get('enConfWhitelist').includes(enUserName)) {
  297. // add actual user to whitelist
  298. GM_config.set('enConfWhitelist', '@'+enUserName +',' + GM_config.get('enConfWhitelist'));
  299. //GM_config.setValue('enConfWhitelist', GM_config.get('enConfWhitelist'));
  300. }
  301. // convert Black/Whitelist to RegEx
  302. var myTemp=GM_config.get('enConfBlacklist');
  303. myTemp = myTemp.replace(unwantedRegex[0], '');
  304. myTemp = myTemp.replace(/^,|,$/g,'');
  305. enBlack = myTemp.replace(/(.),(.)/g,'$1|$2');
  306. myTemp=GM_config.get('enConfWhitelist');
  307. myTemp = myTemp.replace(/^,|,$/g,'');
  308. enWhite = myTemp.replace(/(.),(.)/g,'$1|$2');
  309. enBlackTemp= GM_config.get('enConfHideColder');
  310. EnstylerBlacklistDo();
  311. }
  312.  
  313. function EnstylerBlacklistDo() {
  314. if (!GM_config.get('enConfBlackEnable')) { return;}
  315.  
  316. // process every article
  317. $('article').each(function () {
  318. if ($(this).hasClass(enClassBlackDone)) { return;}
  319. var myThread = '#'+$(this).attr('id');
  320. // return if already done or return no deal
  321. if (!myThread.startsWith('#'+enDealMarker)) { return;}
  322. // mark as already seen
  323. $(this).addClass(enClassBlackDone);
  324. // get title, categorie, user
  325. var myDealText = $(myThread + ' .thread-category').text();
  326. myDealText += ' ' +$(myThread + ' .thread-title a').text();
  327. myDealText += ' @' +$(myThread + ' .user').text();
  328. myDealText = myDealText.replace(unwantedRegex[1] ,' ');
  329. // Whitelist first
  330. // whitelist Regex, exit if found
  331. if (enWhite !='' && myDealText.match(new RegExp(enWhite,'i'))) {
  332. return;
  333. }
  334.  
  335. //get temp
  336. var myVoteTemp = $(myThread + ' .vote-temp').text();
  337.  
  338. // blacklist vote temp
  339. if (parseInt(myVoteTemp) <= enBlackTemp) {
  340. $(this).addClass(enClassHidden);
  341. enBlacklisted++;
  342. EnstylerLastSeenSkip(myThread)
  343. return;
  344. }
  345. // blacklist last
  346. // blacklist Regex, rxit if found
  347. if (enBlack !='' && myDealText.match(new RegExp(enBlack,'i'))) {
  348. $(this).addClass(enClassHidden);
  349. enBlacklisted++;
  350. EnstylerLastSeenSkip(myThread)
  351. return;
  352. }
  353. }); // END Article
  354.  
  355. // set label for unBlacklist button
  356. EnstylerBlacklistShow()
  357. }
  358.  
  359. // blacklist support functions ....
  360. var enUnblackText = 'unBlacklist <NUM-BLACK> Dealz';
  361. function EnstylerBlacklistShow() {
  362. enJSfieldDefs.enConfUnblacklist.label=enUnblackText.replace('<NUM-BLACK>',enBlacklisted)
  363. }
  364.  
  365. function EnstylerBlacklistRemove() {
  366. enBlacklisted=0;
  367. EnstylerBlacklistShow()
  368. $('.'+enClassHidden).removeClass(enClassHidden);
  369. $('.'+enClassBlackDone).removeClass(enClassBlackDone);
  370. }
  371.  
  372. function EnstylerBlacklistUnhide() {
  373. enBlacklisted=0;
  374. EnstylerBlacklistShow()
  375. $('.'+enClassHidden).removeClass(enClassHidden);
  376. }
  377.  
  378.  
  379. // Main Nav will stay on TOP of the screen =========================
  380. function EnstylerFixedNav() {
  381. var myFixedCssMain='.nav { display: block; position: fixed; width: 100%; z-index: 120;} .subNav, .userProfile, .tabbedInterface {margin-top: 4.4em;}';
  382. var myFixedCssSub ='.subNav {margin-top: 0 !important;} .nav-subheadline {margin-top: 4.4em;}';
  383. if (GM_config.get('enConfNavFixed')) {
  384. // everywhere but in Deal detail, I like it like it is ...
  385. if (enSection != '/deals/' && enSection != '/gutscheine/' ){
  386. // delete header element with active stuff, but keep inside HTML
  387. var mySavedHtml = $('header').html();
  388. $('header').replaceWith(mySavedHtml);
  389. // CSS for everywhere
  390. addStyleString(myFixedCssMain);
  391. if (! enSection.match(enMainSectionMatch)) {
  392. // additional CSS for categories
  393. addStyleString(myFixedCssSub);
  394. }
  395. }
  396. }
  397. }
  398.  
  399. // the return of "gestern xx:xx Uhr" ==============
  400. var enNow;
  401. var DealDate;
  402. var TodayStart;
  403. var ShowTime;
  404. var EnstylerTimeSeen='enTimeSeen';
  405.  
  406. function EnstylerDealTime() {
  407. enNow = new Date();
  408. enNow.setTime(EnstylerStartTime)
  409. DealDate=new Date();
  410. TodayStart = new Date(enNow.getFullYear(), enNow.getMonth(), enNow.getDate());
  411. ShowTime= GM_config.get('enConfDealMinTime')*3600*1000;
  412. EnstylerDealTimeDo();
  413. }
  414.  
  415. function EnstylerDealTimeDo() {
  416. if (GM_config.get('enConfDealTime')) {
  417. enNow.setTime(Date.now())
  418. // process every article
  419. $('time').each(function () {
  420. // get Deal time Diff, return if no diff or already seen
  421. var myTime= $(this).text();
  422. // next article
  423. if ( $(this).hasClass(EnstylerTimeSeen) || !myTime.startsWith('vor ')) { return;}
  424. // get Deal Time difference
  425. var h = myTime.replace(/.* ([0-9].*) h.*/, '$1'); if (h==myTime) h=0; else h=parseInt(h);
  426. var m = myTime.replace(/.* ([0-9].*) m.*/, '$1'); if (m==myTime) m=0; else m=parseInt(m);
  427. var s = myTime.replace(/.* ([0-9].*) s.*/, '$1'); if (s==myTime) s=0; else s=parseInt(s);
  428.  
  429. // compose real deal time
  430. var myDealDiff = ((h*60+m)*60+s)*1000; // Offset deal
  431. DealDate.setTime( enNow.getTime() - myDealDiff );
  432.  
  433. // last midnigth
  434. if (DealDate < TodayStart) {
  435. $(this).text('gestern '+ DealDate.toString().slice(16, 21) +' Uhr');
  436. // more than 6 hours ago
  437. } else if (myDealDiff > ShowTime){
  438. $(this).text(myTime + ' (heute '+ DealDate.toString().slice(16, 21) +' Uhr)');
  439. } else { return; }
  440. $(this).addClass(EnstylerTimeSeen);
  441. });
  442. }
  443. }
  444.  
  445.  
  446. // mark last seen Deal in Highligth, Hot and New ============================
  447. var enSec='off';
  448. var enSeenArticle='';
  449.  
  450. // GM variables used here
  451. // store newest loaded deal
  452. // 'enNewestDeal...new'
  453. // 'enNewestDeal...hot'
  454. // 'enNewestDeal...'
  455. // international support added
  456. var enNewestBase='enNewest'+enLocParser.hostname.replace('www','');
  457. function EnstylerLastSeen(){
  458.  
  459. // only in main categories
  460. if(enSection.match(enMainSectionMatch)) {
  461. // get section and save
  462. enSec= enNewestBase + enSection.replace(/\//, '');
  463. GM_setValue(enNewestBase+'LastSec', enSec)
  464. // mark last seen article
  465. enSeenArticle=GM_getValue(enSec);
  466. EnstylerLastSeenDo();
  467. // save actual last seen
  468. $('article').each(function () {
  469. // get ThreadID an return if not enDealMarker
  470. var myThread = $(this).attr('id');
  471. if (!myThread.startsWith(enDealMarker)) {return;}
  472.  
  473. // when in base of section ...
  474. if(enLocParser.search == '') {
  475. //store actual seen
  476. GM_setValue(enSec, myThread);
  477. //store last seen
  478. GM_setValue(enSec+'Last', enSeenArticle);
  479. }
  480. // exit loop
  481. return false;
  482. });
  483. } else {
  484. // if we are not on base of ancategorie restore last value
  485. enSec=GM_getValue(enNewestBase+'LastSec');
  486. GM_setValue(enSec, GM_getValue(enSec+'Last'));
  487. }
  488. }
  489.  
  490. function EnstylerLastSeenDo(){
  491. // only in main categories
  492. if(enSec != 'off') {
  493. // mark last seen article
  494. if ( typeof enSeenArticle != 'undefined') {
  495. //store last marked
  496. GM_setValue(enSec+'Last', enSeenArticle);
  497.  
  498. $('#'+enSeenArticle).addClass('enClassMarkArticle');
  499. $('#'+enSeenArticle).prev().addClass('comments-marker');
  500. } else {
  501. // first time
  502. GM_setValue(enSec, enDealMarker+'1');
  503. }
  504.  
  505. }
  506. }
  507.  
  508. // article is not availible i.e. blacklisted
  509. function EnstylerLastSeenSkip(DealID) {
  510. // if article last seen one, skip to next
  511. if (DealID == '#'+enSeenArticle) {
  512. // magic, get ID of next article
  513. enSeenArticle=$(DealID).next().attr('id');
  514. if (DEBUG) console.error('Skip Deal: '+DealID.substr(1)+' -> '+enSeenArticle)
  515. EnstylerLastSeenDo();
  516. }
  517. }
  518.  
  519.  
  520.  
  521. // AMAZON mobile redirect ==================================
  522. // workaround to not intercept myDealz redirects with GM_xmlhttp
  523. // stolen from amazon redirect mobile: https://greasyfork.org/de/scripts/19700
  524. function enAmazonMobileRedirect() {
  525. var enMyLocation=enLocParser.toString();
  526. // do we run on amazon?
  527. if (enMyLocation.startsWith("https://www.amazon")) {
  528. // redirect enabled?
  529. if (GM_config.get('enConfAmazonRedirect')) {
  530. // do it
  531. if (enMyLocation.includes("/gp/aw/d/")) { window.location.assign(enMyLocation.replace("/gp/aw/d/", "/dp/")) }
  532. else { window.location.assign(enMyLocation.replace("/gp/aw/ol/", "/gp/offer-listing/")); }
  533. }
  534. // Amazon but no redirect enabled
  535. return false;
  536. }
  537. return true;
  538. }
  539.  
  540. // create button for display Config ======================
  541. var EnstylerButton = 'EnstylerButton';
  542.  
  543. //var input = document.createElement('a');
  544. // input.setAttribute('href', 'showEnstylerConfig()');
  545. // input.setAttribute('id', EnstylerButton);
  546. var input = document.createElement('input');
  547. input.type = 'button';
  548. input.setAttribute('id', EnstylerButton);
  549. input.onclick = showEnstylerConfig;
  550.  
  551. function EnstylerButtonCreate() {
  552. // add Enstyler Button to ...
  553. var myElement;
  554. var myMain=false;
  555. input.value = 'Enstyler';
  556. EnstylerButtonRemove();
  557. // MainNav or Deal
  558. if (GM_config.get('enConfBtn') || enSection.match(/deals\/|gutscheine\//))
  559. { myMain=true; }
  560. // only if space left
  561. if ($(window).width() < GM_config.get('enConfBtnMinWidth'))
  562. { myMain=false; }
  563. if (myMain) {
  564. // add button to MainNav
  565. var Elements = document.getElementsByClassName("navMenu-trigger");
  566. myElement=Elements[0];
  567. input.setAttribute('class', 'vAlign--all-m nav-link-text');
  568. input.setAttribute('style', '');
  569. } else {
  570. // add button to SubNav
  571. myElement = document.getElementById('tour-filter');
  572. input.setAttribute('class', 'box--all-i subNavMenu-link');
  573. input.setAttribute('style', 'font-size: 1.28571em; font-weight: 700; top: 3px; left: -0.7em');
  574. }
  575. // only if myElement exist
  576. if (myElement !== null) {
  577. myElement.appendChild(input);
  578. //insertAfter(input, myElement);
  579. }
  580. }
  581.  
  582. // needed for Enstyler Homepage
  583. function EnstylerHomeButton() {
  584. // add Enstyler Button to ...
  585. input.value = 'Options';
  586. var myElement = document.getElementById('style-settings');
  587. input.setAttribute('style', 'font-size: 1.28571em; padding: 0.8em;');
  588. // only if myElement exist
  589. if (myElement !== null) {
  590. //myElement.appendChild(input);
  591. insertAfter(input, myElement);
  592. }
  593. }
  594.  
  595. function EnstylerButtonRemove() {
  596. // Removes button from the document
  597. $('#'+ EnstylerButton).remove
  598. }
  599.  
  600.  
  601. // ============= GM_config functions =======================================
  602. var enJSAutoUpdate=GM_info.scriptWillUpdate;
  603. var enUpdateWindow;
  604.  
  605. // define pattern for main sections here, incl. international support
  606. var enMainSectionMatch=/^\/$|^\/hot$|^\/new$|^\/settings$|^\/discussed$|^\/hei%C3%9F$|^\/diskutiert$/
  607. var enInternationalSite='';
  608. if(!enLocParser.hostname.match(/mydealz\.de/i)) {
  609. enInternationalSite=enLocParser.hostname.replace('www','');
  610. if(DEBUG) console.error('International Site detected: ' +enInternationalSite);
  611. }
  612.  
  613. // define EnstylerJS GM_config elements
  614. var enJSfieldDefs = {
  615. // Part one: load external content --------
  616. 'enstyler': {
  617. 'section': ['additonal features for Enstyler', ''],
  618. 'label': 'Update CSS...', // Appears on the button
  619. 'type': 'button', // Makes this setting a button input
  620. 'click': function() { // Function to call when button is clicked
  621. enUpdateWindow=window.open('https://userstyles.org/styles/128262#style-info',
  622. 'UserCSS', 'width=600,height=950,left=0,top=0');
  623. }
  624. },
  625. 'enstylerJS': {
  626. 'label': 'Update UserScript...', // Appears on the button
  627. 'type': 'button', // Makes this setting a button input
  628. 'click': function() { // Function to call when button is clicked
  629. enUpdateWindow=window.open(!DEBUG ? 'https://greasyfork.org/de/scripts/24243' :
  630. ' https://greasyfork.org/scripts/24244-enstylerjs-develop/code/EnstylerJS Develop.user.js',
  631. 'UserScript', 'width=110,height=110,left=0,top=0');
  632. // give 2s to start update, then close
  633. sleepAsync(2000).then(() => { enUpdateWindow.close(); });
  634.  
  635. }
  636. },
  637.  
  638. 'dontCookies': {
  639. 'label': 'Mozilla no cookies...', // Appears on the button
  640. 'type': 'button', // Makes this setting a button input
  641. 'click': function() { // Function to call when button is clicked
  642. window.open('https://addons.mozilla.org/de/addon/i-dont-care-about-cookies/'); }
  643. },
  644.  
  645. // part two: EnstylerJS internal configuration options ------
  646. 'Section': { // display next section, dont kow why ...
  647. 'section': ['Configuration', ''],
  648. 'type': 'hidden', // Makes this setting a hidden input
  649. },
  650.  
  651. // postion of enstyler "button"
  652. 'enConfBtn': {
  653. 'label': 'Enstyler in MainNav', // Appears next to field
  654. 'type': 'checkbox', // Makes this setting a checkbox input
  655. 'default': false // Default value if user doesn't change it
  656. },
  657. 'enConfBtnMinWidth': {
  658. 'label': 'if width is bigger than ', // Appears next to field
  659. 'type': 'int', // Makes this setting a text input
  660. 'min': 600, // Optional lower range limit
  661. 'max': 1200, // Optional upper range limit
  662. 'size': 4, // Limit length of input (default is 25)
  663. 'default': 850 // Default value if user doesn't change it
  664. },
  665. 'enConfNavFixed': {
  666. 'label': 'Display FIXED MainNav', // Appears next to field
  667. 'type': 'checkbox', // Makes this setting a checkbox input
  668. 'default': true // Default value if user doesn't change it
  669. },
  670. // ehanced USerInfo
  671. 'enConfUser': {
  672. 'label': 'Show Popuop Userinfo', // Appears next to field
  673. 'type': 'checkbox', // Makes this setting a checkbox input
  674. 'default': true // Default value if user doesn't change it
  675. },
  676. 'enConfAvatar': {
  677. 'label': 'bigger Avatar for Popuop', // Appears next to field
  678. 'type': 'checkbox', // Makes this setting a checkbox input
  679. 'default': true // Default value if user doesn't change it
  680. },
  681. // enable filtering of external links
  682. 'enConfAmazonRedirect': {
  683. 'label': 'Amazon mobile redirect', // Appears next to field
  684. 'type': 'checkbox', // Makes this setting a checkbox input
  685. 'default': true // Default value if user doesn't change it
  686. }, // */
  687.  
  688. // more Deal actions
  689. 'enConfMoreDeal': {
  690. 'label': 'additional Deal actions', // Appears next to field
  691. 'type': 'checkbox', // Makes this setting a checkbox input
  692. 'default': true // Default value if user doesn't change it
  693. }, // more Deal actions
  694.  
  695. // show real Dealtime
  696. 'enConfDealTime': {
  697. 'label': 'Show real Deal Time', // Appears next to field
  698. 'type': 'checkbox', // Makes this setting a checkbox input
  699. 'default': true // Default value if user doesn't change it
  700. },
  701. 'enConfDealMinTime': {
  702. 'label': 'if older than Hours', // Appears next to field
  703. 'type': 'int', // Makes this setting a text input
  704. 'min': 1, // Optional lower range limit
  705. 'max': 24, // Optional upper range limit
  706. 'size': 4, // Limit length of input (default is 25)
  707. 'default': 6 // Default value if user doesn't change it
  708. },
  709.  
  710. // Page picker
  711. 'enConfPagePicker': {
  712. 'label': 'Enable Page Picker', // Appears next to field
  713. 'type': 'checkbox', // Makes this setting a checkbox input
  714. 'default': true // Default value if user doesn't change it
  715. },
  716.  
  717.  
  718. // Black/Whitelist input
  719. 'enConfBlackEnable': {
  720. 'label': 'Enable Black- / Whitelist', // Appears next to field
  721. 'type': 'checkbox', // Makes this setting a checkbox input
  722. 'default': true // Default value if user doesn't change it
  723. },
  724. 'enConfHideColder': {
  725. 'label': 'Blacklist if colder then', // Appears next to field
  726. 'type': 'int', // Makes this setting a text input
  727. 'min': -9999, // Optional lower range limit
  728. 'max': -9, // Optional upper range limit
  729. 'size': 4, // Limit length of input (default is 25)
  730. 'default': -999 // Default value if user doesn't change it
  731. },
  732. 'enConfBlacklist': {
  733. 'label': 'Blacklist - deals, categories, @users', // Appears next to field
  734. 'type': 'text', // Makes this setting a text input
  735. 'size': 70, // Limit length of input (default is 25)
  736. 'default': 'Nutella, Bangood, @Admin' // Default value if user doesn't change it
  737. },
  738. 'enConfWhitelist': {
  739. 'label': 'Whitelist', // Appears next to field
  740. 'type': 'text', // Makes this setting a text input
  741. 'size': 70, // Limit length of input (default is 25)
  742. 'default': '' // Default value if user doesn't change it
  743. },
  744. 'enConfUnblacklist': {
  745. 'label': 'UnBlacklist', // Appears on the button
  746. 'type': 'button', // Makes this setting a button input
  747. 'click': function() { // Function to call when button is clicked
  748. EnstylerBlacklistUnhide(); }
  749. },
  750. // display copy message at end of section ...
  751. 'copy': {
  752. 'section': ['', '(c) Gnadelwartz - <a target="blank" href="https://www.mydealz.de/diskussion/enstyler2-style-your-mydealz-incl-pepper-sites-736219">Enstyler2 - Style your MyDealz</a>'],
  753. 'type': 'hidden', // Makes this setting a hidden input
  754. },
  755. };
  756.  
  757. // define EnstylerJS GM_config elements
  758. var enHomefieldDefs = {
  759. // Part one: load external content --------
  760. 'saveOpt': {
  761. 'section': ['save your CSS options for next visit', ''],
  762. 'label': 'Select your CSS on main page then come back and klick "Save" ', // Appears near textarea
  763. 'type': 'textarea', // Makes this setting a button input
  764. 'size': 70,
  765. //'click': function() { // Function to call when button is clicked
  766. // showUrl('https://userstyles.org/styles/128262#style-info'); }
  767. },
  768. // display copy message at end of section ...
  769. 'copy': {
  770. 'section': ['', '(c) Gnadelwartz - <a target="blank" href="https://www.mydealz.de/diskussion/enstyler2-style-your-mydealz-incl-pepper-sites-736219">Enstyler2 - Style your MyDealz</a>'],
  771. 'type': 'hidden', // Makes this setting a hidden input
  772. 'value': 'Some hidden value' // Value stored
  773. },
  774. };
  775.  
  776. // display GM_copnfig as div, so we can apply CSS easy!!
  777. var enGMOptChange = false;
  778. var enGMFrame = document.createElement('div');
  779. enGMFrame.setAttribute('class','GM_config');
  780. document.body.appendChild(enGMFrame);
  781.  
  782. // basic config panel formatting, everything else is formatted by enstyler
  783. var enCSS = ['.GM_config {left: 5% !iportant; top: 9% !important; height: auto !important; max-width: 35em !important;}',
  784. '.GM_config input, .GM_config button, .GM_config textarea { border: 1px solid; margin: 0.5em 0em 0.2em 1em; padding: 0.1em;}',
  785. '.GM_config .reset { font-size: 9pt; padding-right: 1em; }',
  786. '.enClassHidden, #EnPopup_closeBtn {display: none;}',
  787. ].join(" ");
  788. addStyleString(enCSS);
  789.  
  790. var En_Popup = new GM_configStruct(
  791. {
  792. 'id': 'EnPopup', // You need to use a different id for each instance
  793. 'title': 'EnstylerJS - Info',
  794. 'fields': // Fields object
  795. {
  796. 'Text': // This is the id of the field
  797. {
  798. 'label': '', // Appears next to field
  799. 'type': 'textarea', // Makes this setting a text field
  800. 'default': '' // Default value if user doesn't change it
  801. }
  802. },
  803. 'events':
  804. {
  805. 'open': function (doc) {
  806. // rename the buttons
  807. var config = this;
  808. doc.getElementById(config.id + '_saveBtn').textContent = ' OK ';
  809. //doc.getElementById(config.id + '_closeBtn').textContent = 'Cancel';
  810. doc.getElementById(config.id + '_resetLink').textContent = '';
  811. },
  812. 'save': function() {
  813. enUpdateChecked=false;
  814. enCheckUpdates();
  815. En_Popup.close();
  816. },
  817. 'close': function() { enGMConfigOpen=false;},
  818. },
  819. 'frame': enGMFrame // Element used for the panel
  820. }
  821. );
  822.  
  823. function showPopup(text) {
  824. En_Popup.fields['Text'].value = text;
  825. En_Popup.fields['Text'].reload();
  826. En_Popup.open();
  827. }
  828.  
  829.  
  830. var enGMConfigOpen=false;
  831. // EnstylerJS Config Panel anzeigen
  832. function showEnstylerConfig() {
  833. if(!enGMConfigOpen) {
  834. enGetHome();
  835. GM_config.open();
  836. enGMConfigOpen=true;
  837. } else {
  838. GM_config.close();
  839. }
  840. }
  841.  
  842.  
  843. // EnstylerJS START ========================
  844. if (!window.location.hostname.endsWith('userstyles.org')) {
  845. var enFixedNav=false;
  846. GM_config.init(
  847. {
  848. // international sites support
  849. id: 'GM_config'+enInternationalSite,
  850. title: !DEBUG ? 'EnstylerJS - Settings' : ' EnstylerJS - >> Debug <<',
  851. fields: enJSfieldDefs,
  852. 'events': // Callback functions object
  853. {
  854. //'init': function() { alert('onInit()'); },
  855. // remove elements ich switch is checked or not
  856. 'open': function() {
  857. var enRemoveConfig = [
  858. // { check: true, switch: 'enConfFilterLink', remove: 'externalMobileRedirect'},
  859. { check: false, switch: 'enConfBtn', remove: 'enConfBtnMinWidth'},
  860. { check: false, switch: 'enConfUser', remove: 'enConfAvatar'},
  861. { check: false, switch: 'enConfDealTime', remove: 'enConfDealMinTime'},
  862. { check: false, switch: 'enConfBlackEnable', remove: 'enConfWhitelist'},
  863. { check: false, switch: 'enConfBlackEnable', remove: 'enConfBlacklist'},
  864. { check: false, switch: 'enConfBlackEnable', remove: 'enConfHideColder'},
  865. { check: false, switch: 'enConfBlackEnable', remove: 'enConfUnblacklist'}
  866. ];
  867. enFixedNav=GM_config.get('enConfNavFixed');
  868. // remove unneeded controls
  869. $(enRemoveConfig).each(function() {
  870. if (GM_config.get(this.switch) == this.check) {
  871. GM_config.fields[this.remove].remove();
  872. }
  873. });
  874. // remove / display update dialog
  875. if (enJSAutoUpdate) {GM_config.fields['enstylerJS'].remove();}
  876. },
  877. //'reset': function() { alert('reset') },
  878. // relaod page on close after save
  879. 'save': function() {
  880. // disabeling FixedNav can only done with reload
  881. if (!GM_config.get('enConfNavFixed') && GM_config.get('enConfNavFixed')!=enFixedNav) {window.location.reload(false);}
  882. // restart Enstyler magic
  883. EnstylerStart();
  884. EnstylerButtonCreate();
  885. EnstylerPagePickerCreate();
  886.  
  887. // show changes in config after processing magic
  888. GM_config.close();
  889. GM_config.open();
  890. },
  891. 'close': function() { enGMConfigOpen=false;},
  892. },
  893. 'frame': enGMFrame // Element used for the panel
  894. }
  895. );
  896. // Enstyler internal Startup functions ======================
  897. // HACK: we are NOT on Amazon
  898. if (enAmazonMobileRedirect()) {
  899. // Basic Initialisation
  900. function EnstylerInit () {
  901. // hide Enstyler2 CSS (c) text because we have a button now
  902. addStyleString('.threadWidget-footer::after {display: none !important};');
  903. // get LoginStatus and Username
  904. if (enUserLogin = $('.avatar--type-nav').length) {
  905. enUserName = $('.navDropDown a').attr('href');
  906. enUserName = enUserName.replace(/.*\/profile\/([^\/]+).*/,'$1');
  907. if(DEBUG) console.error('User: ' +enUserName);
  908. }
  909. // get Section (first element in path)
  910. enSection= enLocParser.pathname.replace(/\/([^\/]+\/*).*/,'/$1');
  911. if(DEBUG) console.error('Section: ' +enSection);
  912. }
  913. // dummy, do not delete
  914. function enGetHome() {;}
  915.  
  916. // Start Enstyler Magic
  917. function EnstylerStart() {
  918. EnstylerFixedNav();
  919. EnstylerDealTime();
  920. EnstylerLastSeen();
  921. EnstylerBlacklist();
  922. EnstylerAvatarPopup();
  923. EnstylerDealActions();
  924. }
  925.  
  926. // delayed actions after finishing everything else
  927. function EnstylerDelayedInit() {
  928. // don't know why, but works only if called with delay ...
  929. EnstylerButtonCreate();
  930. EnstylerPagePickerCreate();
  931. // track DOM change Events, debounce: wait 1000ms after mutiple events
  932. // then re-apply (somse) changes to dynamic loaded content,
  933. if(DEBUG) console.error('DOMSubtreeModified INIT');
  934. $('.cept-event-deals, .thread-list--type-card').bind("DOMSubtreeModified",$.debounce( 300, function(){
  935. EnstylerLastSeenDo();
  936. EnstylerDealTimeDo();
  937. EnstylerBlacklistDo();
  938. EnstylerAvatarPopupDo();
  939. EnstylerDealActionsDo();
  940. EnstylerPagePickerCreateDo();
  941. if(DEBUG) console.error('DOMSubtreeModified executed');
  942. }));
  943. }
  944.  
  945.  
  946. // =============== MAIN: START EnstyerJS ===================
  947. EnstylerInit();
  948. EnstylerStart()
  949. // wait until page is loaded completely
  950. if (document.readyState == 'loading' || document.readyState == 'interactive'){ // Greasemonkey and Tampermonky -> runs script on DOM ready -> wait for load
  951. if(DEBUG) console.error('Run on DOM ready');
  952. $(window).bind("load", function() { EnstylerDelayedInit(); });
  953. }
  954. else { // if script run on page loaded -> give some time to finish rendering
  955. if(DEBUG) console.error('Run on Document loaded');
  956. sleepAsync(Date.now()-EnstylerStartTime).then(() => { EnstylerDelayedInit(); });
  957. }
  958. } // END Enstyler MAIN
  959.  
  960.  
  961. // ============= EnStyler UserScript Homepage functions =======
  962. // support for EnStyler2 export / import
  963.  
  964. } else {
  965. // we are on ujserstyle
  966. function enGetHome() {
  967. var myOptions='';
  968. $('#style-settings select').each(function() {
  969. var myID = $(this).attr('id');
  970. var myValue = $('#'+myID).val();
  971. var myText = $('option[value='+ myValue +']').text();
  972. myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
  973. });
  974. $('#style-settings input:checked').each(function() {
  975. var myID = $(this).attr('id');
  976. var myValue = $('#'+myID).val();
  977. var myText = $('label[for='+ myID +']').text();
  978. myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
  979. });
  980. GM_config.set('saveOpt', myOptions);
  981. }
  982. function enSetHome() {
  983. // get saved options,remove newlines and split to settings array
  984. var myOptions=GM_config.get('saveOpt');
  985. myOptions=myOptions.replace(/\n/g,'');
  986. var mySettings = myOptions.split(';');
  987.  
  988. // abort if no options found
  989. if (myOptions=='' || !myOptions.startsWith('#')) {return;}
  990.  
  991. for (var i=0; i< mySettings.length; i++) {
  992. // each Setting has 3 fields seperated by :, but only 2 used
  993. var myField=mySettings[i].split(':');
  994.  
  995. if (myField[0].match(/^#setting/i)) {
  996. // select
  997. $(myField[0]).val('');
  998. $(myField[0]).val(myField[1]);
  999. } else if (myField[0].startsWith('#option')) {
  1000. // option
  1001. $(myField[0]).prop('checked', true);
  1002. } else {
  1003. if (myField[0] != '') {alert('unkown option: "' + myField +'"');}
  1004. }
  1005. }
  1006. }
  1007. // close Window after Click on Update
  1008. function UpdateOnClick () {
  1009. sleepAsync(10000).then(() => { window.close(); });
  1010. }
  1011. $(".install").click (UpdateOnClick);
  1012.  
  1013. // remove / display logo selection based on option
  1014. function UpdateLogoSelection() {
  1015. // selection is "Original"
  1016. if ($('#setting-453429').val() == 'ik-flame1') {
  1017. // hide logo selection
  1018. $('#style-settings > li:last-child').addClass('enClassHidden');
  1019. } else {
  1020. // show logo selection
  1021. $('#style-settings > li:last-child').removeClass('enClassHidden');
  1022. }
  1023. }
  1024. $('#setting-453429').change(UpdateLogoSelection);
  1025. // activate config for Enstyler Homepage
  1026. GM_config.init(
  1027. {
  1028. id: 'GM_config',
  1029. title: 'Enstyler2 - Settings',
  1030. fields: enHomefieldDefs,
  1031. 'events': // Callback functions object
  1032. {
  1033. //'init': function() { alert('onInit()'); },
  1034. // remove elements ich switch is checked or not
  1035. //'open': function() { enGetHome(); },
  1036. //'reset': function() { enGMOptChange = true; },
  1037. // relaod page on close after save
  1038. 'save': function() { enSetHome(); GM_config.close();},
  1039. 'close': function() { enGMConfigOpen=false; },
  1040. },
  1041. 'frame': enGMFrame // Element used for the panel
  1042. }
  1043. );
  1044. // START Enstyler 2 Homepage
  1045. EnstylerHomeButton();
  1046. // set saved options
  1047. enSetHome();
  1048. // show/hide logo selection
  1049. UpdateLogoSelection();
  1050.  
  1051. }
  1052.  
  1053.  
  1054. //=========== Support functions for actual use ======
  1055.  
  1056. // add CSS in to document
  1057. function addStyleString(str) {
  1058. var node = document.createElement('style');
  1059. node.innerHTML = str;
  1060. document.body.appendChild(node);
  1061. }
  1062.  
  1063. // insertAfter like .insertBefore but as support function
  1064. function insertAfter(newNode, referenceNode) {
  1065. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  1066. }
  1067.  
  1068. // sleep time expects milliseconds, then execute code
  1069. // NOTE: code runs in parallel (asnyc)!
  1070. // Usage!
  1071. // sleepAsync(500).then(() => {
  1072. // Do something after the sleep!
  1073. // });
  1074.  
  1075. function sleepAsync (time) {
  1076. return new Promise((resolve) => setTimeout(resolve, time));
  1077. }