# PluginProbe

> Browse, permalink and fetch the source code of any plugin published on WordPress.org, in
> any released version or in trunk. Every line has a stable URL, and every file can be
> fetched as plain text.

PluginProbe reads file listings and file contents live from the official WordPress.org SVN
repository at `https://plugins.svn.wordpress.org`, and release archives from
`https://downloads.wordpress.org`. It mirrors nothing permanently, so what you read is what
the repository holds. It is not affiliated with WordPress.org or Automattic.

## URL scheme

`{slug}` is the WordPress.org plugin slug. `{version}` is any released tag, for example
`5.3.3`, or the literal `trunk`. `{path}` is the file path relative to the plugin root.

| URL | Returns |
| --- | --- |
| `/plugins/{slug}` | Plugin overview, versions and links |
| `/plugins/{slug}/changelog` | Every released version with diff links |
| `/plugins/{slug}/{version}` | Version overview |
| `/plugins/{slug}/{version}/code` | File listing at the plugin root |
| `/plugins/{slug}/{version}/code/{path}` | A file, syntax highlighted |
| `/plugins/{slug}/{version}/raw/{path}` | A file as `text/plain` |
| `/plugins/{slug}/{version}/download/{path}` | A file as an attachment |
| `/plugins/{slug}/{version}/diff/{other}` | Files added and removed between two versions |
| `/plugins/{slug}/{version}/diff/{other}/{path}` | Unified diff of one file |
| `/plugins?q={term}` | Plugin search |

A plugin that has not been indexed yet is fetched from the WordPress.org API the first time
its overview is requested, so any valid slug works.

## Line anchors

Every line of a file page is a table row carrying `id="L{n}"`, in source order. Append a
fragment to address one line or a range:

- `#L10` selects line 10
- `#L10-L20` selects lines 10 to 20
- `#L10-20` is accepted as well

Released tags never change, so a link into a tagged version keeps pointing at the same
code. Links into `trunk` follow whatever the author commits next. For a citation that has
to stay accurate, use a tag rather than `trunk`.

## Reading as Markdown

Every `/code/` URL answers with Markdown when asked, so an agent does not have to know a
second URL scheme. Either send the header or append the parameter:

```
curl -H 'Accept: text/markdown' https://pluginprobe.com/plugins/akismet/5.3.3/code/akismet.php
curl https://pluginprobe.com/plugins/akismet/5.3.3/code/akismet.php?format=md
```

The reply is a short header naming the plugin, version and path, the page and raw URLs, the
line count, and then the source in a fenced block. A directory answers with its entries as a
Markdown list. This is roughly a twentieth of the bytes of the HTML page, which for a large
file is the difference between a readable response and an unusable one.

The `?format=md` form is served with `X-Robots-Tag: noindex`, because the page itself is the
canonical address. Both forms send `Vary: Accept`.

## Fetching source

The raw endpoint returns the file exactly as stored, with no header and no fence:

```
curl https://pluginprobe.com/plugins/akismet/5.3.3/raw/akismet.php
```

- `Content-Type: text/plain; charset=utf-8`, with `X-Content-Type-Options: nosniff`
- Files containing binary data are served as `application/octet-stream`
- A missing file or an unknown plugin returns 404

## Caching

Every response carries `Cache-Control` and an `ETag`. Send the ETag back as
`If-None-Match` and an unchanged page answers `304` with no body.

- A file in a released tag: `max-age=604800`, marked `immutable`
- A file in trunk: `max-age=3600`
- Listings: short lifetimes, revalidate freely

Code pages carry `Vary: Accept`, since the same URL has an HTML and a Markdown form.

## Rate limits

- Raw and download endpoints: 120 requests per minute per IP
- Plugin search: 60 requests per minute per IP
- Everything else is unmetered. Reuse connections and honour the cache headers.

## Parsing the HTML

Prefer the raw endpoint. If you must read a file page, each line is:

```html
<tr id="L12" data-line="12">
  <td class="pp-ln"><a href="#L12" data-line-link>12</a></td>
  <td class="pp-code">...the highlighted line...</td>
</tr>
```

The text of `.pp-code` is the line's source. A file page also embeds the complete path list
of the version as JSON in `<script type="application/json" id="pp-tree">`, which is the
cheapest way to learn what a release contains.

## Discovery

- `https://pluginprobe.com/sitemap.xml` every indexed plugin and its latest version
- `https://pluginprobe.com/robots.txt` crawl policy: plugin pages are open to every crawler, code pages only to fetches made for a person
- `https://pluginprobe.com/bot` the same reference as a web page

## Our crawler

Requests PluginProbe makes to WordPress.org carry the User-Agent
`PluginProbe/1.0 (+https://pluginprobe.com)`. Only public APIs and repositories are read.
