Keyboard navigation and Cookie Wall skipper

Keyboard based navigation

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name Keyboard navigation and Cookie Wall skipper
// @version 1.12.16
// @author Sly_North
// @description Keyboard based navigation
// @grant none
// @run-at document-start
// @namespace Sly_North
// @include *
// require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @license MIT
// ==/UserScript==

console.log('Keyboard navigation script...');

// Click first link (tag='a' or 'span') with text matching one of 'keywords' regex
function ClickLink(tag, keywords) {
  var elts = document.getElementsByTagName(tag);
  for (var elt of elts) {
    for (var regexp of keywords) {
      if (elt.textContent.match(regexp)) {
        console.log('Clicking ', elt.textContent);
        elt.click();
        return true;
      }
    }
  }
  return false;
}

function ClickLinkByClass(classname) {
  let elts = document.getElementsByClassName(classname);
  if (elts.length > 0) elts[elts.length - 1].click();
}

// Make 'link' visibly selected.
var lastSelected; // previous selected link
function SelectLink(link, links) {
  if (lastSelected) lastSelected.style.backgroundColor = "";
  lastSelected = link;

  link.focus();
  link.style.backgroundColor = "orange";
}

// Select either link with preferred text or closer to the center.
function SelectBestLink() {
  var links = document.getElementsByTagName('a');
  var curr = document.activeElement;
  var viewport = window.visualViewport;
  var screenHeight = viewport.height;
  var midX = viewport.width/2;
  var midY = screenHeight/2;
  var minD = 1000000000;
  var best;

  var keywords = [/cliquez sur ce lien/i, /#unread/, /Discussions/];

  for (var i = 0; i < links.length; ++i) {
    var link = links[i];
    var br = link.getBoundingClientRect();
    var d = Math.abs(br.left - midX) + Math.abs(br.top - midY);
    if ((link == curr)) continue;

    // Insert here other preferred link texts.
    for (var k in keywords) {
      if (link.text.match(keywords[k])) {
     if ((br.top >= 0) && (br.top < screenHeight)) {
       // link.click();
       SelectLink(link, links);
       console.log('Select link to current page [', link.innerText, '] - boundingRect.top=', br.top);
        return;
        }
      }
    }

    // Select link to current page, if not already selected
    if (link.href == document.URL) {
      if ((br.top >= 0) && (br.top < screenHeight)) {
        // If on start, focus it, without highlighting it.
        SelectLink(link, links);
        console.log('Select link to current page [', link.innerText, '] - boundingRect.top=', br.top);
        return;
      }
    }
    // Select link closer to the center
    if (d < minD) {
      minD = d;
      best = link;
    }
  }
  if (best) {
    // If on start, focus it, without highlighting it.
    console.log("Select link", best.text);
    SelectLink(best, links);
    console.log('Select link [', link.innerText, ']');
  }
  else console.log('Could not find best link');
}

// Skip most simple cookie forms.
function SkipCookieWall() {
  // console.log('SkipCookieWall...');
  for (let tag of ['button', 'span', 'a']) {
    let elts = document.getElementsByTagName(tag);
    for (let e of elts) {
      var t = e.innerText;
      if (!t) continue;
      if (t.match(/Reject all/i) || t.match(/Tout refuser/) || t.match(/Tout rejeter/i) || t.match(/Refuser tout/i) || t.match(/Continuer sans accepter/i) ||
          t.match(/refuse tous les cookies/) || t.match(/Reject non-essential/) || t.match(/Do not accept/) || t.match(/Do not consent/) ||
          t.match(/Continue without agree/) || t.match(/Only allow essential cookies/) || t.match(/Refuse non-essential cookies/) ||
          t.match(/I Do Not Accept/i) || t.match(/Decline optional cookies/)) {
        console.log("SkipCookieWall - Clic ", t);
        e.click();
        return;
      }
    }
  }
 
  for (let e of document.getElementsByClassName('gdpr-lmd-wall')) {
    e.style.display = 'none';
  }

  // console.log('Cookie skip not found');
}

function focusSearchInput() {
  var curr = document.activeElement;
  var inputs = Array.from(document.getElementsByTagName('input')).filter((e)=>{return e.type=="text" || e.type=="search";});
  for (let i of inputs) {
    if (i == curr) continue;
    let rect = i.getBoundingClientRect();
    if (rect.width > 0 && rect.height > 0) {
      // console.log(' - Found text input');
      i.focus();
      return;
    }
  }
  console.log('focusSearchInput - did not found a search text box');
}


// Unblock the page scrolling.
function Unfreeze() {
  console.log('Unfreeze overflow');
  document.documentElement.classList.remove('fixed')
  document.body.classList.remove('content-blurred');
  document.body.classList.remove('noscroll');
  document.body.classList.remove('retention--open');
  if (document.body.style.overflow) document.body.style.overflow = "scroll";
  if (document.documentElement.style.overflow) document.documentElement.style.overflow = "scroll";

 
  // Site specifics
  document.body.classList.remove('didomi-popup-open');
  document.body.classList.remove('popin-gdpr-no-scroll');

  // NY-Times
  var siteContent = document.getElementById('site-content')
  if (siteContent) siteContent.style.position = "relative"

  // Quora
  for (let e of document.getElementsByClassName('q-box')) if (e.style.filter) e.style.filter = "";

  for (let bluredDiv of document.getElementsByClassName('content-blurred'))
    bluredDiv.classList.remove('content-blurred');
  
  // Reddit loggin + blurred background
  for (let e of document.getElementsByTagName('xpromo-nsfw-blocking-modal-desktop')) e.parentElement.remove(e);
  for (let e of document.getElementsByClassName("sidebar-grid")) e.style.filter = '';
}

function TranslateYoutubeCaptions() {
  console.log('TranslateYoutubeCaptions...');
  // $('.ytp-subtitles-button[aria-pressed="false"]').click();
  const captionButtons = document.getElementsByClassName('ytp-subtitles-button');
  if (captionButtons.length > 0 && captionButtons[0].ariaPressed==='false') captionButtons[0].click();
  // $('.ytp-settings-button').click();
  document.getElementsByClassName('ytp-settings-button')[0].click();

  // var sub = $('[role="menuitem"]:contains("Subtitles")');
  var sub = Array.from(document.getElementsByClassName('ytp-menuitem')).filter(e=>e.role==='menuitem').filter(e => e.innerText.match(/Subtitles/));
  if(!sub.length) { console.log('No subs available'); return; }
  sub[0].click();
  // var subc = $('[role="menuitemradio"]:contains("English")');
  var subc = Array.from(document.getElementsByClassName('ytp-menuitem')).filter(e=>e.role==='menuitemradio').filter(e => e.innerText.match(/English/));
  if (subc.length) {
    console.log('Enabling available English captions');
    subc[0].click();
  } else {
    // var autoTrans = $('[role="menuitemradio"]:contains("Auto-translate")');
    var autoTrans = Array.from(document.getElementsByClassName('ytp-menuitem')).filter(e=>e.role==='menuitemradio').filter(e => e.innerText.match(/Auto-translate/));
    if (!autoTrans.length) { console.log('YT autotranslate not available'); return; }
    autoTrans[0].click();
    // var autoTransC = $('[role="menuitemradio"]:contains("English")');
    var autoTransC = Array.from(document.getElementsByClassName('ytp-menuitem')).filter(e=>e.role==='menuitemradio').filter(e => e.innerText.match(/English/));
    if (!autoTransC.length) { console.log('YT autotranslate not available in English'); return; }
    autoTransC[0].click();
  }
}

// Select direction in which to look for 'best' link
function MoveSelectedElement(keyCode) {
  var dirX = 0, dirY = 0;
  var scaleX = 1; // used to allow going up/down to a link not on a straight alignement (angle > 45°)
  var maxDY = 10000000; // used to strongly limit vertical search when going left/right
  var scaleWidth = 0; // To select left/center/right of elements
  if (keyCode == 37 || keyCode == 72)      { dirX = -1; scaleWidth = 0; maxDY = 50; scaleX = 0.25; } // left
  else if (keyCode == 39 || keyCode == 76) { dirX = +1; scaleWidth = 1; maxDY = 50; scaleX = 0.25; } // right
  else if (keyCode == 38 || keyCode == 75) { dirY = -1; scaleWidth = 0.5; scaleX = 0.25; } // up
  else if (keyCode == 40 || keyCode == 74) { dirY = +1; scaleWidth = 0.5; scaleX = 0.25; } // down
  else return false;

  // Look for next link in a given direction
  var adx = Math.abs(dirX), ady = Math.abs(dirY);
  if (!lastSelected) {
    console.log('1st select best link');
    SelectBestLink();
  }
  var curr = document.activeElement;
  var currBR = curr.getBoundingClientRect();
  var currX = currBR.left + currBR.width*scaleWidth;
  var currY = currBR.top  + currBR.height/2;
  var links = document.getElementsByTagName('a');
  var best;
  var minD = 1500;

  for (var i = 0; i < links.length; ++i) {
    var link = links[i];
    if (link != curr) {
      var br2 = link.getBoundingClientRect();
      if (br2.width == 0 && br2.height == 0) continue;

      var dx = (br2.left + br2.width*scaleWidth - currX) * scaleX;
      var dy = br2.top + br2.height/2 - currY;
      var adx = Math.abs(dx);
      var ady = Math.abs(dy);
      if (ady > maxDY) continue;
      if ((adx > ady) != (dirX != 0)) continue; // Wrong angle

      var dot = dx * dirX + dy * dirY;
      if (dot <= 0) continue;
      var dist = Math.abs(dx) + Math.abs(dy);
      if (dist < minD) {
        best = link;
        minD = dist;
        // console.log("   new best link dx = ",dx," dy = ", dy, "  dist = ", dist, ' dot=', dot, 'dot/dist=', dot/dist, ' ', link.text);
      }
    }
  }

  if (best) {
    SelectLink(best, links);
    console.log('Select link [', link.innerText, ']');
  }
  return true;
}

function AdvanceSeveralPages(nbrPages) {
  let url = document.location.href;
  let currPage = url.match(/page=\d*/).join();
let newPage = Number(currPage.match(/\d\d*/).join()) + nbrPages;
  url = url.replace(/page=\d*/, "page=" + newPage);
  document.location.href = url;
}

// Listen for a few control+alt + key
document.addEventListener('keydown', function(e) {
  if (e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 224) return; // Ignore event for special keys Ctrl/Shift/Option

  // console.log(`Key ${e.keyCode} shift=${e.shiftKey} ctrl=${e.ctrlKey} metal=${e.metaKey}`);
  if (e.shiftKey || !e.ctrlKey || (!e.altKey && !e.metaKey)) {
  // if ((!e.shiftKey && !e.ctrlKey) || (!e.altKey && !e.metaKey)) { // For Mac, accept shift|control
    // if (e.keyCode == 37) ClickLink('button', [/arrow down/]); // Left arrow key
    // else if (e.keyCode == 39) ; // Right arrow key
    // console.log(`Ignored Key ${e.keyCode} shift=${e.shiftKey} ctrl=${e.ctrlKey} metal=${e.metaKey}`);
    return;
  }

  // Space=select best link, enter=click best link, #/del=GMail's delete old messages in trash, o[/p]=previous/next page, /=search box
  switch (e.keyCode) {
    case 32: case 220: SelectBestLink(); return; // Space
    case 13: case 90: ClickLink('a', [/cliquez sur ce lien/, /click this link/, /unread#unread/]); return; // Enter or Z
    case 46: case 51: ClickLink('span', [/^delete forever/]); return; // # or del
    case 191: focusSearchInput(); return; // '/'
    case 83: case 89: TranslateYoutubeCaptions(); return; // S or Y
    case 85: Unfreeze(); return; // U
    case 67: SkipCookieWall(); return; // C
    case 79: case 219: // O or [
      if (!ClickLink('a', [/Précédent/i, /Prev/, /Previous page/, /Older/]))
        ClickLink('i', [/ chevron_left /, /fas fa-angle-right/]);
        ClickLinkByClass('fas fa-angle-left');
      return;
    case 80: case 221: // P or ]
      if (!ClickLink('a', [/Suivant/i, /Next/, /Next page/, /Newer/]) &&
        !ClickLink('i', [/ chevron_right /]))
        ClickLinkByClass('fas fa-angle-right');
      return;
    case 222: AdvanceSeveralPages(3); return; // "/'
  }

  if (!MoveSelectedElement(e.keyCode)) {
    console.log('Key ignored:', e.keyCode);
  }
}, false);

for (let i of [500, 1000, 2000, 3000, 4000]) setTimeout(() => {
  SkipCookieWall();
  if (document.location.href.match(/forum.hardware.fr/)) {
    let e = document.getElementById('filter');
    if (e) e.checked = true;
  }
}, i);

console.log('Keyboard navigation handler ready');