Skip to content

Instantly share code, notes, and snippets.

@sp00n
Last active April 8, 2024 22:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sp00n/e8b91d2f47c471bc0627f7b31d659291 to your computer and use it in GitHub Desktop.
Save sp00n/e8b91d2f47c471bc0627f7b31d659291 to your computer and use it in GitHub Desktop.
Restored Pagination for Google (Disable Continuous / Infinite Scrolling)
// ==UserScript==
// @name Restored Pagination for Google
// @namespace google-pagination.sp00n.net
// @match *://*/search*
// @grant none
// @version 0.2
// @author sp00n
// @description This script restores the paged navigation for the Google Search. It also requires that the "Continuous scrolling" in the Google Search settings is turned off (which infuriatingly does not restore the old Pagination!)
// @downloadURL https://gist.github.com/sp00n/e8b91d2f47c471bc0627f7b31d659291
// ==/UserScript==
// Limit the script execution to only Google pages
// @match only allows wildcards for the TLD in ViolentMonkey, not in TamperMonkey or other alternatives
// @includeGlobs also doesn't work in all addons
// These are all the TLDs that Google runs on
// Taken from https://www.google.com/supported_domains
const supportedTLDs = [
"com", "ad", "ae", "com.af", "com.ag", "al", "am", "co.ao", "com.ar", "as", "at", "com.au", "az", "ba", "com.bd", "be", "bf", "bg", "com.bh", "bi", "bj",
"com.bn", "com.bo", "com.br", "bs", "bt", "co.bw", "by", "com.bz", "ca", "cd", "cf", "cg", "ch", "ci", "co.ck", "cl", "cm", "cn", "com.co", "co.cr",
"com.cu", "cv", "com.cy", "cz", "de", "dj", "dk", "dm", "com.do", "dz", "com.ec", "ee", "com.eg", "es", "com.et", "fi", "com.fj", "fm", "fr", "ga", "ge",
"gg", "com.gh", "com.gi", "gl", "gm", "gr", "com.gt", "gy", "com.hk", "hn", "hr", "ht", "hu", "co.id", "ie", "co.il", "im", "co.in", "iq", "is", "it",
"je", "com.jm", "jo", "co.jp", "co.ke", "com.kh", "ki", "kg", "co.kr", "com.kw", "kz", "la", "com.lb", "li", "lk", "co.ls", "lt", "lu", "lv", "com.ly",
"co.ma", "md", "me", "mg", "mk", "ml", "com.mm", "mn", "com.mt", "mu", "mv", "mw", "com.mx", "com.my", "co.mz", "com.na", "com.ng", "com.ni", "ne", "nl",
"no", "com.np", "nr", "nu", "co.nz", "com.om", "com.pa", "com.pe", "com.pg", "com.ph", "com.pk", "pl", "pn", "com.pr", "ps", "pt", "com.py", "com.qa", "ro",
"ru", "rw", "com.sa", "com.sb", "sc", "se", "com.sg", "sh", "si", "sk", "com.sl", "sn", "so", "sm", "sr", "st", "com.sv", "td", "tg", "co.th", "com.tj",
"tl", "tm", "tn", "to", "com.tr", "tt", "com.tw", "co.tz", "com.ua", "co.ug", "co.uk", "com.uy", "co.uz", "com.vc", "co.ve", "co.vi", "com.vn", "vu", "ws",
"rs", "co.za", "co.zm", "co.zw", "cat"
];
const tldString = supportedTLDs.join("|").replaceAll(".", String.raw`\.`); // Uses String.raw to be able to insert a backslash before the dot. Is there an easier way?
const googleRegEx = new RegExp(`^http(s)?://(www\.)?google\.(${tldString})/search\?.*`, "i");
// No Google, no continue!
if ( !googleRegEx.test(location.href) ) {
return;
}
// Google uses a bunch of parameters, but we only need a couple
// https://www.google.com/search?hl=<LANG>&q=<QUERY>&tbm=isch&sa=X&ved=<UNIQUE_ID>&biw=<WIDTH>&bih=<HEIGHT>&dpr=<DPI_SCALING_INT_OR_DECIMAL>
// Date Filters
// Last Hour https://www.google.com/search?q=abc&tbs=qdr:h
// Last Month https://www.google.com/search?q=abc&tbs=qdr:m
// Last Year https://www.google.com/search?q=abc&tbs=qdr:y
// https://www.google.com/search?q=abc&tbs=cdr%3A1%2Ccd_min%3A01.01.2020%2Ccd_max%3A02.02.2022&tbm=
// Language
// German https://www.google.com/search?q=abc&tbs=lr:lang_1de&lr=lang_de
// Image Search
// https://www.google.com/search?q=abc&tbm=isch
let params = new URL(document.location).searchParams;
let searchQuery = params.get("q");
let currentStartEntries = parseInt(params.get("start")) || 0;
let previousStartEntries = Math.max(0, currentStartEntries - 10);
let nextStartEntries = currentStartEntries + 10; // Can we get the maximum amount of search results?
let currentPage = Math.floor(currentStartEntries / 10) + 1; // Page 1 has start=0, Page 2 start=10, etc
let firstPageToDisplay = 1;
let lastPageToDisplay = 10;
// Very weak detection for the dark mode, but here we go
let isDarkMode = (getComputedStyle(document.body).backgroundColor != "rgb(255, 255, 255)");
// Abort if we're on the image search
if ( params.has("tbm") && params.get("tbm") == "isch" ) {
return;
}
// Build the fixed query string
let fixedQueryParams = new URLSearchParams();
fixedQueryParams.set("q", searchQuery);
if ( params.has("filter") ) {
fixedQueryParams.set("filter", params.get("filter"));
}
if ( params.has("tbs") ) {
fixedQueryParams.set("tbs", params.get("tbs"));
}
if ( params.has("lr") ) {
fixedQueryParams.set("lr", params.get("lr"));
}
let fixedQueryString = fixedQueryParams.toString();
// Determine which pages to display
// Page 1-6: display 1-10
// Page 7: 2-11
// Page 8: 3-12
// etc
// 5 to the left visible, 4 to the right
if ( currentPage >= 7 ) {
firstPageToDisplay = currentPage - 5;
lastPageToDisplay = currentPage + 4;
}
// The entries HTML string
let entriesHTMLString = "";
let entryBgPosTop = ( isDarkMode ) ? -112 : 0;
// Generate the entries
for ( let entryPage = firstPageToDisplay; entryPage <= lastPageToDisplay; entryPage++ ) {
let entryStartEntries = (entryPage - 1) * 10;
let entryBgPosLeft = ( entryPage == 1 ) ? -53 : -74;
let entryTemplate = "";
// The template for an entry in the navigation
if ( entryPage == currentPage ) {
entryTemplate = `
<td class="YyVfkd">
<span class="SJajHc" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:${entryBgPosLeft}px ${entryBgPosTop}px;background-size:167px;width:20px"></span>
${entryPage}
</td>
`;
}
else {
entryTemplate = `
<td>
<a aria-label="Page ${entryPage}" class="fl" href="/search?${fixedQueryString}&amp;start=${entryStartEntries}">
<span class="SJajHc NVbCr" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:${entryBgPosLeft}px ${entryBgPosTop}px;background-size:167px;width:20px"></span>
${entryPage}
</a>
</td>
`;
}
entriesHTMLString += entryTemplate;
}
// The navigation table
let navigationHTMLString = `
<table class="AaVjTc" style="border-collapse:collapse;text-align:left" role="presentation" id="restored-pagination">
<tbody>
<tr jsname="TeSSVd" valign="top">
<td aria-level="3" class="d6cvqb BBwThe" role="heading">
<a href="/search?${fixedQueryString}&amp;start=${previousStartEntries}" id="pnprev">
<span class="SJajHc NVbCr" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:0 ${entryBgPosTop}px;background-size:167px;width:53px;float:right"></span>
<span style="display:block;margin-right:35px;clear:right">Previous</span>
</a>
</td>
`
+ entriesHTMLString +
`
<td aria-level="3" class="d6cvqb BBwThe" role="heading">
<a href="/search?${fixedQueryString}&amp;start=${nextStartEntries}" id="pnnext" style="text-align:left">
<span class="SJajHc NVbCr" style="background:url(/images/nav_logo321_hr.webp) no-repeat;background-position:-96px ${entryBgPosTop}px;background-size:167px;width:71px"></span>
<span style="display:block;margin-left:53px">Next</span>
</a>
</td>
</tr>
</tbody>
</table>
`;
// Insert the table
// Get the old navigation container at the bottom, which is currently empty, but this may change or be removed in the future
const navigationContainer = [...document.querySelectorAll("div[role='navigation']:not([class])")].pop();
navigationContainer.innerHTML = navigationHTMLString;
// And some additional styles
let styles = `
<style type="text/css">
#restored-pagination.AaVjTc {
margin: 30px auto 30px;
}
#restored-pagination.AaVjTc td {
padding: 0;
text-align: center;
}
#restored-pagination.AaVjTc a:link {
color: ${isDarkMode ? "#8ab4f8" : "#4285f4"};
font-weight: normal;
}
#restored-pagination .SJajHc {
background: url(/images/nav_logo321_hr.webp) no-repeat;
background-size: 167px;
overflow: hidden;
background-position: 0 0;
height: 40px;
display: block;
}
#restored-pagination .NVbCr {
cursor: pointer;
}
#restored-pagination .YyVfkd {
color: ${isDarkMode ? "#bdc1c6" : "#202124"};
font-weight: normal;
}
</style>
`;
navigationContainer.innerHTML += styles;
@sp00n
Copy link
Author

sp00n commented Jul 5, 2023

This script restores the paged navigation for the Google Search.
It also requires that the "Continuous scrolling" in the Google Search settings has been turned off. Unfortunatly this setting doesn't restore the old behavior, which is why this script exists.

@Asqii
Copy link

Asqii commented Jul 19, 2023

match has to contain TLD and asterisks after search to work properly with tampermonkey

should be \\ @match https://www.google.com/search*

@Asqii
Copy link

Asqii commented Jul 19, 2023

Can you update from my fork? I've added an exclusion to prevent it from displaying in image search (it didn't work there anyway)

@sp00n
Copy link
Author

sp00n commented Jul 19, 2023

I didn't want to add a specific TLD so that it would work for all Google pages (like .com, .ca, .de, .fr, etc).
It works with Violentmonkey, but that's apparently the only implementation of the userscript addons that can do this, all other limit the wildcard usage.

I've updated the code, which now does the check in the code itself. Which means that initially it is loaded for all pages that contain a "/search" in their URL, but it is what it is. I didn't want to limit it only a couple of domains (there seems to be a limit for how many @matches you can add).
It now should only run on those pages that are owned by Google and abort otherwise (and also abort on the Image Search page).

I also had to update the code slightly, as Google made some changes, at least for me, so that the navigation wasn't showing at the correct position anymore.

@betamanx
Copy link

Thank you so much, really thank you!

@AstroSkipper
Copy link

AstroSkipper commented Dec 5, 2023

Hello! Thank you very much for this script! Unfortunately, there is a problem when using this script in combination with Google's advanced search. When calling up the page 2 of the search results, the search string is changing to "null". Therefore, this script doesn't work properly with the advanced search of Google. Maybe, you can fix it. Would be great in any case!

@new-chris
Copy link

This script works fine in 2024-4-6.
Thank you Sir.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment