# Recipes

## A profile card on your site

Plain HTML and JavaScript, no build step. Put it anywhere on a page:

```html
<div class="alloy-card" data-handle="elchi"></div>
<script type="module">
  for (const el of document.querySelectorAll(".alloy-card")) {
    const res = await fetch(`https://api.alloymc.dev/v1/users/${el.dataset.handle}`);
    if (!res.ok) continue;
    const u = await res.json();
    const img = Object.assign(document.createElement("img"), {
      src: u.avatarUrl.replace("avatar.png?", "avatar.png?s=128&"),
      width: 64, height: 64, alt: "",
    });
    img.style.imageRendering = "pixelated";
    const link = Object.assign(document.createElement("a"), {
      href: u.profileUrl,
      textContent: u.displayName,
    });
    const meta = document.createElement("small");
    meta.textContent = u.private ? "Private profile" : `${u.followers} followers`;
    el.append(img, link, meta);
  }
</script>
```

Building the card with `textContent` instead of `innerHTML` keeps someone's display name from being read as HTML.

## A Discord bot command

With [discord.js](https://discord.js.org) 14, a `/alloy handle:<name>` command that answers with an embed:

```js
import { Client, EmbedBuilder, Events, GatewayIntentBits } from "discord.js";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.on(Events.InteractionCreate, async (i) => {
  if (!i.isChatInputCommand() || i.commandName !== "alloy") return;
  const handle = i.options.getString("handle", true);
  const res = await fetch(`https://api.alloymc.dev/v1/users/${encodeURIComponent(handle)}`);
  if (!res.ok) return i.reply({ content: "No one on AlloyMC has that handle.", ephemeral: true });
  const u = await res.json();
  const embed = new EmbedBuilder()
    .setTitle(u.displayName)
    .setURL(u.profileUrl)
    .setThumbnail(u.avatarUrl.replace("avatar.png?", "avatar.png?s=256&"))
    .setDescription(u.bio || null)
    .addFields(
      { name: "Followers", value: String(u.followers ?? "Private"), inline: true },
      { name: "Minecraft", value: u.minecraft?.map((m) => m.name).join(", ") || "None shown", inline: true },
    );
  await i.reply({ embeds: [embed] });
});

client.login(process.env.DISCORD_TOKEN);
```

Register the command with a required string option called `handle`, as described in the [discord.js guide](https://discordjs.guide/creating-your-bot/slash-commands.html).

## AlloyMC profiles for players on your server

Show which of your players have an AlloyMC profile, for example on your server's website. Look each player up by UUID and cache the answer:

```python
import time
import requests

cache = {}  # uuid -> (time, profile or None)

def alloymc_profile(uuid: str):
    hit = cache.get(uuid)
    if hit and time.time() - hit[0] < 600:
        return hit[1]
    r = requests.get(f"https://api.alloymc.dev/v1/minecraft/{uuid}", timeout=10)
    profile = r.json()["user"] if r.status_code == 200 else None
    cache[uuid] = (time.time(), profile)
    return profile

for uuid in ["069a79f444e94726a5befca90e38aaf5"]:
    p = alloymc_profile(uuid)
    print(uuid, "->", p["profileUrl"] if p else "no public AlloyMC profile")
```

`404` simply means the player hasn't linked a public profile, which is the common case. Keep lookups under the [rate limit](https://api.alloymc.dev/docs/conventions#rate-limits) by caching like this.

## Check for a new Alloy version

```bash
curl -s https://api.alloymc.dev/v1/launcher/releases/latest
```

The answer has `version`, `published`, release `notes` in Markdown, and the signed update files per platform with their SHA-256.
