-
Notifications
You must be signed in to change notification settings - Fork 278
Documentation: Fast Forward
Fast Forward function scans the page for links, pointing to the next page and chooses the best one based on token matching. There are 5 different token groups, each of them have different default scores (weights):
- rel attribute, default score - 9000
- text - link's text, aria-label, title and alt attributes, default score - 100
- id - link's id attribute, default score - 11
- class - link's classes, default score - 10
- href - link's href, default score - 1
Functionality is split into 3 parts:
- fastforward.ini - file for tokens and their scores
- fastforward.js - JavaScript for scanning the page for the best fast-forward link
- Otter's integration on c++ side
Tokens are stored in fastforward.ini file. It is bundled in Otter's resources, but it can be replaced with custom file, located in Otter's configuration directory (i.e. ~/.config/fastforward.ini). File is grouped by token groups, each token can have a score. If no score is given, default group's score will be used. Scores can be negative, allowing to disfavor a link (i.e. if you don't want <a href="..">Next Generation</a> to be considered as a valid fast-forward candidate, you can assign generation=-100 in fastforward.ini).
All tokens are case insensitive.
Example of fastforward.ini:
[Href]
page
start
startpage
paged
[Class]
next
paging-next
[Id]
next
[Text]
; Special ones
→=50
▶=50
›=50
»=49
; We don't want to fast-forward on chan's quotes!
>=10
; Afrikaans
volgende
volg
verder
; Bulgarian
напред
следва
следна
нататък
; Chinese (traditional and simplified)
下一
; Danish
næste
; Deutsch
nächste
weiter
vorwärts
naechste
vorwaerts
; Dutch
volgende
; English
next
; Esperanto
venonta
; Estonian
järgmine
; Finnish
seuraava
; French
suivant
prochaine
page suivante
page prochaine
suivants
suivantes
; Greek
επόμενη
; Icelandic
næst
; Italian
succ
; Japanese
次へ
次の
; Korean
다음
; Lithuanian
toliau
kitas
sekantis
; Norwegian
neste
; Polish
dalej
następna
następne
następny
więcej
; Portuguese
próximo
página seguinte
; Russian
следующая
дальше
вперед
; Spanish
siguiente
próxima
próximos
; Swedish
nästa
; Turkish
sonraki
Each token from fastforward.ini is matched with approporate link's attribute via contains criteria. If it matches, token's score is added to the link. Link with the highest score wins. If link's score is lower than threshold (default threshold is 10), it will not be considered as valid fast-forward candidate.
During execution, function is called with these parameters:
- {isSelectingTheBestLink} - true if script is executed only to check if fast-forward button should be enabled. Optimal result is not required, if at least one link with a score above threashold is found - that is enough.
- {hrefTokens}, {classTokens}, {idTokens}, {textTokens} - array token objects for each group, generated from fastforward.ini. Example:
[{
value: 'next',
score: 100
},
{
value: 'nästa',
score: 100
}]
Example of fastforward.js:
(function(isSelectingTheBestLink, hrefTokens, classTokens, idTokens, textTokens)
{
const REL_SCORE = 9000;
const TEXT_SCORE = 100;
const ID_SCORE = 11;
const CLASS_SCORE = 10;
const HREF_SCORE = 1;
const THRESHOLD = 10;
const DEBUG = false;
function calculateScore(value, tokens, defaultScore)
{
var score = 0;
if (value)
{
for (var i = (tokens.length - 1); i >= 0; --i)
{
if (value.indexOf(tokens[i].value) != -1)
{
score += tokens[i].score || defaultScore;
}
}
}
return score;
}
function calculateScoreForValues(values, tokens, defaultScore)
{
var score = 0;
if (values && values.length > 0)
{
for (var i = (values.length - 1); i >= 0; --i)
{
score += calculateScore(values[i].toUpperCase(), tokens, defaultScore);
}
}
return score;
}
var links = document.querySelectorAll('a:not([href^="javascript:"]):not([href="#"])');
var scoredLinks = [];
if (DEBUG)
{
console.log('FastForward DEBUG: Checking', links.length, 'links');
}
for (var i = (links.length - 1); i >= 0; --i)
{
var score = 0;
if (links[i].rel && links[i].rel.indexOf('next') != -1)
{
score += REL_SCORE;
}
score += calculateScore([links[i].innerText, links[i].getAttribute('aria-label'), links[i].getAttribute('alt'), links[i].title].join(' ').toUpperCase(), textTokens, TEXT_SCORE);
score += calculateScore(links[i].id.toUpperCase(), idTokens, ID_SCORE);
score += calculateScoreForValues(links[i].classList, classTokens, CLASS_SCORE);
var url = links[i].pathname.split('/');
if (links[i].search)
{
links[i].search.substr(1).split('&').forEach(function(item)
{
url.push(item.split('=')[0]);
});
}
score += calculateScoreForValues(url, hrefTokens, HREF_SCORE);
//Is link worthy?
if (score > THRESHOLD)
{
if (isSelectingTheBestLink)
{
scoredLinks.push({score: score, link: links[i]});
}
else
{
if (DEBUG)
{
console.log('FastForward DEBUG: Found at least one link for FastForward. Score:', score, links[i].href);
}
return true;
}
}
}
if (scoredLinks.length > 0)
{
scoredLinks.sort(function(first, second)
{
return (second.score - first.score);
});
if (DEBUG)
{
for (var i = 0; i < scoredLinks.length; ++i)
{
console.log('FastForward DEBUG: ', scoredLinks[i].score, 'Url:', scoredLinks[i].link.outerHTML);
}
console.log('FastForward DEBUG: Choosing link with score', scoredLinks[0].score, scoredLinks[0].link.href);
}
//Convert link's url to absolute path
var link = document.createElement('a');
link.href = scoredLinks[0].link.href;
return link.href;
}
if (DEBUG)
{
console.log('FastForward DEBUG: No candidate links found!');
}
return null;
})({isSelectingTheBestLink}, {hrefTokens}, {classTokens}, {idTokens}, {textTokens})