Build your own Discord bot(all pages)

Start here

Reference

For AI assistants

Every page in one Markdown file, ready to attach to your assistant.

Download all docs

Build your own Discord bot

Download .md

The SentinelShare bot covers checking, filing and lifting bans, and already warns your staff when a banned player joins. If your community wants something of its own, such as adding these checks to a bot you already run, call the admin panel API from your bot.

Both examples below do two things:

  1. On member join: look up the new member's Discord ID and post in your staff channel if they have an active ban anywhere on the network.
  2. /check member: let staff check anyone on demand. Only members with Timeout Members permission see the command.

You need a community API key (how to get one). Keep it in an environment variable on the machine that runs the bot.

Set up the Discord application

  1. In the Discord Developer Portal, create an application and add a bot.
  2. Under Bot → Privileged Gateway Intents, turn on Server Members Intent. Without it your bot never sees members join.
  3. Invite it with the bot and applications.commands scopes and the Send Messages and Embed Links permissions.
  4. Right-click your staff channel and your server and Copy ID (turn on Developer Mode in Discord's Advanced settings first).

Environment variables used below:

Shell
DISCORD_TOKEN=your-bot-token
SENTINEL_KEY=ssk_your_key_here
GUILD_ID=your-server-id
STAFF_CHANNEL_ID=your-staff-channel-id

discord.js (Node.js 18+)

npm install discord.js, then node bot.mjs.

JavaScript
// bot.mjs: SentinelShare checks for your Discord
import {
  Client, EmbedBuilder, Events, GatewayIntentBits, MessageFlags,
  PermissionFlagsBits, SlashCommandBuilder,
} from "discord.js";

const API = "https://sentinelshare.com/api/v1/integration";

async function lookup(params) {
  const url = new URL(API + "/lookup");
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SENTINEL_KEY}` } });
  if (!res.ok) throw new Error(`SentinelShare lookup failed: HTTP ${res.status}`);
  return res.json();
}

function banEmbed(user, result) {
  const active = result.bans.filter((b) => b.status === "active");
  return new EmbedBuilder()
    .setTitle(`${user.username}: ${active.length} active SentinelShare ban(s)`)
    .setURL(result.url)
    .setColor(0xeb6262)
    .addFields(
      active.slice(0, 5).map((b) => ({
        name: `${b.type} · ${b.verification.state.replaceAll("_", " ").toLowerCase()}`,
        value: `${b.filed_by.community}: ${b.reason ?? "No reason given"}`.slice(0, 1024),
      })),
    );
}

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

client.once(Events.ClientReady, async (c) => {
  const check = new SlashCommandBuilder()
    .setName("check")
    .setDescription("Check a member on SentinelShare")
    .addUserOption((o) => o.setName("member").setDescription("Who to check").setRequired(true))
    .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers);
  await c.application.commands.create(check, process.env.GUILD_ID);
  console.log(`Ready as ${c.user.username}`);
});

// 1. Warn staff when a banned player joins
client.on(Events.GuildMemberAdd, async (member) => {
  try {
    const result = await lookup({ discord_id: member.id });
    if (!result.found || !result.bans.some((b) => b.status === "active")) return;
    const channel = await client.channels.fetch(process.env.STAFF_CHANNEL_ID);
    await channel.send({ content: `${member} just joined.`, embeds: [banEmbed(member.user, result)] });
  } catch (err) {
    console.error("Join check failed:", err);
  }
});

// 2. /check member
client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== "check") return;
  await interaction.deferReply({ flags: MessageFlags.Ephemeral });
  const user = interaction.options.getUser("member", true);
  try {
    const result = await lookup({ discord_id: user.id });
    if (!result.found || !result.bans.some((b) => b.status === "active")) {
      await interaction.editReply(`${user.username} has no active bans on SentinelShare.`);
    } else {
      await interaction.editReply({ embeds: [banEmbed(user, result)] });
    }
  } catch {
    await interaction.editReply("SentinelShare didn't answer. Try again in a minute.");
  }
});

client.login(process.env.DISCORD_TOKEN);

discord.py (Python 3.9+)

pip install discord.py, then python bot.py.

Python
# bot.py: SentinelShare checks for your Discord
import os

import aiohttp
import discord
from discord import app_commands

API = "https://sentinelshare.com/api/v1/integration"
GUILD = discord.Object(id=int(os.environ["GUILD_ID"]))
STAFF_CHANNEL_ID = int(os.environ["STAFF_CHANNEL_ID"])


async def lookup(**params) -> dict:
    headers = {"Authorization": f"Bearer {os.environ['SENTINEL_KEY']}"}
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.get(f"{API}/lookup", params=params, timeout=aiohttp.ClientTimeout(total=15)) as res:
            res.raise_for_status()
            return await res.json()


def active_bans(result: dict) -> list[dict]:
    return [b for b in result["bans"] if b["status"] == "active"] if result["found"] else []


def ban_embed(user: discord.abc.User, result: dict) -> discord.Embed:
    active = active_bans(result)
    embed = discord.Embed(
        title=f"{user.name}: {len(active)} active SentinelShare ban(s)",
        url=result["url"],
        color=0xEB6262,
    )
    for ban in active[:5]:
        state = ban["verification"]["state"].replace("_", " ").lower()
        embed.add_field(
            name=f"{ban['type']} · {state}",
            value=f"{ban['filed_by']['community']}: {ban['reason'] or 'No reason given'}"[:1024],
            inline=False,
        )
    return embed


class Bot(discord.Client):
    def __init__(self):
        intents = discord.Intents.default()
        intents.members = True  # needs Server Members Intent in the Developer Portal
        super().__init__(intents=intents)
        self.tree = app_commands.CommandTree(self)

    async def setup_hook(self):
        self.tree.copy_global_to(guild=GUILD)
        await self.tree.sync(guild=GUILD)


bot = Bot()


# 1. Warn staff when a banned player joins
@bot.event
async def on_member_join(member: discord.Member):
    try:
        result = await lookup(discord_id=str(member.id))
    except aiohttp.ClientError as exc:
        print("Join check failed:", exc)
        return
    if active_bans(result):
        channel = bot.get_channel(STAFF_CHANNEL_ID)
        await channel.send(f"{member.mention} just joined.", embed=ban_embed(member, result))


# 2. /check member
@bot.tree.command(name="check", description="Check a member on SentinelShare")
@app_commands.default_permissions(moderate_members=True)
async def check(interaction: discord.Interaction, member: discord.User):
    await interaction.response.defer(ephemeral=True)
    try:
        result = await lookup(discord_id=str(member.id))
    except aiohttp.ClientError:
        await interaction.followup.send("SentinelShare didn't answer. Try again in a minute.", ephemeral=True)
        return
    if active_bans(result):
        await interaction.followup.send(embed=ban_embed(member, result), ephemeral=True)
    else:
        await interaction.followup.send(f"{member.name} has no active bans on SentinelShare.", ephemeral=True)


bot.run(os.environ["DISCORD_TOKEN"])

Ideas to take further

  • Check by Arma UID too: if your whitelist or verification bot knows a member's Arma UID, pass arma_uid as well as discord_id.
  • Only alert on verified bans: check ban.verification.state is PEER_VERIFIED or STAFF_VERIFIED.
  • Post every new network ban to a channel: poll GET /bans with a saved cursor every few minutes (see Node.js and Python) and send an embed per new record. The official bot already does this with /setup notifications.
  • Remember each key allows 300 requests a minute. A busy server's join wave fits easily; don't look up every member of a large server at once.
  • To check everyone already in your server, use the official bot's /bans scan: it checks 1,000 members per request and saves a report on the website. See Scan your Discord.