Bir bot neler yapar
Bot, sizin yazdığınız bir program. Anten’e üye gibi bağlanır, mesaj okuyup yazar ve olayları (yeni mesaj, tepki, odaya giren biri) olduğu anda duyar. Bot hesabı programın kimliğidir; program bu hesabın anahtarıyla bağlanır.
Bir bot şunları yapabilir:
- Komut sunmak: kişiler yazma kutusuna
/yazınca botun komutları önerilir. - Mesajlarına kartlar, butonlar ve seçim menüleri eklemek; basılınca yalnızca basan kişiye cevap vermek.
- Şifreli odalarda, kişilerin uygulamaları gibi kendi anahtarlarıyla okuyup yazmak.
- Sesli odaya girip ses çalmak.
Tek ihtiyacınız başka bir sistemden bir odaya mesaj yazmaksa webhook daha basit: sürekli bağlantı da kütüphane de gerekmez.
Örnekler anten-bot kütüphanesini kullanır. Kütüphane Anten’in dosyalarıyla gelir (sdk/js klasörü), Node.js
22.12 ve üstünde çalışır ve başka paket gerektirmez. Bağlantıyı açık tutar, koparsa yeniden bağlanır ve şifreli
odaları kendisi yönetir.
İlk botunuz
-
Botu oluşturun.
Sunucu ayarları → Botlar bölümünde bir ad yazın ve Bot oluştur’a basın. Bunun için ilk sunucuda Botları yönetme izni gerekir; yöneticilerde vardır.
-
Anahtarı kopyalayın.
Anahtar
anten_bot_ile başlar ve yalnızca bir kez gösterilir. Hemen güvenli bir yere kaydedin. -
Kütüphaneyi botun klasörüne kopyalayın.
Anten’in dosyaları bir üst klasördeki
antenklasöründeyse:mkdir benim-botum && cd benim-botum cp -r ../anten/sdk/js anten-bot -
bot.mjsdosyasını yazın.import { Client } from './anten-bot/src/index.js'; const bot = new Client({ host: process.env.ANTEN_HOST, // e.g. anten.example.org token: process.env.ANTEN_BOT_TOKEN, keyDir: '.anten-bot', // the bot's own keys }); bot.on('ready', () => bot.setCommands([{ name: 'ping', description: 'Botun cevap verdiğini sınar' }])); bot.on('command', async ({ name, reply }) => { if (name === 'ping') await reply('pong'); }); bot.on('error', (err) => console.error(err)); bot.on('close', (code, reason) => console.error('stopped:', code, reason)); await bot.start(); -
Botu çalıştırın.
export ANTEN_HOST=anten.example.org export ANTEN_BOT_TOKEN=anten_bot_… node bot.mjs -
Deneyin.
Botun görebildiği bir odada
/pingyazın. Botpongdiye cevap verir.
Bot her sunucuya üye olur ve varsayılan gruplara girer. Hangi odaları göreceğini ve nereye yazabileceğini, bir kişide olduğu gibi gruplarıyla ve oda izinleriyle belirlersiniz. Botların adının yanında BOT etiketi görünür. Botun resmini aynı bölümde Resmi değiştir ile koyabilirsiniz.
Komutlar
Bot, sunduğu komutları setCommands ile bildirir. Kişiler / yazınca, o odayı görebilen ve oraya yazabilen
botların komutları açıklamalarıyla önerilir. Kullanılan komut sıradan bir mesajdır: şifreli odada şifreli gider ve
sunucu içeriğini görmez. Kütüphane mesajı okur, seçenekleri ayırır ve command olayını verir.
bot.on('ready', () =>
bot.setCommands([
{
name: 'zar',
description: 'Zar atar',
options: [{ name: 'yüz', description: 'Kaç yüzlü', type: 'integer' }],
},
{
name: 'selam',
description: 'Birini selamlar',
options: [{ name: 'kim', description: 'Selamlanacak kişi', type: 'user', required: true }],
},
]),
);
bot.on('command', async ({ name, args, message, reply }) => {
if (name === 'zar') await reply(`🎲 ${1 + Math.floor(Math.random() * (args.yüz ?? 6))}`);
if (name === 'selam') {
const member = bot.guilds.get(message.guild_id)?.members.get(args.kim);
await reply(`Merhaba ${member?.user.display_name}!`);
}
});
Seçenekler
- Türler:
string(metin),integer(tam sayı),number(sayı),boolean(evet/hayır, açık/kapalı, true/false) veuser(bir üyenin adı ya da@adı; bota kullanıcının kimliği gelir). - Komutun son seçeneği metinse (
string), satırın geri kalanını alır. Boşluk içeren bir metin tırnak içinde de yazılabilir. - Zorunlu seçenekler (
required: true) önce gelir. - Eksik ya da hatalı kullanımda kütüphane kullanım satırıyla cevap verir, örneğin
⚠ /selam <kim>. Cevabı kendiniz vermek içincommandErrorolayını dinleyin. - İki bot aynı komut adını kullanırsa öneri
/zar@Asistanbiçiminde yazılır (botun adı, boşluksuz).
Hazır değerler ve tamamlama
Bir seçeneğe hazır değerler (choices) verebilirsiniz; kişi yazarken önerilir:
{ name: 'öncelik', description: 'Ne kadar acil', type: 'string', choices: [{ name: 'Yüksek', value: 'yuksek' }, { name: 'Düşük', value: 'dusuk' }] }
Değerler önceden bilinmiyorsa seçeneğe autocomplete: true ekleyin. Kişi yazdıkça bota sorulur; bot en fazla 25
öneriyle cevap verir:
bot.on('autocomplete', async ({ command, option, query, choices }) => {
const cities = ['İstanbul', 'İzmir', 'Ankara'];
await choices(cities.filter((c) => c.toLocaleLowerCase('tr').startsWith(query.toLocaleLowerCase('tr'))));
});
Butonlar ve menüler
Bir mesaja kartlar (embeds) ve butonlarla seçim menüleri (components) eklenebilir. Bunlar mesajın içinde
taşınır, yani şifreli odada mesajla birlikte şifrelenir. Uygulama bunları yalnızca botların mesajlarında çizer ve
dışarıdan resim yüklemez.
await bot.send(roomId, 'Bugün ne yiyelim?', {
embeds: [{ title: 'Öğle yemeği', description: 'Birini seçin', color: 0x4fd1a5, fields: [{ name: 'Süre', value: '5 dk', inline: true }] }],
components: [
[
{ type: 'button', id: 'evet', label: 'Katılıyorum', style: 'success' },
{ type: 'button', id: 'hayir', label: 'Bugün yokum', style: 'danger' },
],
[{ type: 'select', id: 'yemek', placeholder: 'Seçin', options: [{ label: 'Çorba', value: 'corba' }, { label: 'Makarna', value: 'makarna' }] }],
[{ type: 'link', label: 'Menü', url: 'https://anten.example.org/menu' }],
],
});
- Buton stilleri:
primary,secondary,success,danger. Bağlantı butonu (link) yalnızca http(s) adreslerine gider. - Bir satırda en fazla 5 buton ya da tek bir menü olur; bir mesajda en fazla 5 satır ve 5 kart bulunur.
- Bir menüde en fazla 25 seçenek olur.
minvemaxile birden fazla seçim istenebilir. - Mesajı sonradan
bot.edit(message, text, { embeds, components })ile değiştirebilirsiniz.
Etkileşimler ve cevaplar
Bir basış ya da menü seçimi bota etkileşim olarak gelir. Bot buna reply ile cevap verir. Cevabı yalnızca
basan kişi görür ve sayfa yenilenince kaybolur.
bot.on('interaction', async ({ customId, values, user_id, reply }) => {
if (customId === 'yemek') await reply(`Seçiminiz: ${values[0]}`);
else await reply(customId === 'evet' ? 'Sizi listeye ekledim.' : 'Peki, bir dahaki sefere.');
});
Şifreli odalar
Uçtan uca şifreli bir odada sunucu mesajları okuyamaz. Bot bu odalarda, kişilerin uygulamaları gibi kendi
anahtarlarıyla çalışır. Bunu anten-bot kendisi halleder; aynı kod iki tür odada da çalışır.
- Bot ilk açılışta bir kimlik anahtarı ve bir cihaz anahtarı üretir. İkisini
keyDirklasöründe, yalnızca kendi kullanıcısının okuyabileceği bir dosyada saklar. - Odadakilerin uygulamaları botun anahtarını ilk görüşte tanır ve odanın anahtarını ona da verir. Gerektiğinde bot yeni bir oda anahtarını kendisi oluşturur.
- Bot mesajlarını kişilerinkiyle aynı biçimde şifreler ve imzalar.
- Yazarının bilinen anahtarıyla imzalanmamış bir mesaj
trusted: falseile gelir veidentityChangedolayı yayılır. Yeni anahtarı kişiyle Anten dışında doğruladıktan sonrabot.acceptIdentity(userId, key)ile kabul edin.
keyDir bir parola kadar değerlidir: onu ele geçiren, botun okuyabildiği odaları okuyabilir. Yedekleyin, ama
paylaşmayın ve bir depoya koymayın. Kaybederseniz bot yeni bir kimlikle başlar ve odadakilere botun anahtarının
değiştiği bildirilir.
Anahtar ya da keyDir sızarsa Anahtarı yenile’ye basın: botun cihazları silinir, tuttuğu oda anahtarları geri
çekilir ve her oda bir sonraki mesajla yeni bir anahtara geçer. Sızan anahtarlarla eski mesajlar okunabilir,
yenileri okunamaz.
Sesli botlar
Bir bot sesli odaya girip ses çalabilir; sesi kişilerinki gibi uçtan uca şifrelenir. Sesi, Anten’in dosyalarındaki
ses yardımcısı gönderir: bots/voice klasöründe Go ile yazılmış anten-voice programı. Dosyaları ve adresleri
ffmpeg ile çalar. Hazır müzik botu da bu yolla çalışır. Programı botun klasöründe derleyin:
go build -C ../anten/bots/voice -o "$PWD/anten-voice" .
Kütüphanenin VoiceHelper sınıfı bu programı başlatır. Bot bağlandıktan sonra (ready) odaya girebilir:
import { VoiceHelper } from './anten-bot/src/index.js';
const helper = new VoiceHelper({ path: './anten-voice', ffmpeg: 'ffmpeg' });
await bot.joinVoice(guildId, roomId, helper); // deaf by default
helper.play('muzik.ogg', { volume: 0.8 });
helper.on('ended', (error) => console.log('ended', error));
Bot odaya varsayılan olarak sağır girer. Bu yalnızca bir işaret değil:
- Sunucu sağır bir bota kimsenin sesini göndermez.
- Odadakilerin uygulamaları ona ses anahtarı vermez; bot sağır olunca odadakiler botun almadığı yeni bir anahtara geçer.
- Bot sağırlığını kaldırırsa (
{ deaf: false }) bu herkese görünür; anahtarlarıvoiceKeyolayıyla alır. Hazır ses yardımcısı yalnızca ses gönderir; gelen sesi çözmek sizin kodunuza kalır. - Mikrofonu kapalı (
bot.setVoice({ mute: true })) bir botun sesini sunucu iletmez.
Başka dillerden bağlanmak
Kütüphane olmadan da bağlanabilirsiniz; şifresiz odalar için bu yeter. Şifreli bir odada sunucu düz bir
mesajı sealed_required hatasıyla geri çevirir.
REST: Adresler https://anten.example.org/api/v1/... biçimindedir. Her istek Authorization: Bot <anahtar>
başlığını taşır; gövdeler JSON’dur. Kimlikler JSON’da metin olarak gelir. Hatalar
{"code": "...", "message": "..."} biçimindedir.
Gateway: Canlı olaylar wss://anten.example.org/gateway adresinden, aynı başlıkla gelir. Her çerçeve
{"op", "d", "s", "t"} biçimindedir.
-
Sunucunun ilk çerçevesini (
op10) okuyun.d.heartbeat_intervalkalp atışı aralığıdır (milisaniye). -
Kendinizi tanıtın.
{"op": 2, "d": {}}gönderin. SunucuREADYolayıyla sunucuları, odaları ve üyeleri verir. -
Kalp atışını sürdürün.
Her aralıkta
{"op": 1, "d": <son s>}gönderin; sunucuop11 ile cevaplar. Kalp atışı kesilen bağlantı 4009 koduyla kapanır. -
Olayları işleyin.
Olaylar
op0 ile gelir, adıtalanındadır:MESSAGE_CREATE,MESSAGE_UPDATE,MESSAGE_DELETE,MESSAGE_REACTION_ADD,GUILD_MEMBER_ADD,VOICE_STATE_UPDATE…
Bağlantı koparsa biraz bekleyip yeniden bağlanın. Yalnızca 4004 (anahtar yenilendi ya da bot silindi), 4006
(sunucudan atıldı) ve 4007 (yasaklandı) kodlarında yeniden bağlanmayın. Botun kendi mesajları da MESSAGE_CREATE
olarak gelir: yazar botun kendisiyse cevap vermeyin, yoksa bot kendi kendisiyle konuşur.
Python ile !ping yazana cevap veren bir örnek (Python 3.10 ve üstü, pip install websockets):
import asyncio, json, os, urllib.request
import websockets
HOST = os.environ["ANTEN_HOST"]
AUTH = {"Authorization": "Bot " + os.environ["ANTEN_BOT_TOKEN"]}
def api(method, path, body=None):
req = urllib.request.Request(
f"https://{HOST}/api/v1{path}", method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={**AUTH, **({"Content-Type": "application/json"} if body is not None else {})},
)
with urllib.request.urlopen(req) as res:
return json.loads(res.read() or "null")
ME = api("GET", "/users/@me")
async def run():
while True:
try:
async with websockets.connect(f"wss://{HOST}/gateway", additional_headers=AUTH) as ws:
seq = 0
hello = json.loads(await ws.recv())
await ws.send(json.dumps({"op": 2, "d": {}}))
async def beat():
while True:
await asyncio.sleep(hello["d"]["heartbeat_interval"] / 1000)
await ws.send(json.dumps({"op": 1, "d": seq}))
beater = asyncio.create_task(beat())
try:
async for raw in ws:
f = json.loads(raw)
seq = f.get("s") or seq
if f["op"] == 0 and f["t"] == "MESSAGE_CREATE":
m = f["d"]
if (m.get("author") or {}).get("id") == ME["id"] or m.get("dm"):
continue
if m["content"].strip() == "!ping":
await asyncio.to_thread(api, "POST", f"/rooms/{m['room_id']}/messages",
{"content": "pong", "reply_to": m["id"]})
finally:
beater.cancel()
except websockets.ConnectionClosed as e:
if e.rcvd and e.rcvd.code in (4004, 4006, 4007):
raise SystemExit(f"closed: {e.rcvd.code} {e.rcvd.reason}")
await asyncio.sleep(5)
asyncio.run(run())
Sınırlar ve güvenlik
Botlar şunlara hiçbir zaman ulaşamaz, yönetici grubunda olsalar bile:
- Özel mesajlar: bot okuyamaz ve yazamaz; kimse bota özel mesaj gönderemez.
- Kurulumun yönetimi: erişim, yedekler, güncellemeler, sunucu durumu ve botlar.
- Yetki anahtarları, hesaplar ve anahtar yedekleri.
Böylece sızan bir anahtar kurulumu ele geçiremez. Diğer sınırlar:
- Bir kurulumda en fazla 50 bot olur.
- Bir botun en fazla 25 komutu, her komutun en fazla 10 seçeneği, her seçeneğin en fazla 25 hazır değeri olur.
Komut adları küçük harf (Türkçe harfler dahil), rakam,
_ve-içerir. - Yazma istekleri (mesaj, tepki, yazıyor bilgisi) art arda 10, sonra saniyede 2 olabilir. Fazlası
429alır:Retry-Afterbaşlığındaki saniye kadar bekleyin. Kütüphane bunu kendisi yapar.
İyi alışkanlıklar:
- Anahtarı yalnızca botun çalıştığı makinede, bir ortam değişkeninde ya da bir gizli bilgi kasasında tutun.
- Botu yönetici yapmayın; yalnızca ihtiyaç duyduğu odaları gören bir gruba koyun.
- Komutu kimin kullanabileceğine bot karar verir:
message.authorvebot.guilds.get(message.guild_id).membersile kişinin gruplarına bakın. - Kişilerden gelen metni bir komut satırına ya da adrese olduğu gibi koymayın.
Başvuru
Client
new Client({ host, token, keyDir }) bir bot oluşturur; await bot.start() bağlanır, await bot.stop() kapatır.
| Yöntem | Ne yapar |
|---|---|
send(roomId, text, { replyTo, embeds, components }) |
Mesaj gönderir; oda şifreliyse şifreler |
reply(message, text, extra) |
Bir mesaja cevap verir |
edit(message, text, { embeds, components }) |
Botun kendi mesajını değiştirir |
delete(message) |
Mesajı siler |
react(message, emoji) |
Tepki ekler |
typing(roomId) |
Birkaç saniye “yazıyor” gösterir |
messages(roomId, { before, limit }) |
Odanın mesajları, eskiden yeniye, açılmış olarak |
setCommands(commands) |
Botun komutlarını bildirir |
setActivity({ kind, name }) |
Adının yanında ne yaptığını gösterir; null kaldırır |
joinVoice(guildId, roomId, helper, { mute, deaf }) |
Sesli odaya girer |
setVoice({ mute, deaf }) |
Odadayken mikrofonu ya da sağırlığı değiştirir |
leaveVoice() |
Sesli odadan çıkar |
acceptIdentity(userId, key) |
Doğrulanmış yeni bir kimlik anahtarını kabul eder |
api(method, path, body) |
Bir REST isteği gönderir |
bot.me botun kendisi, bot.guilds sunucuları (her birinde guild, rooms, members), bot.voiceFlags ses
durumudur. setActivity için kind değeri playing, listening ya da watching olur; ad en fazla 64 karakterdir.
Olaylar
| Olay | Ne zaman |
|---|---|
ready |
Bağlantı kuruldu |
message, messageUpdate, messageDelete |
Bir mesaj geldi, değişti ya da silindi (text açılmış metindir) |
reaction |
Bir mesaja tepki eklendi |
command, commandError |
Botun bir komutu kullanıldı ya da yanlış kullanıldı |
interaction, autocomplete |
Bir butona basıldı, bir menüden seçildi ya da bir seçenek yazılıyor |
identityChanged |
Bir üyenin kimlik anahtarı bilinenden farklı |
voice, voiceKey |
Botun sesli odası değişti; sağır değilken bir ses anahtarı geldi |
event |
Gateway’den gelen her olay (adı ve verisi) |
error, close |
Bir hata oldu; bağlantı kalıcı olarak kapandı |
REST
| İstek | Ne yapar |
|---|---|
GET /users/@me |
Botun kendisi |
GET /users/@me/guilds |
Botun sunucuları |
GET /guilds/{sunucu} |
Sunucunun odaları, grupları ve üyeleri |
GET /rooms/{oda}/messages?before={id}&limit=50 |
Odanın mesajları |
POST /rooms/{oda}/messages |
Mesaj gönderir: {"content": "...", "reply_to": "{id}"} |
PATCH /rooms/{oda}/messages/{id} |
Botun kendi mesajını düzenler |
DELETE /rooms/{oda}/messages/{id} |
Mesajı siler |
POST /rooms/{oda}/messages/{id}/reactions |
Tepki ekler: {"emoji": "👍"} |
POST /rooms/{oda}/typing |
“Yazıyor” gösterir |
PUT /users/@me/commands |
Komutları bildirir |
PUT /users/@me/avatar |
Botun resmini değiştirir (gövde: resim) |