Skip to content

Instantly share code, notes, and snippets.

@bdevel
Created December 18, 2025 05:20
Show Gist options
  • Select an option

  • Save bdevel/8d9817575f83c4fe6d6ca9b52658f6d2 to your computer and use it in GitHub Desktop.

Select an option

Save bdevel/8d9817575f83c4fe6d6ca9b52658f6d2 to your computer and use it in GitHub Desktop.
MeshCore repeater available public key prefix finder
#!/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