poit-puller (0.2.0)

Published 2026-09-14 16:18:01 +00:00 by niro

Installation

[registries.forgejo]
index = "sparse+" # Sparse index
# index = "" # Git

[net]
git-fetch-with-cli = true
cargo add poit-puller@0.2.0 --registry forgejo

About this package

Poller for POIT

POIT Puller

A Rust client for listing and fetching Kallelser from Post- och Inrikes Tidningar (POIT).

Status

The client can:

  • list publications with amnesomradeId=1 for a date range;
  • fetch an individual publication by kungorelseid;
  • decode POIT's version 5 response format into JSON.

Persistence and scheduled polling still need to be implemented.

Requirements

  • A recent stable Rust toolchain

Install Rust with rustup if needed.

Library usage

use poit_puller::{PoitClient, Result};

async fn fetch() -> Result<()> {
    let client = PoitClient::new()?;
    let listings = client
        .list_kallelser("2026-09-14", "2026-09-14")
        .await?;
    let notice = client.fetch_announcement(&listings[0].kungorelseid).await?;
    println!("{notice:#}");
    Ok(())
}

Rate-limited polling

Fetch an iterator of IDs sequentially with caller-defined random spacing:

use std::time::Duration;

let notices = client
    .poll_announcements(
        ids,
        Duration::from_secs(10)..=Duration::from_secs(30),
    )
    .await?;

The first request runs immediately; subsequent requests wait for a random duration in the inclusive range. Polling stops on the first error so a security challenge or throttling response is not repeatedly retried. Random spacing cannot guarantee that POIT will allow the traffic—callers remain responsible for choosing a conservative rate and complying with the service's terms.

PDF support

Enable the optional pdf feature to convert kungorelseText to PDF:

let pdf: Vec<u8> = client.fetch_announcement_pdf("K621433/26").await?;
std::fs::write("K621433-26.pdf", pdf)?;

The targeted renderer supports the HTML elements emitted by POIT: paragraphs, bold text, line breaks, and ordered or unordered lists. It uses built-in PDF fonts and an all-Rust dependency stack—no browser, wkhtmltopdf, system font, or native library is required.

Examples

List Kallelser for one day or a date range:

cargo run --example list -- 2026-09-14
cargo run --example list -- 2026-09-14 2026-09-16

Fetch and decode an individual publication:

cargo run --example fetch -- 'K621433/26'

The examples write pretty-printed JSON to standard output. Poll all IDs listed for a date, using required minimum and maximum delays in seconds:

cargo run --example poll -- 2026-09-14 10 30

Generate a PDF with:

cargo run --features pdf --example pdf -- 'K621433/26' output.pdf

How individual publications are decoded

The client follows the current POIT web application:

  1. Request a coupling token from HamtaKoppling using the same HTTP session.
  2. Request HamtaKungorelse with the publication ID, coupling token, and version=5.
  3. Reverse POIT's letter-case and digit transformation.
  4. Base64-decode the response.
  5. Derive a 32-byte state from SHA-256(kungorelseid + koppling).
  6. Apply POIT's mutable XOR routine and parse the resulting JSON.

Coupling token

HamtaKoppling returns a short-lived token that links a detail request to the current HTTP session. POIT also uses it when encoding the response: the decoder key state is derived from SHA-256(kungorelseid + koppling). The token and cookies must therefore be fetched dynamically and reused by the same client.

The client sends browser-like request headers because POIT otherwise returns its security challenge instead of API data.

Development

cargo fmt --check
cargo test
cargo clippy --all-targets --all-features

Dependencies

Dependencies

ID Version
base64 ^0.23.1
html-escape ^0.2.15
printpdf ^0.12.8
rand ^0.10.2
reqwest ^0.13.5
serde ^1.0.229
serde_json ^1.0.151
sha2 ^0.11.0
tokio ^1.53.1
Details
Cargo
2026-09-14 16:18:01 +00:00
2
MIT
582 KiB
Assets (1)
Versions (3) View all
0.3.0 2026-10-07
0.2.0 2026-09-14
0.1.0 2026-09-14