Liking cljdoc? Tell your friends :D

dk.simongray.podcast.analytics

The downloads of episodes, counted by the rules of the IAB Tech Lab from the requests that a podcast host logs.

The rules are those of the Podcast Measurement Technical Guidelines, version 2.3 of September 2026. They count the episodes of video too, when a feed delivers them as files.

A request is a map of one line of the log of a server:

{:time       #inst "2026-09-26T08:15:02Z"
 :ip         "203.0.113.5"                 ; or a hash of it
 :user-agent "Overcast/3.0 (+http://overcast.fm/; iOS podcast app)"
 :method     :get
 :url        "https://cdn.example.com/episode-54.mp3"
 :status     206
 :range      "bytes=0-"                     ; the Range header
 :bytes      1048576                         ; of the body that was sent
 :referer    nil}

The requests of a listener for a file in 24 hours count as one download when they hold enough of the file to play for one minute, and a listener is an IP address and a user agent. The requests that can't be part of a download are left out, such as HEAD requests and the requests of bots.

The rules need data from outside podcast-clj: the open list of user agents of the OPAWG, which names the apps and the bots, and lists of the addresses of data centers, VPNs and known bots from other sources.

The downloads of episodes, counted by the rules of the IAB Tech Lab from
the requests that a podcast host logs.

The rules are those of the Podcast Measurement Technical Guidelines,
version 2.3 of September 2026. They count the episodes of video too,
when a feed delivers them as files.

A request is a map of one line of the log of a server:

    {:time       #inst "2026-09-26T08:15:02Z"
     :ip         "203.0.113.5"                 ; or a hash of it
     :user-agent "Overcast/3.0 (+http://overcast.fm/; iOS podcast app)"
     :method     :get
     :url        "https://cdn.example.com/episode-54.mp3"
     :status     206
     :range      "bytes=0-"                     ; the Range header
     :bytes      1048576                         ; of the body that was sent
     :referer    nil}

The requests of a listener for a file in 24 hours count as one download
when they hold enough of the file to play for one minute, and a
listener is an IP address and a user agent. The requests that can't be
part of a download are left out, such as HEAD requests and the requests
of bots.

The rules need data from outside podcast-clj: the open list of user
agents of the OPAWG, which names the apps and the bots, and lists of the
addresses of data centers, VPNs and known bots from other sources.
raw docstring

downloadsclj/s

(downloads requests)
(downloads requests
           {:keys [threshold file-fn window]
            :or {file-fn (comp file-url :url) window :day}
            :as opts})

The downloads that requests make by the rules of the IAB with opts, and why the other requests don't count, as a map of :downloads and :dropped.

The requests of one listener for one file in one window of 24 hours make one download. They must hold at least the bytes of the threshold of the file. A download is the first of its requests, with the :listener that it counts for and the agent that its user agent names. The listener is a pair of the part of the address that counts and the user agent. The downloads come in the order of their first requests.

The :dropped map counts the requests that the filters leave out, by reason. Under :short, it counts the requests of listeners who fetched less than the threshold. The opts are these:

  • :user-agents, as user-agents gives them, to name the apps, the devices and the bots
  • :exclude-pred, a predicate of a request, for the addresses to leave out
  • :threshold, a function of a file that gives the bytes that a download needs, or a map of files to bytes
  • :file-fn, a function of a request that gives the file that it asks for, by default its URL without the query
  • :window, which is :day by default, :rolling or :extending

A window is a day in UTC. With :rolling, a window is the 24 hours from the first request of a listener for a file. With :extending, it's the 24 hours from the last request, so each request extends it. These are the three windows of the guidelines. For a file with no threshold, any request that passes the filters counts.

The requests of a listener hold the bytes that their ranges ask for, cut to the :bytes that the server sent. A byte counts once, whatever the number of requests that ask for it.

The downloads that `requests` make by the rules of the IAB with `opts`,
and why the other requests don't count, as a map of :downloads and
:dropped.

The requests of one listener for one file in one window of 24 hours make
one download. They must hold at least the bytes of the threshold of the
file. A download is the first of its requests, with the :listener that
it counts for and the agent that its user agent names. The listener is
a pair of the part of the address that counts and the user agent. The
downloads come in the order of their first requests.

The :dropped map counts the requests that the filters leave out, by
reason. Under :short, it counts the requests of listeners who fetched
less than the threshold. The `opts` are these:

- :user-agents, as user-agents gives them, to name the apps, the devices
  and the bots
- :exclude-pred, a predicate of a request, for the addresses to leave out
- :threshold, a function of a file that gives the bytes that a download
  needs, or a map of files to bytes
- :file-fn, a function of a request that gives the file that it asks for,
  by default its URL without the query
- :window, which is :day by default, :rolling or :extending

A window is a day in UTC. With :rolling, a window is the 24 hours from
the first request of a listener for a file. With :extending, it's the
24 hours from the last request, so each request extends it. These are
the three windows of the guidelines. For a file with no threshold, any
request that passes the filters counts.

The requests of a listener hold the bytes that their ranges ask for, cut
to the :bytes that the server sent. A byte counts once, whatever the
number of requests that ask for it.
sourceraw docstring

drop-reasonclj/s

(drop-reason request)
(drop-reason {:keys [method status range user-agent] :as request}
             {:keys [exclude-pred] :as opts})

Why the rules of the IAB leave out request by opts, as a keyword, or nil when it can be part of a download. These are the filters of the first step of the guidelines, in the order that they apply:

  • :malformed, a request with no instant as :time, or no :url or :ip, or with a field of another type than the type of that field
  • :method, a request that isn't GET, since HEAD moves no content
  • :status, an answer that isn't 200 or 206, or 000, which Akamai logs for a 206 that ended early. A request without a status passes, since a prefix service sees no answers.
  • :two-bytes, ranges of two bytes or fewer, which players ask for to learn whether a server sends ranges
  • :no-user-agent, a request with no user agent
  • :watch, a request of Apple Podcasts on a watch, which repeats the download of the phone
  • :bot, a request whose :agent-type is bot or bot-library. When the list doesn't know the agent, it's a user agent that says bot, crawler or spider.
  • :excluded, a request that the :exclude-pred of opts takes. It's for the addresses of servers, data centers, VPNs and known bots, from the lists of the host.
Why the rules of the IAB leave out `request` by `opts`, as a keyword, or
nil when it can be part of a download. These are the filters of the
first step of the guidelines, in the order that they apply:

- :malformed, a request with no instant as :time, or no :url or :ip, or
  with a field of another type than the type of that field
- :method, a request that isn't GET, since HEAD moves no content
- :status, an answer that isn't 200 or 206, or 000, which Akamai logs
  for a 206 that ended early. A request without a status passes, since
  a prefix service sees no answers.
- :two-bytes, ranges of two bytes or fewer, which players ask for to
  learn whether a server sends ranges
- :no-user-agent, a request with no user agent
- :watch, a request of Apple Podcasts on a watch, which repeats the
  download of the phone
- :bot, a request whose :agent-type is bot or bot-library. When the list
  doesn't know the agent, it's a user agent that says bot, crawler or
  spider.
- :excluded, a request that the :exclude-pred of `opts` takes.
  It's for the addresses of servers, data centers, VPNs and known bots,
  from the lists of the host.
sourceraw docstring

identifyclj/s

(identify user-agents user-agent)
(identify user-agents user-agent referer)

The agent that the header user-agent names by the list user-agents, with the keys that OP3 gives it, and the referrer of the header referer.

The result is a map of :agent-type, :agent-name, :device-type and :device-name. With the referer of a request from a browser, it also has :referrer-type and :referrer-name.

The agent type is the type of the file of the first entry that matches: bot, app, library or browser. A library that bots use is bot-library. A user agent that matches no entry is unknown, and its name is the header. The device comes from the devices of the list, except for a bot. The referrer comes from the referrers of the list, or else it's the domain of the referer.

The agent that the header `user-agent` names by the list `user-agents`,
with the keys that OP3 gives it, and the referrer of the header
`referer`.

The result is a map of :agent-type, :agent-name, :device-type and
:device-name. With the referer of a request from a browser, it also has
:referrer-type and :referrer-name.

The agent type is the type of the file of the first entry that matches:
bot, app, library or browser. A library that bots use is bot-library. A
user agent that matches no entry is unknown, and its name is the header.
The device comes from the devices of the list, except for a bot. The
referrer comes from the referrers of the list, or else it's the domain
of the referer.
sourceraw docstring

listener-ipclj/s

(listener-ip ip)

The part of the IP address ip that tells listeners apart, as the IAB counts them.

That's an IPv4 address whole, and the first 64 bits of an IPv6 address, as a prefix such as 2001:db8:1:2::/64. An IPv4 address inside IPv6, as a server writes a client of IPv4, is that IPv4 address.

Other text, such as a hash of an address, stays as it is. So a host that keeps a hash in place of the address hashes this part.

The part of the IP address `ip` that tells listeners apart, as the IAB
counts them.

That's an IPv4 address whole, and the first 64 bits of an IPv6 address,
as a prefix such as 2001:db8:1:2::/64. An IPv4 address inside IPv6, as a
server writes a client of IPv4, is that IPv4 address.

Other text, such as a hash of an address, stays as it is. So a host that
keeps a hash in place of the address hashes this part.
sourceraw docstring

thresholdclj/s

(threshold {:keys [size header duration] :as file})

The bytes of file that a download needs by the rules of the IAB: the header, and one minute of the content. It's nil when the size isn't known.

The file is a map of its :size and the size of its :header in bytes, and its :duration in seconds. The header of an MP3 file is its ID3 tag. An episode of one minute or less needs the whole file, and so does a file with no duration.

The bytes of `file` that a download needs by the rules of the IAB: the
header, and one minute of the content. It's nil when the size isn't
known.

The `file` is a map of its :size and the size of its :header in bytes,
and its :duration in seconds. The header of an MP3 file is its ID3 tag.
An episode of one minute or less needs the whole file, and so does a
file with no duration.
sourceraw docstring

user-agentsclj/s

(user-agents files)

The open list of user agents of the OPAWG from files, ready to identify agents by.

The files map :bots, :apps, :libraries, :browsers, :devices and :referrers to the JSON of the file of that name, as text or read. The files are in src/ or build/ of https://github.com/opawg/user-agents-v2.

Each entry of a file has a name, a pattern and at times a category. The pattern is a regular expression of JavaScript. A pattern that doesn't compile on the platform is left out, and the report of the list says so. The list changes often, so a host keeps a copy of one commit.

The open list of user agents of the OPAWG from `files`, ready to
identify agents by.

The `files` map :bots, :apps, :libraries, :browsers, :devices and
:referrers to the JSON of the file of that name, as text or read. The
files are in src/ or build/ of https://github.com/opawg/user-agents-v2.

Each entry of a file has a name, a pattern and at times a category. The
pattern is a regular expression of JavaScript. A pattern that doesn't
compile on the platform is left out, and the report of the list says
so. The list changes often, so a host keeps a copy of one commit.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close