# `Tayori.GitHub`
[🔗](https://github.com/hattoriai/tayori/blob/v0.1.0/lib/tayori/github.ex#L1)

Changes from GitHub: a webhook of the repository, at
`POST /hooks/github` of the JSON API (`Tayori.API`). The
[Automation](automation.md) guide adds it in GitHub.

GitHub signs each delivery with the webhook's secret
(`x-hub-signature-256`), and Tayori checks it against the body as it
came. The admin makes the secret, `new_secret/0`, and pastes it into
GitHub; Tayori keeps it encrypted, as the AI key (the `:vault` or the
`:secret_key_base` of `Tayori.Config`).

Two events make changes:

  * A pull request merged into the default branch: one change, its
    `:ref` the repository and the number (`"owner/repo#123"`). Its
    title is the pull request's, and its body the section of the pull
    request's description headed `## Changelog` or `## Release notes`,
    when there is one: never the whole description, which is often for
    the team. A pull request closed without a merge is nothing.
  * A release published: each item of its notes, as
    `Tayori.Sources.Changelog` reads them. Items under `### Added` have
    that kind; items with no section have none. Notes with no item are
    one change, titled with the release's name. A prerelease is nothing
    yet.

A pull request's labels say what the change is, with the prefix of the
`:github` option of the `:inbox` config (`"changelog:"`):

  * `changelog:skip` - no change.
  * `changelog:internal` - for the team only; `changelog:public` - for
    everyone.
  * `changelog:highlight` - worth leading with.
  * `changelog:category:<key>` - its category.
  * `changelog:segment:<segment>` - the readers it is for (one label
    each).
  * `changelog:<kind>` - its kind (`changelog:added`,
    `changelog:fixed`), and so its category (the `:kinds` of the
    `:inbox` config).

With `only_labeled: true`, a pull request with none of these labels is
nothing.

## The raw body

The signature is of the body as GitHub sent it. An endpoint whose
`Plug.Parsers` reads JSON (a Phoenix app's does) reads the body before
the router; `read_body/2` keeps a copy of it for the requests GitHub
signs, and only for them:

    plug Plug.Parsers,
      parsers: [:urlencoded, :multipart, :json],
      pass: ["*/*"],
      body_reader: {Tayori.GitHub, :read_body, []},
      json_decoder: Phoenix.json_library()

Without it, a delivery is answered with a 500, and the log says why.

# `changelog_section`

```elixir
@spec changelog_section(String.t() | nil) :: String.t() | nil
```

The section of a pull request's description for its readers: the text
under a heading `Changelog`, `Change log` or `Release notes`, to the
next heading of its level, without HTML comments. Nil when there is no
such section, or it is empty.

    iex> Tayori.GitHub.changelog_section("Fixes #12.\n\n## Changelog\n\nExports are faster.\n\n## Tests\n\nAdded.")
    "Exports are faster."

# `handle`

```elixir
@spec handle(String.t(), map(), keyword()) ::
  {:ok, [Tayori.Source.change()]} | :ignore
```

The changes of an event of GitHub, `x-github-event` and its payload:
`{:ok, changes}` (none for a `ping`, or a pull request that is not
news), or `:ignore` for an event Tayori does not read. The options are
those of the `:github` option of the `:inbox` config, which they
replace.

# `new_secret`

```elixir
@spec new_secret() :: {:ok, String.t()} | {:error, :no_secrets}
```

Makes the webhook's secret, in place of any other, and returns it, to
paste into GitHub. Tayori keeps it encrypted, and never shows it again.
`{:error, :no_secrets}` without a `:vault` or a `:secret_key_base`.

# `read_body`

```elixir
@spec read_body(Plug.Conn.t(), keyword()) ::
  {:ok, binary(), Plug.Conn.t()}
  | {:more, binary(), Plug.Conn.t()}
  | {:error, term()}
```

A body reader for `Plug.Parsers` (`body_reader: {Tayori.GitHub,
:read_body, []}`): it reads the body as `Plug.Conn.read_body/2` does,
and keeps a copy of a body GitHub signed, for `verify/2`.

# `remove_secret`

```elixir
@spec remove_secret() :: :ok
```

Removes the webhook's secret: GitHub's deliveries get a 404.

# `secret?`

```elixir
@spec secret?() :: boolean()
```

Whether there is a webhook secret.

# `verify`

```elixir
@spec verify(binary(), String.t() | nil) :: :ok | {:error, :no_secret | :invalid}
```

Checks the signature of a body, the `x-hub-signature-256` header
(`sha256=<hex>`), in constant time. Returns `:ok`, `{:error,
:no_secret}`, or `{:error, :invalid}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
