Sign inSign up

psyb0t/planesnitch

By psyb0t

β€’Updated 2 months ago

Image
0

8.3K

psyb0t/planesnitch repository overview

β πŸ›©οΈ planesnitch

CI version license Docker Pulls

Snitches on every interesting aircraft that dares fly near your locations β€” military jets, government spooks, emergency squawks, sketchy low-flyers, or whatever the fuck you tell it to watch for. Monitor multiple locations at once β€” your house, your office, grandma's house, Area 51, whatever. Rats them out straight to your Telegram or webhook like a paranoid neighbor with radar.

No SDR required. No antenna. No hardware. Just an internet connection and a config file. Alerts via Telegram and/or webhooks. Works anywhere on the fuckin planet β€” and for as many places as you want.

⁠Table of Contents

β πŸš€ Quick Start

# grab the example config and edit it
# with your location + notification settings
curl -sL \
  https://raw.githubusercontent.com/psyb0t/docker-planesnitch/main/config.yaml.example \
  -o config.yaml

# optional: download CSV watchlists for
# military/gov/police tracking
# (see Plane-Alert-DB section below)
# mkdir -p csv
# BASE=https://raw.githubusercontent.com/sdr-enthusiasts/plane-alert-db/main
# curl -sLo csv/plane-alert-mil.csv $BASE/plane-alert-mil.csv
# curl -sLo csv/plane-alert-gov.csv $BASE/plane-alert-gov.csv
# curl -sLo csv/plane-alert-pol.csv $BASE/plane-alert-pol.csv
# curl -sLo csv/plane-alert-civ.csv $BASE/plane-alert-civ.csv
# curl -sLo csv/plane-alert-pia.csv $BASE/plane-alert-pia.csv
# curl -sLo csv/plane-alert-db.csv  $BASE/plane-alert-db.csv

# let it rip β€” without CSV watchlists
docker run \
  -v ./config.yaml:/app/config.yaml:ro \
  psyb0t/planesnitch

# or with CSV watchlists β€” mount your csv/ dir
docker run \
  -v ./config.yaml:/app/config.yaml:ro \
  -v ./csv:/csv:ro \
  psyb0t/planesnitch

# full setup with persistent aircraft-type image cache
docker run \
  -v ./config.yaml:/app/config.yaml:ro \
  -v ./csv:/csv:ro \
  -v ./images:/images \
  psyb0t/planesnitch
2026-03-07 22:25:39 [planesnitch] INFO planesnitch starting
2026-03-07 22:25:39 [planesnitch] INFO health endpoint listening on :8080
2026-03-07 22:25:40 [planesnitch] INFO watchlist military: 8709 aircraft loaded
2026-03-07 22:25:40 [planesnitch] INFO watchlist government: 1743 aircraft loaded
2026-03-07 22:25:40 [planesnitch] INFO fetched 14 aircraft
2026-03-07 22:25:40 [planesnitch] INFO ALERT [Military Spotter] [home] ae07e1 TEDDY64

β βš™οΈ Configuration

Single YAML file. Define watchlists (what to snitch on), alert rules (when to lose your shit), and notification targets (where to scream about it).

⁠Display Units

Control how altitude, distance, and speed show up in alerts and webhook payloads:

display_units: aviation # default
PresetAltitudeDistanceSpeed
aviationftnmkts
metricmkmkm/h
imperialftmimph
⁠Locations

Define one or more named locations. Each location has coordinates and a search radius.

Units: Any distance or altitude value in the config accepts a unit suffix: km, mi, nm, ft, m. They all convert internally β€” use whatever makes sense. radius: 100mi and max_altitude: 1km both work. Plain numbers without a suffix default to km for distances and ft for altitudes.

locations:
  home:
    name: "Home"
    lat: 38.8719
    lon: -77.0563
    radius: 150km
  area51:
    name: "Area 51"
    lat: 37.2350
    lon: -115.8111
    radius: 50nm

Each location can have an optional name for pretty display in alerts. Falls back to the key if not set.

⁠Sources

Where to get the goods. Use multiple sources β€” they fetch in parallel for each location and deduplicate by ICAO hex, keeping the entry with the most data.

Note: adsb_fi, airplanes_live, and adsb_one return enriched data (full aircraft name, owner/operator, year). adsb_lol and ultrafeeder only return raw ADS-B fields. When using multiple sources, planesnitch automatically keeps the richest entry for each aircraft.

sources:
  # Public APIs β€” no hardware, no bullshit
  - type: adsb_lol
  - type: adsb_fi
  - type: airplanes_live
  - type: adsb_one

  # Local ultrafeeder β€” if you're running your own receiver
  - type: ultrafeeder
    url: http://ultrafeeder:80/tar1090/data/aircraft.json
⁠Watchlists

Tell the snitch what to look for. All types respect the location's radius β€” aircraft outside the radius are ignored regardless of type.

TypeMatches OnSource
allEvery aircraftEverything within the location's radius
squawkTransponder squawk codeInline list
icaoICAO hex addressInline list
icao_typeICAO type designator (doc 8643⁠) e.g. C17, B738Inline list
icao_csvICAO hex from CSVLocal file in csv/ dir (plane-alert-db⁠ format)
proximityAltitude filterLocation radius + altitude limits
watchlists:
  # The panic buttons
  emergencies:
    type: squawk
    values: ["7500", "7600", "7700"]

  # 8,709 military aircraft β€” the big boys
  military:
    type: icao_csv
    source: plane-alert-mil.csv

  # Government aircraft
  government:
    type: icao_csv
    source: plane-alert-gov.csv

  # Police / law enforcement
  police:
    type: icao_csv
    source: plane-alert-pol.csv

  # Stalk specific aircraft by hex
  my_planes:
    type: icao
    values: ["4ca123", "a12345"]

  # Stalk by aircraft type β€” any A400M, Rafale, or Alpha Jet
  cool_jets:
    type: icao_type
    values: ["A400", "RFAL", "AJET"]

  # Every single aircraft in range
  everything:
    type: all

  # WTF just buzzed my house
  low_flyers:
    type: proximity
    min_altitude: 0ft
    max_altitude: 3000ft
⁠Alerts

Connect watchlists to notifications. Optionally filter by locations β€” if omitted, all locations are checked. Cooldown so it doesn't spam the shit out of you about the same C-17 doing laps for 3 hours. Durations support s, m, h β€” e.g. 5m, 1h30m, 90s, or plain seconds:

alerts:
  - name: "Emergency Alert"
    watchlists: [emergencies]
    cooldown: 1m
    notify: [tg_emergencies, my_webhook]

  - name: "Military Spotter"
    watchlists: [military, government]
    cooldown: 5m
    notify: [tg_spotting]

  # Only alert for this watchlist at specific locations
  - name: "Everything at Home"
    locations: [home]
    watchlists: [everything]
    cooldown: 1m
    notify: [tg_main]
⁠Notifications

Telegram β€” different alerts to different channels:

notifications:
  tg_emergencies:
    type: telegram
    bot_token: "123456:ABC-DEF"
    chat_id: "-100123456789"

  tg_spotting:
    type: telegram
    bot_token: "123456:ABC-DEF"
    chat_id: "-100987654321"

When an aircraft has an ICAO type designator (field t), planesnitch fetches the matching aircraft image from doc8643.com⁠ and:

  • attaches it to the Telegram message as a photo (caption = the alert text)
  • embeds the JPEG bytes (base64) in webhook payloads as image_base64

Images are cached in images/ (mount -v ./images:/images to persist across restarts). Misses are recorded as .notfound markers so types without images aren't re-fetched. The cache survives indefinitely β€” delete the dir to force a refresh.

To opt out per notification target (e.g. avoid blowing up a chat with photos, or cut webhook payload size on Home Assistant), set attach_image: false on the target. Default is true. If every target attached to a rule opts out, planesnitch skips the doc8643 fetch entirely for that alert β€” no wasted bandwidth or disk.

notifications:
  tg_main:
    type: telegram
    bot_token: "..."
    chat_id: "..."
    attach_image: false   # text-only alerts to this chat

Webhook β€” POSTs a JSON array of alert objects per poll cycle:

notifications:
  my_webhook:
    type: webhook
    url: "https://example.com/hook"
    headers:
      Authorization: "Bearer xxx"

Webhook payload schema β€” always a JSON array, even for a single alert:

[
  {
    "alert": "Military Spotter",
    "location": "Home",
    "match": {
      "reason": "icao_csv_match",
      "watchlist": "military",
      "info": {
        "Registration": "94-0067",
        "Operator": "USAF",
        "Type": "BOEING C-17A Globemaster III",
        "ICAO Type": "C17",
        "CMPG": "Mil",
        "Tag 1": "Cargo",
        "Tag 2": "Strategic Airlift",
        "Tag 3": "Freedom Delivery",
        "Category": "US Military",
        "Link": "https://w.wiki/..."
      }
    },
    "units": {
      "altitude": "ft",
      "distance": "nm",
      "speed": "kts"
    },
    "image_base64": "/9j/4AAQSkZJRgABAQ...",  // null if no image cached
    "aircraft": {
      "hex": "ae07e1",
      "flight": "TEDDY64",
      "registration": "94-0067",
      "type": "C17",
      "description": "BOEING C-17A Globemaster III",
      "owner_operator": "USAF",
      "year": "1994",
      "squawk": "1613",
      "emergency": "none",
      "altitude": 12350,
      "lat": 37.9306,
      "lon": -78.7019,
      "speed": 413,
      "track": 245.3,
      "distance": 98.9
    }
  }
]

The match object varies by watchlist type:

Watchlist TypeMatch Fields
squawk{"reason": "squawk", "watchlist": "...", "squawk": "7700", "distance_km": 12.3}
icao{"reason": "icao_match", "watchlist": "...", "distance_km": 12.3}
icao_type{"reason": "icao_type_match", "watchlist": "...", "type": "C17", "distance_km": 12.3}
icao_csv{"reason": "icao_csv_match", "watchlist": "...", "info": {"Operator": "..."}, "distance_km": 12.3}
all{"reason": "all", "watchlist": "...", "distance_km": 12.3}
proximity{"reason": "proximity", "watchlist": "...", "distance_km": 12.3}
⁠What the alerts look like
πŸ”” Emergency Alert
🚨 squawk 7700 (EMERGENCY)
✈️ RYR1234
πŸ›©οΈ BOEING 737-800 | EI-ABC | 2015
πŸ’Ό RYANAIR
πŸ“ 45.5000, 28.1000 | 3,200 ft
πŸ“ 6 nm from home
πŸ’¨ 280 kts
πŸ—ΊοΈ https://globe.adsb.fi/?icao=4ca123
πŸ”” Military Spotter
✈️ TEDDY64
πŸ›©οΈ BOEING C-17A Globemaster III | 94-0067 | 1994
πŸ’Ό USAF
🏷️ USAF β€” USAF
πŸ“ 37.9306, -78.7019 | 12,350 ft
πŸ“ 99 nm from home
πŸ’¨ 413 kts
πŸ“‘ squawk 1613
πŸ—ΊοΈ https://globe.adsb.fi/?icao=ae07e1

Click the link, watch the bastard in real time on globe.adsb.fi⁠.

β πŸ€– Telegram Setup

  1. Message @BotFather⁠, send /newbot, get a token
  2. For personal alerts: message your bot, then check https://api.telegram.org/bot<TOKEN>/getUpdates for your chat ID
  3. For channel alerts: add bot as admin, post something, check getUpdates for the channel ID (starts with -100)

β πŸ—ƒοΈ Plane-Alert-DB Lists

Uses the community-curated lists from sdr-enthusiasts/plane-alert-db⁠ β€” 15,000+ aircraft catalogued by the fine degenerates of the plane spotting community:

ListCountFile
πŸŽ–οΈ Military8,709plane-alert-mil.csv⁠
πŸ›οΈ Government1,743plane-alert-gov.csv⁠
πŸš” Police932plane-alert-pol.csv⁠
✈️ Civilian4,530plane-alert-civ.csv⁠
πŸ”’ Privacy (PIA)94plane-alert-pia.csv⁠
πŸ“‹ Everything15,914plane-alert-db.csv⁠

Download what you need into your csv/ directory:

mkdir -p csv
BASE=https://raw.githubusercontent.com/sdr-enthusiasts/plane-alert-db/main
curl -sLo csv/plane-alert-mil.csv $BASE/plane-alert-mil.csv
curl -sLo csv/plane-alert-gov.csv $BASE/plane-alert-gov.csv
curl -sLo csv/plane-alert-pol.csv $BASE/plane-alert-pol.csv
curl -sLo csv/plane-alert-civ.csv $BASE/plane-alert-civ.csv
curl -sLo csv/plane-alert-pia.csv $BASE/plane-alert-pia.csv
curl -sLo csv/plane-alert-db.csv  $BASE/plane-alert-db.csv

plane-alert-db.csv contains everything (mil + gov + pol + civ + pia) in one file. If you just want to watch all 15,000+ aircraft, use that one and skip the rest.

Re-download whenever you want fresh data. Or write your own CSV β€” just needs an ICAO hex column first.

⁠Agent integrations

The skill⁠ works in any agent that reads .agents/skills/, and installs natively in the clients below.

⁠Claude Code
claude plugin marketplace add psyb0t/agents
claude plugin install planesnitch@psyb0t
⁠Codex
codex plugin marketplace add psyb0t/agents
codex plugin add planesnitch@psyb0t

Installed via the marketplace, invoke it as $planesnitch:planesnitch. Codex also picks the skill up automatically with no install in any repo containing .agents/skills/, where it invokes as plain $planesnitch.

⁠OpenClaw

The skill is published to ClawHub on every release:

openclaw skills install @psyb0t/planesnitch

β πŸ“ Project Structure

β”œβ”€β”€ planesnitch/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ __main__.py       # entry point + main loop
β”‚   β”œβ”€β”€ config.py         # config loading, unit conversion, constants
β”‚   β”œβ”€β”€ sources.py        # ADS-B API fetching + dedup
β”‚   β”œβ”€β”€ geo.py            # distance calculations
β”‚   β”œβ”€β”€ watchlists.py     # watchlist loading + matching
β”‚   β”œβ”€β”€ alerts.py         # alert checking + cooldowns
β”‚   β”œβ”€β”€ images.py         # doc8643 aircraft type image caching
β”‚   └── notify.py         # telegram + webhook formatting + sending
β”œβ”€β”€ config.yaml.example   # example config β€” copy to config.yaml and fill in your shit
β”œβ”€β”€ csv/                  # CSV watchlists go here, mounted to /csv
β”œβ”€β”€ images/               # cached doc8643 type images, mounted to /images
β”œβ”€β”€ run.sh                # build + run in docker
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ Dockerfile
└── README.md

Not 47 microservices. It watches planes and sends messages. That's it.

Environment variables:

VariableDefaultDescription
LOG_LEVELINFOSet to DEBUG for verbose output
PLANESNITCH_CONFIGconfig.yamlPath to config file
PLANESNITCH_CSV_DIR/csvPath to CSV watchlist files
PLANESNITCH_IMAGES_DIR/imagesPath to cached doc8643 aircraft-type images

A health endpoint runs on port 8080. The Docker image includes a built-in healthcheck against it.

β πŸ“ License

WTFPL⁠ β€” Do What The Fuck You Want To.

Tag summary

Content type

Image

Digest

sha256:e696491a4…

Size

48.6 MB

Last updated

2 months ago

docker pull psyb0t/planesnitch