Guise
Session-sticky User-Agent rotation with modern, refreshable browser profiles.
Rotating a fresh random UA on every request is a tell — real clients keep
one identity for a while. Guise assigns one realistic browser profile
(User-Agent + matching Accept-Language) per session key and keeps it
sticky for 30 minutes, so your traffic looks like a handful of real
browsers rather than a random-UA firehose.
def deps do
[{:guise, "~> 0.1"}]
end
Usage
# Random, share-weighted UA string
Guise.random()
#=> "Mozilla/5.0 (Windows NT 10.0; Win64; x64) … Chrome/150.0.0.0 Safari/537.36"
# Headers for a one-off request
Guise.headers("https://example.com/")
#=> [{"user-agent", "…"}, {"referer", "https://example.com/"}]
# Sticky headers for a session — same key → same browser identity for 30 min
Guise.headers("https://example.com/", {user_id, "example.com"})
#=> [{"user-agent", "…"}, {"accept-language", "en-US,en;q=0.9"}, {"referer", "https://example.com/"}]
Guise starts its own supervised process (an ETS-backed session store), so there's nothing to add to your supervision tree.
The pool
A modern UA string is a stable template plus one volatile part — the
browser's major version (Chrome froze everything below the major to
.0.0.0 years ago). Guise renders its pool from templates (engine × OS ×
weight) against a small version table, so the whole pool modernizes when a
handful of numbers do — and can't produce an inconsistent string.
The default pool spans desktop (Chrome/Firefox/Edge/Safari across Windows, macOS and Linux) and mobile (Chrome on Android, Safari on iPhone and iPad), share-weighted so Chrome-heavy rotation looks like real traffic.
Keeping it fresh
mix guise.refresh
Pulls the current stable Chrome and Firefox majors from
endoflife.date and rewrites priv/versions.json.
Edge is derived from Chrome (Chromium tracks it); Safari has no clean feed
so it's maintained by hand. The runtime never hits the network — it only
reads the committed file at boot. (guise.refresh uses OTP's built-in
:httpc, so the package itself adds no HTTP dependency.)
Configuration
Pin or override individual versions:
config :guise, Guise,
versions: %{"chrome" => "150.0.0.0"}
Or replace the pool wholesale with your own profiles — each a map with a
required :ua and optional :accept_language (default en-US) and
:weight (default 1); a bare string is taken as the UA. When :profiles
is set it wins over the built-ins and :versions is ignored:
config :guise, Guise,
profiles: [
%{ua: "MyBot/1.0 (+https://example.com/bot)", weight: 3},
"Mozilla/5.0 (…) Chrome/150.0.0.0 Safari/537.36"
]
License
MIT © Alexander Don