Formlar
Form, botunun bir komuta ya da butona yanıt olarak kişiye açtığı pencere: kişi en çok beş alanı doldurur, gönderir ve doldurduğu her şey sana yeni bir etkileşim olarak gelir.
Form mu, komut seçeneği mi?
| Durum | Seç |
|---|---|
| Kısa, tek satırlık birkaç değer: bir sayı, bir kişi, listeden bir şey | Komut seçeneği |
| Uzun ya da çok satırlı metin: şikayet, başvuru, açıklama | Form |
| Değeri bir butona basınca sormak | Form (butonun seçeneği yoktur) |
| Kişinin yazdığını önceden doldurup düzelttirmek | Form, value ile |
| Yazarken öneri göstermek | Otomatik tamamlama; formda öneri yok |
Akış
| Adım | Ne olur |
|---|---|
| 1 | Kişi komutunu çalıştırır ya da butonuna basar. Sana INTERACTION_CREATE gelir, type: 2 ya da type: 3. |
| 2 | 3 saniye içinde callback ile type: 9 ve formu gönderirsin. Yanıt 204'tür. Bu, etkileşimin tek yanıtıdır. |
| 3 | Form kişinin ekranında açılır; altında sabit uyarı satırı durur. Kişi istediği kadar uzun yazabilir. |
| 4 | Kişi gönderir. Sana yeni bir INTERACTION_CREATE gelir, type: 5: kendi id'si ve 15 dakikalık yeni token'ı vardır. |
| 5 | 3 saniye içinde bu yeni etkileşime yanıt verirsin: 4, 5, form bir butondan açıldıysa 6 ya da 7 de olur. |
DM'de de aynıdır: komut ya da buton botla açılmış DM'deyse gönderim context: 1 ile ve member yerine user ile gelir; kişi gönderim arasında botu engellerse gönderim reddedilir.
Form açıldıktan sonra 1 saat gönderilebilir. Açan etkileşimin 15 dakikalık token'ı bunu kısaltmaz: kişi 40 dakika yazsa da gönderim çalışır.
Kişi ne görür
Form yalnızca komutu çalıştıran ya da butona basan cihazda açılır; kişinin öbür oturumları aynı olayı alır ama yok sayar. Pencerenin başlığı senin title'ındır. Altında botunun avatarı, adı, BOT etiketi ve uygulaman doğrulanmışsa "Doğrulanmış" etiketi durur. Alanlar senin sıranla gelir; zorunlu olanların adının yanında kırmızı bir yıldız vardır.
| Alan | Kişi ne görür |
|---|---|
Metin girişi, style: 1 | Tek satırlık yazı kutusu, placeholder ve value ile. Altında kalan karakter sayısı yazar; sınırı geçince sayı kırmızıya döner. max_length göndermezsen sınır 4000'dir. |
Metin girişi, style: 2 | Çok satırlı, boyu uzatılabilen yazı kutusu; sayaç aynı. |
| Metin menüsü | Tek seçimliyse uygulamanın açılır listesi, çok seçimliyse işaretlenen bir liste penceresi. default seçenekler seçili gelir. |
| Kullanıcı, rol, kanal, karma menü | Bir düğme; basınca aramalı bir liste penceresi açılır, kişi işaretleyip "Onayla"ya basar. Kanal listesinde yalnızca kişinin gördüğü ve channel_types'a uyan kanallar çıkar. required: false menü boş bırakılabilir. |
Pencerenin altında, alanların dışında, kişinin her zaman gördüğü satır durur: "Bu form {bot} botuna gider. Şifreni ya da doğrulama kodunu asla yazma." Alanlar uzunsa yalnızca alanlar kayar; bu satır ve düğmeler yerinde kalır.
Kişi "Gönder"e basınca uygulama önce zorunlu alanları ve uzunlukları sunucunun kurallarıyla denetler: yalnızca boşluk yazılmış zorunlu alan boş sayılır. Hata alanın altında kırmızı yazar ve form gitmez. Her şey tamsa "Gönder" dönen bir simgeye döner ve botun yanıtlayana kadar bekler; yanıt gelince form kapanır.
Gönderim başarısız olursa form açık kalır ve kişinin yazdığı her şey yerinde durur:
| Ne oldu | Kişi ne görür |
|---|---|
| Botun 3 saniyede yanıt vermedi | Formun altında "Bot yanıt vermedi." Kişi yeniden gönderebilir. |
Sunucu bir alanı reddetti (validation_failed) | Hata o alanın altında. |
Form süresi doldu ya da üç kez gönderildi (modal_expired) | "Süre doldu, metni kopyalayıp komutu yeniden çalıştır." "Gönder" kapanır, yanında bir "Kopyala" düğmesi çıkar; yazılan metin alan adlarıyla birlikte panoya kopyalanır. |
| Başka bir hata | Hatanın metni formun altında. |
Kişi bir şey yazmışsa kapatma düğmesi, Esc, dışarı tıklama ya da "Vazgeç" önce "Yazdıkların silinsin mi?" diye sorar. Yalnızca senin value ile doldurduğun metin duruyorsa form sormadan kapanır.
En küçük örnek
Komuta form ile yanıt ver:
curl -X POST "https://noluyo.net/api/v1/interactions/$INTERACTION_ID/$INTERACTION_TOKEN/callback" \
-H "Authorization: Bot $BOT_TOKEN" -H 'Content-Type: application/json' \
-d '{"type":9,"data":{"custom_id":"sikayet","title":"Şikayet","components":[{"type":1,"components":[{"type":4,"custom_id":"konu","style":1,"label":"Konu","max_length":100}]},{"type":1,"components":[{"type":4,"custom_id":"detay","style":2,"label":"Ne oldu?","required":false}]}]}}'Aynı form JSON olarak:
{
"type": 9,
"data": {
"custom_id": "sikayet",
"title": "Şikayet",
"components": [
{ "type": 1, "components": [
{ "type": 4, "custom_id": "konu", "style": 1, "label": "Konu", "max_length": 100 }
] },
{ "type": 1, "components": [
{ "type": 4, "custom_id": "detay", "style": 2, "label": "Ne oldu?", "required": false }
] }
]
}
}Formun alanları
| Alan | Kural |
|---|---|
custom_id | Zorunlu, en çok 100 karakter. Gönderimde data.custom_id olarak geri gelir. |
title | Zorunlu, en çok 45 karakter |
components | 1-5 eylem satırı (type: 1); her satırda tek bir metin girişi ya da tek bir menü |
Formun JSON'u en çok 16 KiB olabilir. Alanların custom_id'leri form içinde tekil olmalı. Otomatik moderasyon başlığı, etiketleri, yer tutucuları ve ön doldurduğun değerleri okur; kuralı tutan form automod_blocked ile reddedilir.
Metin girişi
| Alan | Kural |
|---|---|
type | 4 |
custom_id | Zorunlu, en çok 100 karakter |
style | 1 tek satır, 2 paragraf |
label | Zorunlu, en çok 45 karakter |
placeholder | Boşken görünen yazı, en çok 100 karakter |
min_length | 0-4000, varsayılan 0 |
max_length | 1-4000, varsayılan 4000 |
required | Varsayılan true |
value | Form açılırken alanda dolu görünen metin, en çok max_length; kişi değiştirebilir |
Zorunlu alana yalnız boşluk yazılırsa gönderim reddedilir ve kişi alanı doldurması gerektiğini görür. Bunun dışında metin yazıldığı gibi gelir: baştaki ve sondaki boşluk silinmez, satır sonları korunur. İsteğe bağlı alan boş bırakıldıysa value boş dizedir.
Formda menü
Bir satır metin girişi yerine bir menü de taşıyabilir: metin (3), kullanıcı (5), rol (6), kullanıcı ya da rol (7), kanal (8). Bileşenler sayfasındaki kurallar geçerlidir; formda iki fark vardır:
| Alan | Formda |
|---|---|
label | Zorunlu, en çok 45 karakter |
required | Varsayılan true; false ise kişi menüyü boş bırakabilir, o zaman values boş liste gelir |
disabled | Kullanılmaz |
Sunucu seçilenleri kişinin erişebildiğine göre denetler: kullanıcı sunucunun üyesi, rol bu sunucunun, kanal kişinin görebildiği olmalı. Seçilen kullanıcılar, üyeler, roller ve kanallar data.resolved içinde gelir.
Ön doldurma
value ile kişinin daha önce yazdığını ya da senin bildiğin bir değeri alana koyarsın; kişi onaylar ya da düzeltir. Örneğin bir butondan "düzenle" formu açarken eski metni geri ver:
{ "type": 4, "custom_id": "aciklama", "style": 2, "label": "Açıklama", "value": "Eski açıklama burada." }Kullanıcı, rol ve kanal menüsünde default_values aynı işi görür.
Gönderim sana nasıl gelir
{
"id": "111804206284800777",
"application_id": "14631410073600008",
"type": 5,
"token": "eyJpZCI6IjExMTgwNDIwNjI4NDgwMDc3NyJ9.bWFj",
"version": 1,
"guild_id": "21531879014400001",
"channel_id": "21531887403008007",
"member": {
"user": { "id": "14631410073600003", "username": "ayse", "display_name": "Ayşe", "avatar": null, "cosmetics": null },
"nick": null, "roles": [], "joined_at": "2026-10-01T12:00:00Z", "permissions": "2147483648"
},
"app_permissions": "2048",
"locale": "tr",
"data": {
"custom_id": "sikayet",
"components": [
{ "type": 1, "components": [{ "type": 4, "custom_id": "konu", "value": "Spam yapan biri" }] },
{ "type": 1, "components": [{ "type": 4, "custom_id": "detay", "value": "Genel kanalda\nreklam atıyor." }] },
{ "type": 1, "components": [{ "type": 5, "custom_id": "kim", "values": ["14631410073600005"] }] }
],
"resolved": {
"users": { "14631410073600005": { "id": "14631410073600005", "username": "spamci", "display_name": null, "avatar": null, "cosmetics": null } }
}
}
}| Alan | Anlamı |
|---|---|
type | 5: gönderilen form |
token | Bu gönderimin kendi token'ı, 15 dakika geçerli; yanıt ve takip mesajları bununla |
data.custom_id | Açtığın formun custom_id'si |
data.components | Formun her alanı kendi sırasıyla, doldurulmuş ya da boş; metin girişi value, menü values taşır |
data.resolved | Menülerde seçilen kullanıcı, üye, rol ve kanal; menü yoksa yer almaz |
member ya da user, locale | Gönderen kişi ve dili |
Formu bir komut açtıysa yanıtın mesajı "X, /komut kullandı" diye gösterilir.
Yanıt seçenekleri
| Etkileşim | type: 4 | type: 5 | type: 6 | type: 7 | type: 9 |
|---|---|---|---|---|---|
Komut çalıştırma (type: 2) | Evet | Evet | Hayır | Hayır | Evet |
Butona basma, menüden seçme (type: 3) | Evet | Evet | Evet | Evet | Evet |
Otomatik tamamlama (type: 4) | Hayır | Hayır | Hayır | Hayır | Hayır |
Komuttan açılmış formun gönderimi (type: 5) | Evet | Evet | Hayır | Hayır | Hayır |
Butondan açılmış formun gönderimi (type: 5) | Evet | Evet | Evet | Evet | Hayır |
type: 7 butondan açılmış formda butonun durduğu mesajı günceller; o mesaj ephemeral ise ephemeral mesaj bütünüyle değişir. flags: 64 ile 4 ya da 5 yanıtı yalnızca gönderen görür. Ayrıntılar Etkileşimler sayfasında.
Kurallar
Formu açan bir form açamaz. Gönderilen forma
type: 9ile yanıt veremezsin. Başka soruların varsa ilk formu en çok beş alanla tasarla ya da yanıtına bir buton koy; buton yeni bir form açabilir.Formu açmak ilk yanıttır. Önce
type: 5ya datype: 6verip sonra form açamazsın: 3 saniye içinde doğrudantype: 9gönder. Formu hazırlamak için dışarıya gitme; tanımı kodunda hazır tut.Form yazmaz.
type: 9kanala mesaj yazmaz ve tüm botların ortak yazma tavanına sayılmaz.Bir form üç kez gönderilebilir. Botun yanıt vermezse kişi yazdığını kaybetmeden yeniden gönderebilir. Her gönderim sana ayrı bir etkileşim olarak gelir; aynı içerik ikinci kez gelirse işini iki kez yapma. Üçüncüden sonra kişi
modal_expiredalır ve formu yeniden açması gerekir.Gönderimde her şey yeniden denetlenir. Kişi kanala hâlâ erişebilmeli, zaman aşımında olmamalı, botun sunucuda olmalı ve form bir komuttan açıldıysa komutun izin kuralları kişiye hâlâ izin vermeli. Form sen açtığından beri değişmemiştir: kişi alan ekleyemez,
custom_iddeğiştiremez.Yazılan saklanmaz. N'oluyo formun içeriğini saklamaz; yalnızca kişinin bir form gönderdiğini erişim kaydına yazar. İçerik sana gelir; yalnızca işin için kullan.
Tam örnek: şikayet formu botu
Node.js 22 veya üstü yeter. Gateway sayfasındaki gateway.mjs ile Örnekler sayfasındaki api.mjs aynı klasörde olmalı. Bot /sikayet komutunu kaydeder, form açar, gönderilen şikayeti moderatör kanalına yazar ve kişiye yalnızca onun göreceği bir teşekkür gönderir:
import { connect } from "./gateway.mjs";
import { call } from "./api.mjs";
// Moderatörlerin gördüğü kanalın kimliği.
const modChannel = process.env.MOD_CHANNEL_ID;
const commands = [{
name: "sikayet",
name_localizations: { en: "report" },
description: "Moderatörlere şikayet gönder",
description_localizations: { en: "Send a report to the moderators" },
}];
const form = (en) => ({
custom_id: "sikayet",
title: en ? "Report" : "Şikayet",
components: [
{ type: 1, components: [{ type: 5, custom_id: "kim", label: en ? "Who?" : "Kim?" }] },
{ type: 1, components: [{ type: 4, custom_id: "konu", style: 1, label: en ? "Subject" : "Konu", max_length: 100 }] },
{ type: 1, components: [{ type: 4, custom_id: "detay", style: 2, label: en ? "What happened?" : "Ne oldu?",
required: false, max_length: 1500 }] },
],
});
// Gönderilen formun alanları: custom_id -> value ya da values.
function fields(data) {
const out = {};
for (const row of data.components) for (const f of row.components) out[f.custom_id] = f.value ?? f.values;
return out;
}
async function onInteraction(d) {
const path = `/interactions/${d.id}/${d.token}/callback`;
const en = d.locale === "en";
if (d.type === 2 && d.data.name === "sikayet") {
// Formu açmak ilk yanıttır: önce 5 verilmez.
await call("POST", path, { type: 9, data: form(en) });
return;
}
if (d.type !== 5 || d.data.custom_id !== "sikayet") return;
const f = fields(d.data);
const who = d.data.resolved?.users?.[f.kim[0]];
const from = d.member?.user ?? d.user;
await call("POST", path, { type: 5, data: { flags: 64 } });
try {
await call("POST", `/channels/${modChannel}/messages`, {
embeds: [{
title: f.konu,
description: f.detay || "-",
fields: [
{ name: "Şikayet edilen", value: `<@${who.id}>`, inline: true },
{ name: "Gönderen", value: `<@${from.id}>`, inline: true },
],
}],
allowed_mentions: { parse: [] },
});
await call("PATCH", `/interactions/${d.id}/${d.token}/messages/@original`, {
content: en ? "Thanks, the moderators got your report." : "Teşekkürler, şikayetin moderatörlere ulaştı.",
});
} catch (err) {
console.error(err.message);
await call("PATCH", `/interactions/${d.id}/${d.token}/messages/@original`, {
content: en ? "Your report could not be delivered. Try again later." : "Şikayetin iletilemedi. Biraz sonra yeniden dene.",
});
}
}
let registered = false;
await connect({
intents: 0,
onEvent(t, d) {
if (t === "READY" && !registered) {
registered = true;
call("PUT", `/applications/${d.application.id}/commands`, commands)
.then(() => console.log("/sikayet kaydedildi"))
.catch(console.error);
}
if (t === "INTERACTION_CREATE") onInteraction(d).catch((err) => console.error(err.message));
},
});Çalıştırmak için botunun moderatör kanalında SendMessages ve EmbedLinks izni olsun:
BOT_TOKEN='kopyaladigin_token' MOD_CHANNEL_ID='kanal_kimligi' node sikayet-botu.mjsÖrnek şikayetin içeriğini konsola yazmaz. Moderatör kanalına yazma 3 saniyeyi aşabilir; bu yüzden önce type: 5 ile yanıt verip sonucu @original ile doldurur.
Limitler
| Anahtar | Değer | Saniye | Anlamı |
|---|---|---|---|
title | 45 | Başlık, karakter | |
rows | 5 | Formdaki alan sayısı | |
label | 45 | Alan etiketi, karakter | |
placeholder | 100 | Metin girişinin yer tutucusu, karakter | |
value | 4000 | Metin girişine yazılabilen, karakter | |
bytes | 16384 | Formun JSON'u | |
sends | 3 | 3600 | Bir formun gönderilebileceği sayı ve formun ömrü |
user | 5 | 5 | Kişi başına kanalda basma, komut ve form gönderimi birlikte |
Kişinin gönderimleri basma ve komutlarla aynı sınırları paylaşır; aşılırsa sana bir şey gelmez, kişi 429 alır ve form açık kalır.
Hatalar
Formu açarken sana dönenler:
| Kod | Ne oldu | Ne yap |
|---|---|---|
interaction_response_invalid | type: 9'u otomatik tamamlamaya ya da gönderilen forma verdin, data yok, ya da yanında content, embeds, flags gibi mesaj alanı var | Formu yalnızca komuta ya da basmaya, yalnızca custom_id, title ve components ile gönder. |
validation_failed | Form geçersiz: örneğin başlık yok, beşten çok satır, bir satırda iki alan, etiket 45 karakteri aşıyor | errors alanına bak. Hakkın gitmedi, düzeltip yeniden gönder. |
automod_blocked | Başlık, etiket ya da ön doldurduğun değer sunucunun otomatik moderasyon kuralına takıldı | Metni değiştir. |
interaction_already_acknowledged | Bu etkileşime zaten yanıt verdin, örneğin önce type: 5 | Formu ilk yanıt olarak gönder. |
interaction_timeout | 3 saniye geçti | Formu kodunda hazır tut, açmadan önce dışarıya gitme. |
Kişinin gönderirken alabileceği hatalar sana gelmez:
| Kod | Ne oldu |
|---|---|
modal_expired | Form 1 saatten eski, üç kez gönderildi, ya da başka bir kişinin veya kanalın |
validation_failed | Zorunlu alan boş ya da yalnız boşluk, uzunluk dışında, menüde kişinin erişemediği bir değer |
missing_access, missing_permissions | Kişi kanalı artık göremiyor ya da zaman aşımında |
application_unavailable | Botun sunucudan çıkarıldı ya da kapatıldı |
command_forbidden | Form bir komuttan açıldı ve komutun izin kuralları artık kişiye izin vermiyor |
rate_limited, interaction_busy | Kişinin ya da tüm botların sınırı doldu |
Bu sayfayı yapay zekaya verirsen
Sırada ne var
Komutlar: form açabilen eğik çizgi, kullanıcı ve mesaj komutları
Bileşenler: form açabilen butonlar ve seçim menüleri
Etkileşimler: form gönderimine verilen yanıt türleri ve takip mesajları