Sofascore browser search - GIF example
I watch a lot of football and I'm constantly looking things up on Sofascore. Opening the site, clicking the magnifier, typing — it adds up. What I want is: type sofa barcelona in my address bar, hit Enter, land on the search results.
Sofascore's URL doesn't accept a ?q= parameter natively, and the search input is a React-controlled field that ignores programmatically-set values. Getting from the address bar to a live Sofascore search takes three pieces:
- A browser keyword shortcut that routes
sofa <query>to Sofascore with the query attached as?q= - A Tampermonkey userscript that reads
?q=on page load and feeds it into the search box - A local typing service that actually types the query, key by key, because the React input won't accept anything less
1. Add the browser keyword
Chrome (and any Chromium browser) supports address-bar search keywords. Add one pointing at Sofascore:
- Open
chrome://settings/searchEngines - Click Add under "Site search"
- Set:
- Name: Sofascore
-
Shortcut:
sofa -
URL:
https://www.sofascore.com/pl/?q=%s
Firefox and Safari have equivalent settings — the important part is the %s placeholder, which the browser replaces with whatever you typed after the keyword.
Now typing sofa barcelona in the address bar navigates to https://www.sofascore.com/pl/?q=barcelona. Sofascore itself ignores the ?q=; the userscript picks it up from there.
2. Userscript: turn ?q= into a real search
Install Tampermonkey, then add this script:
// ==UserScript==
// @name Websearch for Sofascore
// @namespace https://digit11.com/
// @version 1.0.0
// @description Forward ?q= search terms into Sofascore's search input via Real Type
// @author kkujawinski
// @match https://www.sofascore.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=sofascore.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
const TYPING_SERVER_URL = 'http://localhost:8765';
async function realType(text, options = {}) {
const {
delay = 2,
focusFirst = true
} = options;
// Focus the current element if requested
if (focusFirst && document.activeElement) {
document.activeElement.focus();
}
// Small delay to ensure focus
await new Promise(resolve => setTimeout(resolve, 100));
// Send to macOS typing service
try {
const response = await fetch(TYPING_SERVER_URL + '/type', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text, delay })
});
if (!response.ok) {
throw new Error(`Server responded with ${response.status}`);
}
const result = await response.json();
console.log('Typing completed:', result);
return result;
} catch (error) {
console.error('Failed to connect to typing service:', error);
console.log('Make sure the macOS typing service is running on port 8765');
throw error;
}
}
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name) || '';
}
const searchValue = getQueryParam('q').replace(/\/+$/, '');
if (searchValue) {
(async function simulateFocusClickLoop() {
let input;
let maxAttempts = 50;
while (!(input = document.querySelector('input#search-input'))) {
maxAttempts--;
if (maxAttempts === 0) {
break;
}
await new Promise(r => setTimeout(r, 10));
}
maxAttempts = 100;
while (!(document.querySelector('.z_dropdown input[type="radio"][name="all"]'))) {
maxAttempts--;
if (maxAttempts === 0) {
break;
}
console.log('focusing...');
input.focus();
input.click();
await new Promise(r => setTimeout(r, 10));
}
await new Promise(r => setTimeout(r, 30));
await realType(searchValue);
// input.value = searchValue;
// input.dispatchEvent(new InputEvent('input', { bubbles: true }));
})();
}
})();
The script:
- Reads the
qquery parameter (with a trailing-slash guard for browsers that append one). - Polls for
input#search-inputto appear, then focuses and clicks it in a loop until the search dropdown (.z_dropdown input[type="radio"][name="all"]) shows up — Sofascore's signal that the input is ready to accept keystrokes. - Calls
realType(searchValue), which forwards the query to a local typing server.
Why not just input.value = searchValue? That's the commented-out line at the bottom — it's what you reach for first, and it doesn't work. React's controlled-input handlers ignore the direct assignment, and dispatching a synthetic InputEvent gets filtered out too. The search input stays empty.
The typing server exists for exactly this reason. That's the last piece.
3. Real Type — typing like a real keyboard
Repo: kkujawinski/real-type-server
The AppleScript doing the work
The core is a handful of AppleScript lines driving System Events:
tell application "System Events"
repeat with i from 1 to length of textToType
set currentChar to character i of textToType
keystroke currentChar
delay charDelay
end repeat
end tell
keystroke is the important bit. Unlike pasting or setting a field's value, it emits events indistinguishable from a real keyboard. The target app sees key-down / key-up events arriving at human pace, so input validators, focus handlers — all of it just works. Sofascore's React input included.
The standalone macos-typer.applescript file wraps this loop so you can invoke it directly:
osascript macos-typer.applescript "hello world" 50
A tiny Python server in front
To make the typer callable from a browser userscript — or a shell alias, or another machine on localhost — there's a thin Python HTTP server (osascript-typing-server.py) on port 8765. It's http.server from the stdlib, no dependencies.
It accepts one route, POST /type, reads { "text", "delay" } from the JSON body, escapes the text for AppleScript, shells out to osascript -e …, and returns a JSON status. You can call it straight from a webpage — exactly what the userscript does.
curl -X POST http://localhost:8765/type \
-H 'Content-Type: application/json' \
-d '{"text": "Hello, world!", "delay": 50}'
That's the whole server. The value isn't in the Python — it's in giving the AppleScript a stable local endpoint.
Autostart at login
install-autostart.sh writes a LaunchAgent plist to ~/Library/LaunchAgents/ and launchctl loads it. From then on the server boots with your user session, restarts automatically if it crashes, and logs to /tmp/osascript-typing-server.log. One-time setup: grant Accessibility permission to the Python binary the installer reports, otherwise keystroke silently no-ops.
uninstall-autostart.sh and check-status.sh handle the rest of the lifecycle.
Full source, install scripts, and the LaunchAgent plist are in the repo.
Putting it together
With the three pieces in place, the flow is:
- Type
sofa barcelonain the address bar, hit Enter. - Browser navigates to
https://www.sofascore.com/pl/?q=barcelona. - Userscript waits for the search input to be ready, then hands the query off to the typing server.
- Real Type types
barcelonainto the focused field at human pace. - Sofascore's search dropdown fills with live results.
The same pattern works for any site whose search resists synthetic input — swap the selector and the keyword URL, and you've got another shortcut.
Top comments (0)