Keyboard navigation and Cookie Wall skipper

Keyboard based navigation

当前为 2022-11-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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');