Created
December 18, 2025 05:20
-
-
Save bdevel/8d9817575f83c4fe6d6ca9b52658f6d2 to your computer and use it in GitHub Desktop.
MeshCore repeater available public key prefix finder
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 ruby | |
| # Prints un-used two letter public key hex prefixes | |
| # for new repeater nodes within a given radius for a location. | |
| # | |
| # This allows for better trace display without repeating hex codes. | |
| # | |
| # After finding a prefix, generate a key pair here: | |
| # https://gessaman.com/mc-keygen/ | |
| require 'json' | |
| require 'date' | |
| # Constants | |
| # Seattle: 47.6068836 -122.3260829 | |
| LOC_LAT = 47.6068836 | |
| LOC_LON = -122.3260829 | |
| RADIUS_MILES = 20 | |
| MAX_AGE_DAYS = 30 # Filter out items not updated within this many days | |
| # Haversine formula to calculate distance between two GPS coordinates in miles | |
| def haversine_miles(lat1, lon1, lat2, lon2) | |
| rad_per_deg = Math::PI / 180 | |
| earth_radius_miles = 3959.0 | |
| dlat = (lat2 - lat1) * rad_per_deg | |
| dlon = (lon2 - lon1) * rad_per_deg | |
| a = Math.sin(dlat / 2)**2 + | |
| Math.cos(lat1 * rad_per_deg) * Math.cos(lat2 * rad_per_deg) * | |
| Math.sin(dlon / 2)**2 | |
| c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) | |
| earth_radius_miles * c | |
| end | |
| require 'net/http' | |
| require 'json' | |
| filename = 'meshcore-nodes.json' | |
| unless File.exist?(filename) | |
| uri = URI('https://map.meshcore.dev/api/v1/nodes') | |
| File.write(filename, Net::HTTP.get(uri)) | |
| puts "Downloaded #{filename}" | |
| else | |
| puts "#{filename} already exists, skipping download" | |
| end | |
| json_data = JSON.parse(File.read(filename)) | |
| cutoff_date = DateTime.now - MAX_AGE_DAYS | |
| near = json_data | |
| .select { |node| node['type'] == 2 } # Only repeaters | |
| .reject do |node| | |
| # Reject if within 20 miles of location | |
| lat = node['adv_lat'] | |
| lon = node['adv_lon'] | |
| d = lat && lon && haversine_miles(LOC_LAT, LOC_LON, lat, lon) | |
| node['distance'] = d | |
| d > RADIUS_MILES | |
| end | |
| .reject do |node| | |
| # Reject old items (not updated recently) | |
| updated = node['updated_date'] | |
| updated.nil? || DateTime.parse(updated) < cutoff_date | |
| end | |
| .sort_by{|node| node['distance']} | |
| prefixes = near.map { |node| node['public_key'][0, 2] }.sort # First two chars of public key | |
| # dump nodes | |
| # near.each do |n| | |
| # h = n.slice("adv_name", "params", "updated_date", "distance") | |
| # params = h.delete "params" | |
| # h['freq'] = params['freq'] | |
| # puts h.values.join("; ") | |
| # end | |
| hex = [0,1,2,3,4,5,6,7,8,9] + %w{a b c d e f } | |
| all_prefixes = hex.map{|a| hex.map{|b| a.to_s + b.to_s}}.flatten | |
| unseen = all_prefixes - prefixes | |
| puts unseen |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment