Keyboard navigation and Cookie Wall skipper

Keyboard based navigation

目前為 2022-11-08 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name Keyboard navigation and Cookie Wall skipper
// @version 1.12.4
// @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('Key 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 i = 0; i < elts.length; ++i) {
    var elt = elts[i];
    for (k in keywords)
    {
      var regexp = keywords[k];
      if (elt.textContent.match(keywords[k])) {
        console.log('Clicking ', elt.textContent);
        elt.click();
        return true;
      }
    }
  }
  return false;
}

// 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 (i in elts) {
      var t = elts[i].innerText;
      if (!t) continue;
      if (t.match(/Reject all/i) || t.match(/Tout refuser/) || t.match(/Refuser tout/i) || t.match(/Continuer sans accepter/i) ||
          t.match(/refuse tous les cookies/) || t.match(/Reject non-essential/) ||
          t.match(/Continue without agree/) || t.match(/Only allow essential cookies/) || t.match(/Refuse non-essential cookies/)) {
        console.log("Clic ", t);
        elts[i].click();
        return;
      }
    }
  }

  // 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.body.classList.remove('content-blurred');
  document.body.classList.remove('noscroll');
  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');
}

function TranslateYoutubeCaptions() {
  console.log('L144');
  $('.ytp-subtitles-button[aria-pressed="false"]').click();
  $('.ytp-settings-button').click();

  console.log('L148');
  var sub = $('[role="menuitem"]:contains("Subtitles")');
  if(!sub.length) { console.log('No subs available'); return; }
  sub.click();
  var subc = $('[role="menuitemradio"]:contains("English")');
  if (subc.length) {
    console.log('Enabling available English captions');
    subc.click();
  } else {
    var autoTrans = $('[role="menuitemradio"]:contains("Auto-translate")');
    if (!autoTrans.length) { console.log('YT autotranslate not available'); return; }
    autoTrans.click();
    var autoTransC = $('[role="menuitemradio"]:contains("English")');
    if (!autoTransC.length) { console.log('YT autotranslate not available in English'); return; }
    autoTransC.click();
  }
  console.log('end');
}

// 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
console.log('Install key navigation key listener');
document.addEventListener('keydown', function(e) {
  // console.log('ctrl=',e.ctrlKey,' alt=',e.altKey, ' meta=', e.metaKey, ' key=',e.keyCode);
  // console.log(`Key ${e.keyCode} shift=${e.shiftKey} ctrl=${e.ctrlKey} metal=${e.metaKey}`);
  if (e.shiftKey || !e.ctrlKey || (!e.altKey && !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 /]);
      return;
    case 80: case 221: // P or ]
      if (!ClickLink('a', [/Suivant/i, /Next/, /Next page/, /Newer/]))
        ClickLink('i', [/ chevron_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();}, i);

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