Search Sofascore from Your Browser's Address Bar
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 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 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. 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 q query parameter (with a trailing-slash guard for browsers that append one). Polls for input#search-input to 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. 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. With the three pieces in place, the flow is: Type sofa barcelona in 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 barcelona into 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.
