# Bileşenler

Bileşen, botun mesajına eklediği buton ve menüdür. Bir kişi butona bastığında ya da menüden seçim yaptığında bot bunu `INTERACTION_CREATE` olayı olarak alır. Bu sayfa akışı baştan sona anlatır. Botla açılmış DM'de de aynı çalışır; tek fark etkileşimin `context: 1` ile, `member` yerine `user` ile gelmesi ve kullanıcı menüsünde yalnızca DM'nin iki kişisinin, rol menüsünde hiçbir şeyin seçilememesidir. Bileşenin mesajla nasıl gönderildiği ve JSON sınırları [Embed'ler ve bileşenler](https://noluyo.net/gelistirici/embedler.md) sayfasındadır.

## Akış

```text
Bot                         N'oluyo                         Kişi
 |  POST /messages (components) |                              |
 |----------------------------->|  mesajı gösterir             |
 |                              |----------------------------->|
 |                              |  butona basar                |
 |                              |<-----------------------------|
 |                              |  mesajın şimdiki halinde     |
 |                              |  custom_id'yi denetler       |
 |  INTERACTION_CREATE          |                              |
 |<-----------------------------|                              |
 |  3 saniye içinde yanıtlar    |                              |
 |----------------------------->|                              |
```

Sunucu her basmada mesajın **o anki** bileşenlerine bakar. Bot butonu kaldırdıysa eski ekranda duran butona basmak bota hiçbir şey ulaştırmaz, kişi `component_not_found` alır. Bot olaya gelen `custom_id`'nin kendi gönderdiği bir değer olduğuna bu yüzden güvenebilir.

`INTERACTION_CREATE` için intent gerekmez; bağlantın ne seçmiş olursa olsun gelir. Çok shard'lı botta olay, sunucunun shard'ına gider. Alanların tamamı [Olaylar](https://noluyo.net/gelistirici/olaylar.md#INTERACTION_CREATE) sayfasında.

Olaya nasıl yanıt vereceğin [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md) sayfasında.

## Satır ve buton

Bileşenler eylem satırlarına konur (`type: 1`). Bir mesajda en çok 5 satır, bir satırda en çok 5 buton olur:

```json
{
  "content": "Bu akşam geliyor musun?",
  "components": [
    {
      "type": 1,
      "components": [
        { "type": 2, "style": 3, "label": "Geliyorum", "custom_id": "katilim:evet" },
        { "type": 2, "style": 2, "label": "Gelemem", "custom_id": "katilim:hayir" },
        { "type": 2, "style": 5, "label": "Etkinlik sayfası", "url": "https://example.com/etkinlik" }
      ]
    }
  ]
}
```

Beş stil vardır:

| `style` | Ad | Görünüm | Ne zaman |
|---|---|---|---|
| `1` | Birincil | Vurgu rengi, dolu | Mesajın beklediği asıl eylem |
| `2` | İkincil | Gri, dolu | Yan seçenekler, vazgeçme |
| `3` | Başarılı | Yeşil, dolu | Onay, katılma, kabul |
| `4` | Tehlike | Kırmızı, dolu | Silme, yasaklama, geri dönmeyen işler |
| `5` | Bağlantı | Gri, dışarı oklu | Bir adres açar, bota bir şey gitmez |

Bağlantı butonunda `custom_id` olmaz, `url` zorunludur. Basıldığında bot olay almaz. Bir butonu kapatmak için `"disabled": true` yaz; kapalı bileşene basma isteği `component_disabled` ile döner.

## Menüler

Menü bir satırı tek başına doldurur. Dört türü vardır. Hepsinde `custom_id`, `placeholder`, `min_values`, `max_values` ve `disabled` alanları ortaktır. `min_values` ve `max_values` yazılmazsa ikisi de 1 sayılır; `min_values` 0 ise kişi hiçbir şey seçmeden gönderebilir ve `values` boş liste gelir. Menüler bir [formun](https://noluyo.net/gelistirici/formlar.md) içinde de durabilir; orada etiket alırlar.

### Metin menüsü

`type: 3`. Seçenekleri sen yazarsın, bot seçilen `value` değerlerini alır:

```json
{
  "type": 1,
  "components": [
    {
      "type": 3,
      "custom_id": "renk",
      "placeholder": "Renk seç",
      "max_values": 2,
      "options": [
        { "label": "Kırmızı", "value": "kirmizi" },
        { "label": "Mavi", "value": "mavi", "description": "Sakin olan" }
      ]
    }
  ]
}
```

Bot şunu alır:

```json
{ "custom_id": "renk", "component_type": 3, "values": ["kirmizi", "mavi"] }
```

Bir seçeneğe `"default": true` yazarsan menü o seçenek işaretli açılır; kişi değiştirmeden gönderebilir.

Listede olmayan bir değer, tekrar eden bir değer ya da `min_values` ve `max_values` dışında bir sayı `validation_failed` ile döner ve bota ulaşmaz.

### Kullanıcı menüsü

`type: 5`. Kişi sunucunun üyeleri arasından seçer. `default_values` ile baştan seçili kişiler verebilirsin:

```json
{
  "type": 1,
  "components": [
    { "type": 5, "custom_id": "uyar", "placeholder": "Kimi uyaralım?", "default_values": [{ "id": "14631410073600003", "type": "user" }] }
  ]
}
```

Bot, `values` içinde kimlikleri, `resolved` içinde kişilerin bilgisini alır:

```json
{
  "custom_id": "uyar",
  "component_type": 5,
  "values": ["14631410073600003"],
  "resolved": {
    "users": { "14631410073600003": { "id": "14631410073600003", "username": "ayse", "display_name": "Ayşe", "avatar": null, "cosmetics": null } },
    "members": { "14631410073600003": { "user": { "id": "14631410073600003", "username": "ayse", "display_name": "Ayşe", "avatar": null, "cosmetics": null }, "nick": null, "roles": [], "joined_at": "2026-11-01T10:00:00Z", "mute": false, "deaf": false, "communication_disabled_until": null } }
  }
}
```

Sunucunun üyesi olmayan biri seçilemez. `resolved`, seçilen kişi için `GUILD_MEMBERS` intent'in olmasa da gelir. DM'de `members` yer almaz.

### Rol menüsü

`type: 6`. Kişi sunucunun rolleri arasından seçer:

```json
{ "type": 1, "components": [{ "type": 6, "custom_id": "rol-ver", "placeholder": "Rol seç" }] }
```

Bot şunu alır:

```json
{
  "custom_id": "rol-ver",
  "component_type": 6,
  "values": ["21531879014400009"],
  "resolved": {
    "roles": { "21531879014400009": { "id": "21531879014400009", "name": "Oyuncu", "color": 3368703, "hoist": false, "mentionable": true, "position": 2, "permissions": "68672", "auto_assign": false } }
  }
}
```

Başka bir sunucunun rolü seçilemez.

### Kanal menüsü

`type: 8`. Kişi yalnızca **kendisinin gördüğü** kanalları seçebilir. `channel_types` listeyi kanal türüne göre daraltır: `0` metin, `2` ses, `4` kategori.

```json
{ "type": 1, "components": [{ "type": 8, "custom_id": "duyuru-kanali", "channel_types": [0] }] }
```

Bot şunu alır:

```json
{
  "custom_id": "duyuru-kanali",
  "component_type": 8,
  "values": ["21531887403008007"],
  "resolved": {
    "channels": { "21531887403008007": { "id": "21531887403008007", "type": 0, "name": "genel", "parent_id": null } }
  }
}
```

Bot o kanalı görmüyor olabilir, bu yüzden `resolved.channels` yalnızca kimlik, tür, ad ve üst kategoriyi taşır. Konu ya da izin ayarları gelmez.

## custom_id tasarımı

`custom_id` en çok 100 karakterdir ve mesajda tekil olmalıdır. Bot yeniden başlasa bile basmayı anlayabilsin diye gereken bilgiyi içine yaz:

- `rol:21531879014400009`: hangi rol olduğu değerin içinde, ek kayıt gerekmez.
- `anket:42:evet`: önce iş, sonra kayıt, sonra seçenek. `:` ile bölmek yeter.
- Kişinin göreceği metin ya da gizli bilgi koyma. Değer mesajla birlikte kanalı görebilen herkesin istemcisine gider.

## Sınırlar

| Sınır | Değer |
|---|---|
| Mesajdaki satır | 5 |
| Satırdaki buton | 5 |
| `custom_id` | 100 karakter |
| Buton yazısı | 80 karakter |
| Metin menüsü seçeneği | 25 |
| Seçenek yazısı ve değeri | 100 karakter |
| `placeholder` | 150 karakter |
| Seçilen değer | 25 |
| Basma, kişi başına kanalda | 5 sn'de 5 |
| Basma, kişi başına tüm kanallarda | 5 sn'de 10 |
| Basma, uygulama başına | saniyede 22 |
| Basma, tüm uygulamalar | saniyede 67 |

Basma sınırları kişiyi korur, botunu cezalandırmaz: dolduğunda `429` yalnızca basan kişiye gider, mesaj akışı etkilenmez. Kişi başına sınırlar `rate_limited` verir; tüm kanallardaki sınır, birkaç hesabın ortak sınırı tek başına doldurmasını önler. Uygulama ve ortak sınır `interaction_busy` kodunu verir; ortak sınır doluyken o anda başka bir bota basan kişi de bu kodu alabilir. Sınırı aşan basma bota ulaşmaz.

Mesajın bileşenleri, embed'leriyle birlikte [yayılma bütçesine](https://noluyo.net/gelistirici/rate-limit.md#embed-ve-bilesenlerin-yayilma-butcesi) sayılır: boyutları sunucunun üye sayısıyla çarpılır. Sunucu büyüdükçe en büyük mesaj küçülür; onu aşan mesaj `max_bytes` ile `400 fanout_too_large` alır.

## Kişi ne görür

> **Not:** Bu bölüm webde gözlenen davranışı anlatır. Masaüstü uygulaması aynı web kodunu kullanır ama ayrıca çalıştırılmadı. Android ve iOS uygulamalarında henüz gözlenmedi.

Butonlar mesajın altında satır satır, uygulamanın kendi buton stilleriyle durur: birincil temanın rengini alır, ikincil gri, başarılı yeşil, tehlike kırmızıdır. Kapalı bileşen soluk görünür ve basılmaz. Bağlantı butonunun yazısının yanında bir bağlantı simgesi olur, üzerine gelince hedefin alan adı görünür. Basınca adres yeni sekmede açılır. Masaüstü uygulamasında, koda göre, varsayılan tarayıcıda açılması gerekir; bu henüz gözlenmedi. Yalnızca `https` adresli bağlantı butonu çizilir.

Kişi basınca buton dönen bir simgeye döner ve bot yanıtlayana kadar o mesajın bütün bileşenleri kapalı kalır: bir mesajda aynı anda tek basma olur. Bot 3 saniye içinde yanıtlamazsa kişi yaklaşık 3,5 saniye sonra bileşenlerin altında "Bot yanıt vermedi." görür. Botun çevrimdışıysa olay kaybolur ve kişi aynı metni görür: olaylar bot bağlanınca sonradan gönderilmez. Hata metni 5 saniye sonra kendiliğinden gider.

Menü satırı boydan boya kaplar ve seçim yokken `placeholder`'ı gösterir; yazmadıysan "Seçim yap" yazar. `max_values` değeri 1 olan metin menüsü uygulamanın açılır listesidir, seçim yapılır yapılmaz gönderilir. Çok seçimli metin menüsü ile kullanıcı, rol ve kanal menüleri bir pencerede liste açar: kişi seçenekleri işaretler ve "Onayla"ya basar. `min_values`'tan azıyla "Onayla" kapalı kalır, `max_values` dolunca başka seçenek işaretlenemez. Seçeneğin `emoji`'si adının önünde, `description`'ı altında küçük yazıyla görünür. Değerler bota listedeki sırayla gider.

Kullanıcı, rol ve kanal menüsünün penceresinde arama kutusu olur. Kullanıcı menüsü sunucunun üyelerini, rol menüsü `@everyone` dışındaki rolleri en üstteki rolden başlayarak listeler. Kanal menüsünde yalnızca kişinin gördüğü ve `channel_types`'a uyan kanallar çıkar. Gönderdikten sonra menü seçilenlerin adlarını gösterir. Mesaj güncellenince menüler yeniden `default` ve `default_values` ile başlar.

Klavyeyle de kullanılır: `Tab` bileşenler arasında gezer, `Enter` ya da boşluk basar. Açılır listede oklar seçeneği değiştirir, pencerede `Enter` ya da boşluk işaretler, `Esc` kapatır.

Android uygulamasında, koda göre, her menü ekranın altından açılan bir panelde listelenir; bu davranış telefonda henüz gözlenmedi. Tek seçim isteyen menüde (`min_values` ve `max_values` 1) seçeneğe dokunmak onu hemen gönderir, öbürlerinde "Onayla" vardır. Butonların dokunma alanı en az 48 dp'dir. Bağlantı butonu adresi telefonun tarayıcısında açar.

Bot `type: 7` ile yanıt verince mesaj kişinin ekranında yerinde değişir: kapattığın buton soluklaşır, kaldırdığın buton gider.

Şunlar basmayı daha baştan durdurur ve bota hiçbir şey gitmez. Kişi hatayı bileşenlerin altında görür:

| Durum | Yanıt | Kişinin gördüğü |
|---|---|---|
| Kanalı görmüyor | `403 missing_access` | "Bu kanalı görme yetkin yok." |
| Zaman aşımında | `403 missing_permissions` | "Bunun için yetkin yok." |
| Bot butonu kaldırmış | `404 component_not_found` | "Bu bileşen artık geçerli değil." |
| Bileşen kapalı | `400 component_disabled` | "Bu bileşen kapalı." |
| Bot sunucudan çıkmış ya da uygulama silinmiş | `409 application_unavailable` | "Bu mesajı gönderen bot artık burada değil." |
| Ephemeral mesajın süresi dolmuş | `400 interaction_expired` | "Bu etkileşimin süresi doldu." |
| Kişi çok hızlı basıyor | `429 rate_limited` | "Çok hızlı gidiyorsun, biraz yavaşla." |
| Uygulama ya da ortak sınır dolu | `429 interaction_busy` | "Bot şu an yoğun. Biraz sonra yeniden dene." |

Ekranı henüz güncellenmemiş biri botunun kaldırdığı bir butona basarsa basma bota ulaşmaz, kişi "Bu bileşen artık geçerli değil." görür. Uygulama `429`'u beklemez ve yeniden denemez: geç kalan bir basma botunun 3 saniyesini zaten kaçırmış olur.

Kanalı gören herkes basabilir, `SendMessages` gerekmez: salt okunur bir duyuru kanalındaki rol butonu böyle çalışır.

Yalnızca basan kişinin göreceği butonlu mesaj için [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md#yalnizca-basan-kisi-gorsun) sayfasına bak.

## Yetkiyi sen denetlersin

> **Uyarı:** Sunucu `custom_id`'nin mesajda gerçekten olduğunu doğrular, ama basan kişinin o işi yapmaya yetkili olduğunu doğrulamaz. Kanalı gören herkes "Yasakla" butonuna basabilir. İş yetki istiyorsa `member.permissions`'a bak.

`member.permissions` kişinin bu kanaldaki izinleridir, ondalık metin olarak gelir. `BigInt` ile denetle:

```js
const BAN_MEMBERS = 1n << 2n;

if (!(BigInt(d.member.permissions) & BAN_MEMBERS)) {
  // Yetkisi yok: işi yapma.
}
```

`app_permissions` de botun kendi izinleridir. DM'de `member` yerine `user` gelir ve izinler DM'in sabit izinleridir.

## Tam örnek: rol seçme botu

Node.js 22 veya üstü yeter. [Gateway](https://noluyo.net/gelistirici/gateway.md#tam-bir-istemci) sayfasındaki `gateway.mjs` ile [Örnekler](https://noluyo.net/gelistirici/ornekler.md) sayfasındaki `api.mjs` aynı klasörde olmalı. Botun `ManageRoles` izni olmalı ve rolü kendi rolünün altında kalmalı. Bot her açıldığında kanala bir buton atar, basan kişiye rolü verir, yeniden basana geri alır. Ekranda değişen bir şey olmadığı için yanıtı `type: 6`'dır:

```js title=rol-botu.mjs
import { connect } from "./gateway.mjs";
import { call } from "./api.mjs";

const { CHANNEL_ID, ROLE_ID } = process.env;
if (!CHANNEL_ID || !ROLE_ID) throw new Error("CHANNEL_ID ve ROLE_ID tanımlı olmalı");

let posted = false;

async function onPress(d) {
  // Sunucu custom_id'yi doğruladı: içindeki rol bizim yazdığımız roldür.
  const [kind, roleId] = d.data.custom_id.split(":");
  if (kind !== "rol" || !d.member) return;
  await call("POST", `/interactions/${d.id}/${d.token}/callback`, { type: 6 });
  const path = `/guilds/${d.guild_id}/members/${d.member.user.id}/roles/${roleId}`;
  await call(d.member.roles.includes(roleId) ? "DELETE" : "PUT", path);
}

await connect({
  intents: 0,
  onEvent(t, d) {
    if (t === "READY" && !posted) {
      posted = true;
      call("POST", `/channels/${CHANNEL_ID}/messages`, {
        content: "Rolü almak ya da bırakmak için bas.",
        components: [{ type: 1, components: [
          { type: 2, style: 1, label: "Oyuncu", custom_id: `rol:${ROLE_ID}` },
        ] }],
      }).catch(console.error);
    }
    if (t === "INTERACTION_CREATE") onPress(d).catch(console.error);
  },
});
```

Çalıştırmak için:

```bash
BOT_TOKEN='kopyaladigin_token' CHANNEL_ID='kanal id degeri' ROLE_ID='rol id degeri' node rol-botu.mjs
```

## Sık hatalar

- **Olay hiç gelmiyor.** Bot bağlı mı? Çevrimdışı bota olay sonradan gönderilmez. Çok shard'lı botta olay sunucunun shard'ına gider, her shard'ı dinle.
- **`component_not_found` alan kişiler var.** Bot mesajı düzenleyip `custom_id`'yi değiştirmiş; ekranı güncellenmemiş kişi eski değere basıyor. Bu doğru davranıştır.
- **`custom_id` iki kez kullanılmış.** Mesaj gönderilirken `validation_failed` döner; değer mesajda tekil olmalıdır.
- **Yönetim butonuna herkes basabiliyor.** Yetkiyi sunucu denetlemez, `member.permissions`'a bak.
- **Rol verilemiyor.** Botun `ManageRoles` izni yoksa ya da rol botun rolünün üstündeyse REST isteği `403` döner.
- **`values` içinde kimlik yerine başka bir şey bekleniyor.** Kullanıcı, rol ve kanal menülerinde `values` kimliklerdir; ayrıntı `resolved` içindedir.

## Bu sayfayı yapay zekaya verirsen

> **İpucu:** Bu sayfayı bir yapay zeka asistanına verip kod yazdırıyorsan şu kuralları da ekle:
>
> - Token koda yazılmaz, `process.env.BOT_TOKEN`'dan okunur.
> - `INTERACTION_CREATE` gelince ilk yanıt 3 saniye içinde gider. Önce yanıt verilir, uzun iş ondan sonra yapılır.
> - Sunucu `custom_id`'nin mesajda olduğunu doğrular, basanın yetkisini doğrulamaz. Yetki isteyen işte `member.permissions` `BigInt` ile denetlenir.
> - Gereken bilgi `custom_id`'ye yazılır; gizli bilgi konmaz, çünkü kanalı gören herkesin istemcisine gider.
> - Sınırlar: mesajda en çok 5 satır, satırda 5 buton ya da tek menü; `custom_id` 100, buton yazısı 80 karakter; en çok 25 seçenek, seçenek yazısı ve değeri 100, `placeholder` 150 karakter.
> - Bağlantı butonu (`style: 5`) `url` alır, `custom_id` almaz ve bota olay göndermez.
> - Bot çevrimdışıyken yapılan basma sonradan gelmez.
> - Kullanıcı, rol ve kanal menüsünde `values` kimliklerdir; ayrıntı `resolved` içindedir.

## Sırada ne var

- [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md): butona ve menüye basıldığında gelen olayın yanıt türleri
- [Formlar](https://noluyo.net/gelistirici/formlar.md): buton tıklamasıyla en çok beş alanlı pencere açmak
- [Hazır tarifler](https://noluyo.net/gelistirici/tarifler.md): butonlu rol seçici ve anket örnekleri
