Build your own Discord bot
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:
- 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.
/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
- In the Discord Developer Portal, create an application and add a bot.
- Under Bot → Privileged Gateway Intents, turn on Server Members Intent. Without it your bot never sees members join.
- Invite it with the
botandapplications.commandsscopes and the Send Messages and Embed Links permissions. - 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:
DISCORD_TOKEN=your-bot-token
SENTINEL_KEY=ssk_your_key_here
GUILD_ID=your-server-id
STAFF_CHANNEL_ID=your-staff-channel-iddiscord.js (Node.js 18+)
npm install discord.js, then node bot.mjs.
// 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.
# 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_uidas well asdiscord_id. - Only alert on verified bans: check
ban.verification.stateisPEER_VERIFIEDorSTAFF_VERIFIED. - Post every new network ban to a channel: poll
GET /banswith 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.