,

Pakkesporing med et script, cron og Discord-varsel

Pakkesporing med et script, cron og Discord-varsel

Du venter på en pakke, og du gidder ikke å laste sporingssiden om og om igjen som en nervøs katt. Transportøren har et status-API. Da kan du sette et lite script til å gjøre kjedejobben: sjekk med jevne mellomrom, oppdag når noe endrer seg, send et varsel. Så er du ferdig.

Mønsteret

Skjematisk flytdiagram: cron utløser scriptet, som poller transportørens status-API, sammenligner mot tilstandsfilen, og sender varsel til Discord kun ved endring

Det samme mønsteret dukker opp overalt der noe eksponerer en status: pakkesporing, en byggejobb i CI, en ordre, en sensor på loftet. Du poller periodisk, sammenligner mot forrige verdi, og varsler bare når noe faktisk har endret seg. Det henger sammen av tre deler:

  • En tilstandsfil som husker siste kjente status per pakke.
  • Et diff-steg som holder det nye svaret opp mot det som står i filen.
  • En varselkanal. Her bruker jeg en Discord-webhook.

Det kritiske her er idempotens. Kjører scriptet hver tredje time, vil du ikke ha det samme varselet dundrende inn fire ganger på rad. Regelen er grei nok: varsle kun ved reell endring, og skriv den nye statusen til filen etter at varselet er ute.

Tilstandsfilen

En enkel JSON-fil holder rede på siste status per sporingsnummer. Ingen database, ingen tabeller, ingenting å drifte.

{
  "NO123456789": "Sendt fra terminal",
  "NO987654321": "Levert"
}

Scriptet

Gangen er den samme hver gang: poll API-et, bygg opp den nye tilstanden, diff mot den lagrede, send varsel på det som har endret seg, og lagre helt til slutt.

import json, os, requests

STATE = os.path.expanduser("~/pakker/state.json")
WEBHOOK = os.environ["DISCORD_WEBHOOK"]
PAKKER = ["NO123456789", "NO987654321"]

def hent_status(nr):
    r = requests.get(f"https://api.transportoer.example/track/{nr}", timeout=30)
    r.raise_for_status()
    return r.json()["events"][0]["status"]

def varsle(tekst):
    requests.post(WEBHOOK, json={"content": tekst}, timeout=30)

gammel = json.load(open(STATE)) if os.path.exists(STATE) else {}
ny = {}
for nr in PAKKER:
    try:
        ny[nr] = hent_status(nr)
    except Exception:
        ny[nr] = gammel.get(nr)  # behold forrige ved feil

for nr, status in ny.items():
    if status and status != gammel.get(nr):
        varsle(f"Pakke {nr}: {status}")

json.dump(ny, open(STATE, "w"), ensure_ascii=False, indent=2)

Legg merke til feilhåndteringen. Feiler et kall, beholder vi forrige status i stedet for å skrive None over den. Uten det ville en kortvarig API-hikke gitt deg et falskt varsel ved neste vellykkede kjøring, og da er vi like langt.

Cron

Skjematisk tidslinje: scriptet kjøres hver tredje time gjennom døgnet, med markerte kjøretidspunkter og idempotent tilstandssjekk mellom hver kjøring

Hver tredje time holder i massevis. Oftere er bortkastet: transportøren oppdaterer uansett ikke i sanntid, og du slipper å mase på API-et med kall som ikke gir deg noe nytt.

0 */3 * * * DISCORD_WEBHOOK=https://discord.com/api/webhooks/xxx \
  /usr/bin/python3 /home/bruker/pakker/track.py >> /home/bruker/pakker/track.log 2>&1

Oppsummering

Rundt 25 linjer kode og én linje cron. Ingen app, ingen server, ingen push-tjeneste å abonnere på. Poenget er egentlig ikke pakkesporing i seg selv, men mønsteret bak: har noe et status-API, kan du polle det, diffe mot forrige tilstand og varsle på endring. Hold det idempotent, så plager det deg aldri med duplikater.


Trenger du hjelp med dette?

Ta kontakt for en uforpliktende prat om hvordan jeg kan hjelpe deg.



Navn