Greasy Fork 还支持 简体中文。

EnstylerJS

MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect

目前為 2016-11-30 提交的版本,檢視 最新版本

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