Created
March 9, 2026 15:43
-
-
Save vil/b61f04c4df6b0d25c10c1fc87f4412c6 to your computer and use it in GitHub Desktop.
Simple Shell script to look up Finnish phone numbers/persons from the Fonecta.fi API endpoint, note that the data might be partly redacted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Simple Fonecta person/phone number lookup (scrapes the site JSON endpoint) | |
| # Usage: ./fonecta-lookup.sh "Mikko Mallikas" OR ./fonecta-lookup.sh "0401234567" | |
| # ./fonecta-lookup.sh -v "Mikko Mallikas" (verbose mode) | |
| # Made by Vili <https://vili.dev> | |
| set -euo pipefail | |
| VERBOSE=0 | |
| # Parse command line arguments | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -v|--verbose) | |
| VERBOSE=1 | |
| shift | |
| ;; | |
| -h|--help) | |
| echo "Usage: $0 [-v|--verbose] \"Search Name or Phone Number\"" | |
| echo "" | |
| echo "Examples:" | |
| echo " $0 \"Mikko Mallikas\"" | |
| echo " $0 \"0401234567\"" | |
| echo " $0 -v \"Mikko Mallikas\"" | |
| echo "" | |
| echo "Options:" | |
| echo " -v, --verbose Enable verbose output for debugging" | |
| echo " -h, --help Show this help message" | |
| exit 0 | |
| ;; | |
| *) | |
| break | |
| ;; | |
| esac | |
| done | |
| if [ $# -lt 1 ]; then | |
| echo "Usage: $0 [-v|--verbose] \"Search Name or Phone Number\"" >&2 | |
| echo "Try '$0 --help' for more information" >&2 | |
| exit 2 | |
| fi | |
| query="$*" | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Query: $query" >&2 | |
| echo "[DEBUG] Query length: ${#query}" >&2 | |
| fi | |
| # Detect if query is a phone number (Finnish format) | |
| if [[ $query =~ ^[0-9\ \-\+]+$ ]]; then | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Detected as phone number format" >&2 | |
| fi | |
| fi | |
| # URL-encode the query | |
| url_query=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$query") | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] URL-encoded query: $url_query" >&2 | |
| fi | |
| # Fetch the search page HTML | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Fetching search page..." >&2 | |
| fi | |
| html=$(curl -sS "https://www.fonecta.fi/fi/haku/${url_query}" \ | |
| -H "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36" \ | |
| 2>&1 || { echo "Fetch failed"; exit 3; }) | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Page fetch completed (length: ${#html} bytes)" >&2 | |
| fi | |
| # Extract the __NEXT_DATA__ JSON from the HTML | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Extracting __NEXT_DATA__ from HTML..." >&2 | |
| fi | |
| # Find and extract the JSON from the __NEXT_DATA__ script tag | |
| json=$(echo "$html" | grep -oP '__NEXT_DATA__" type="application/json">\K\{.*\}(?=</script>)' || true) | |
| if [ -z "$json" ]; then | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Could not extract __NEXT_DATA__ from page" >&2 | |
| echo "[DEBUG] Page preview (first 1000 chars):" >&2 | |
| echo "${html:0:1000}" >&2 | |
| fi | |
| echo "Error: Could not extract data from page" >&2 | |
| exit 5 | |
| fi | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] __NEXT_DATA__ extracted (length: ${#json} bytes)" >&2 | |
| echo "[DEBUG] First 500 chars of JSON:" >&2 | |
| echo "${json:0:500}" >&2 | |
| fi | |
| # Check if jq is available | |
| if ! command -v jq >/dev/null 2>&1; then | |
| echo "jq is required. Install it and retry." >&2 | |
| exit 4 | |
| fi | |
| # Validate JSON | |
| if ! echo "$json" | jq empty 2>/dev/null; then | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Invalid JSON" >&2 | |
| echo "[DEBUG] Full JSON:" >&2 | |
| echo "$json" >&2 | |
| fi | |
| echo "Error: Invalid JSON data" >&2 | |
| exit 5 | |
| fi | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] Parsing results with jq..." >&2 | |
| fi | |
| # Extract and format results from the dehydrated state | |
| result=$(echo "$json" | jq -r ' | |
| .props.pageProps.dehydratedState.queries[] | |
| | select(.queryKey[0]=="search" and (.state.data.results != null)) | |
| | .state.data.results | |
| | map({ | |
| id: .id, | |
| contactType: .contactType, | |
| name: .name, | |
| cityName: .cityName, | |
| teaser: .teaser, | |
| hasOwnerName: .hasOwnerName, | |
| hasCompanyUrl: .hasCompanyUrl, | |
| hasVisibility: .hasVisibility, | |
| distinctiveSearchResult: .distinctiveSearchResult, | |
| hasAddress: .hasAddress, | |
| hasOperatorName: .hasOperatorName, | |
| provinceName: .provinceName, | |
| phone: .phone, | |
| mobile: .mobile | |
| }) | {results: .} | |
| ' 2>&1) || { | |
| if [ $VERBOSE -eq 1 ]; then | |
| echo "[DEBUG] JQ parsing failed with error: $result" >&2 | |
| echo "[DEBUG] Checking JSON structure..." >&2 | |
| echo "$json" | jq 'keys' >&2 2>/dev/null || true | |
| echo "[DEBUG] Checking props.pageProps..." >&2 | |
| echo "$json" | jq '.props.pageProps | keys' 2>/dev/null || true | |
| echo "[DEBUG] Checking dehydratedState.queries..." >&2 | |
| echo "$json" | jq '.props.pageProps.dehydratedState.queries | length' 2>/dev/null || true | |
| fi | |
| echo "Parsing failed" >&2 | |
| exit 5 | |
| } | |
| echo "$result" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment