# Otomatik tamamlama

Otomatik tamamlama, kişi bir komut seçeneğine yazarken botunun ona öneriler sunmasıdır: sunucu yazılanı botuna gönderir, botun en çok 25 öneriyle yanıt verir ve kişi listeden seçer ya da kendi yazdığını gönderir.

> **Uyarı:** Kişinin bitmemiş yazısı, Enter'a basmadan sana gelir. Onu yalnızca öneri bulmak için kullan: saklama, loglama, başka bir yere gönderme. Kişi yazarken fikrini değiştirebilir; yazdığı her harf bir karar değildir.

## Akış

| Adım | Ne olur |
|---|---|
| 1 | Seçeneği `autocomplete: true` ile tanımlarsın. Yalnızca metin (`3`), tam sayı (`4`) ve sayı (`10`) seçeneklerinde olur, `choices` ile birlikte olmaz. |
| 2 | Kişi o seçeneğe yazar. Uygulaması yazma durduktan 300 ms sonra yazılanı sunucuya gönderir; kişi yazdıkça sana 300 ms'de bir yeni istek gelebilir. |
| 3 | Sana `INTERACTION_CREATE` gelir, `type: 4`. `data.options` içinde `focused: true` olan seçenek kişinin yazdığı yerdir. |
| 4 | 3 saniye içinde `callback` ile `type: 8` ve `data.choices` gönderirsin. Her otomatik tamamlama tek yanıt alır. |
| 5 | Öneriler yalnızca yazan kişiye gider. Kişi birini seçer ya da kendi yazdığıyla devam eder. Enter'a basınca komut sana her zamanki gibi `type: 2` olarak gelir. |

Geliştirici `autocomplete` açmadıkça kişinin yazdığı Enter'dan önce hiçbir bota gitmez.

## 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 uygulamasında henüz gözlenmedi.

Kişi alana yazar; yazma 300 ms durunca önerilerin alanın üstünde bir listede açılır, her öneri yerelleştirilmiş adıyla (`name_localized`) görünür. `↑` ve `↓` öneri değiştirir, `Enter` ya da `Tab` seçer, `Esc` listeyi kapatır, tıklamak da seçer. Seçilen önerinin adı alanda görünür, sana çalıştırmada `value`'su gelir. Kişi sonra yeniden yazarsa değer onun yazdığına döner.

Kişi yazdıkça eski yazıya ait geç gelen yanıt listeyi değiştirmez; yalnızca son isteğin yanıtı gösterilir. Botun 1 saniye geç yanıt verse de öneriler gelir. Botun hiç yanıt vermezse ya da çevrimdışıysa liste açılmaz, kişiye hata gösterilmez ve alan serbest yazıya açık kalır: kişi Enter'a basınca komut yazdığı metinle gelir. Bu yüzden çalıştırmada değeri her zaman yeniden doğrula.

Odaktaki alanla birlikte yalnızca geçerli değeri olan diğer seçenekler gelir; yarım yazılmış sayı ya da dosya gelmez.

## En küçük örnek

Seçeneği tanımla:

```json
{
  "name": "hava",
  "description": "Bir şehrin havası",
  "options": [
    { "type": 3, "name": "sehir", "description": "Şehir", "required": true, "autocomplete": true }
  ]
}
```

Kişi `ist` yazınca sana şu gelir (kısaltıldı):

```json
{
  "id": "111804206284800120",
  "type": 4,
  "token": "eyJpZCI6IjExMTgwNDIwNjI4NDgwMDEyMCJ9.bWFj",
  "locale": "tr",
  "data": {
    "id": "111560000000000000",
    "name": "hava",
    "type": 1,
    "options": [{ "name": "sehir", "type": 3, "value": "ist", "focused": true }]
  }
}
```

Önerilerle yanıt ver:

```bash
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":8,"data":{"choices":[{"name":"İstanbul","name_localizations":{"en":"Istanbul"},"value":"istanbul"}]}}'
```

Yanıt `204` ve gövdesizdir.

## Gelen etkileşim

| Alan | Anlamı |
|---|---|
| `type` | `4`: otomatik tamamlama |
| `data.id`, `data.name` | Komutun kimliği ve tanımdaki adı |
| `data.options` | Şimdiye dek yazılanlar, tanımına göre denetlenmiş. Alt komut varsa içindeki `options`'tadır. |
| `data.options[].focused` | Yalnızca kişinin yazdığı tek seçenekte `true` |
| `data.options[].value` | Odaktaki seçenekte bitmemiş yazı: en çok 100 karakter metin, tam sayı ve sayı seçeneğinde sayı ya da `"1."` gibi metin |
| `data.resolved` | Kişinin diğer seçeneklerde seçtiği kullanıcı, rol, kanal |
| `member`, `locale` | Yazan kişi ve dili; DM'de `member` yerine `user` |
| `context` | `0` sunucu, `1` botla DM. DM'de de aynı çalışır; tek fark kişinin `user`'da olmasıdır. |
| `token` | Yalnızca `callback` için. Takip mesajı ve düzenleme almaz. |

Zorunlu seçenekler henüz eksik olabilir. Dosya seçeneği otomatik tamamlamada gönderilmez; dosya ancak çalıştırmada gelir.

## Yanıt: type 8

| Alan | Kural |
|---|---|
| `choices` | Zorunlu, en çok 25 öğe. Boş liste "eşleşen yok" demektir. |
| `choices[].name` | Kişinin gördüğü, en çok 100 karakter |
| `choices[].name_localizations` | `tr` ve `en` karşılığı; kişi kendi dilindekini görür |
| `choices[].value` | Odaktaki seçeneğin türünde; metin en çok 100 karakter. Seçilince bu gider, ad değil. |

`type: 8` yalnızca otomatik tamamlamaya, otomatik tamamlama yalnızca `type: 8`'e uyar. Öneriler kanala yazılmaz, mesaj değildir; bu yüzden tüm botların saniyede 70 yazmalık [ortak tavanına](https://noluyo.net/gelistirici/rate-limit.md) sayılmaz. Yine `interaction` sınıfına ve aynı anda 8 bot isteği sınırına girer.

## Değeri çalıştırırken yeniden doğrula

Kişi listeden seçmek zorunda değildir: kendi yazdığını gönderebilir, öneri gelmeden Enter'a basabilir ya da başka bir istemci kullanabilir. Sunucu çalıştırmadaki değeri önerilerine karşı **denetlemez**. `type: 2` gelince değeri kendi kaynağında yeniden ara; bulamazsan kişiye `flags: 64` ile ne yapacağını söyle. Önerdiğin bir değerin hâlâ geçerli olduğunu da varsayma: liste o arada değişmiş olabilir.

## Hızlı kal

- 3 saniye üst sınırdır; kişi o sırada yazmaya devam ediyor. 300 ms içinde yanıt vermeyi hedefle.
- Uygulama yalnızca son yazının yanıtını gösterir. Eski bir yazıya geç gelen yanıt atılır; yanıt vermezsen alan serbest yazıma açık kalır.
- Listeni bellekte tut, her istekte veritabanına ya da dış bir servise gitme. Dış kaynağa gitmen gerekiyorsa sonucu kısa süre önbellekle ve aynı yazı için kopya sorgu yapma.
- Hata gelince yeniden deneme: 3 saniye geçtiyse yanıt artık işe yaramaz. Sıradaki yazı zaten gelecek.

## Yerelleştirme

`locale` kişinin dilidir. Öneriyi `name` ile Türkçe yaz, `name_localizations.en` ile İngilizcesini ver; kişi kendi dilindekini görür. `value` dilden bağımsız kalsın ki çalıştırmada aynı değeri tanıyasın. Türkçe harfleri karşılaştırırken `toLocaleLowerCase("tr")` kullan: `"İST"` küçültülünce `"ist"` olur.

## Tam örnek: şehir arama 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ı. Bot açılınca `/hava` komutunu kaydeder; kişi şehir yazdıkça eşleşen şehirleri önerir, çalıştırılınca şehri listede yeniden arar:

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

const cities = [
  { value: "istanbul", tr: "İstanbul", en: "Istanbul" },
  { value: "izmir", tr: "İzmir", en: "Izmir" },
  { value: "ankara", tr: "Ankara", en: "Ankara" },
  { value: "bursa", tr: "Bursa", en: "Bursa" },
  { value: "antalya", tr: "Antalya", en: "Antalya" },
  { value: "eskisehir", tr: "Eskişehir", en: "Eskisehir" },
];

const commands = [{
  name: "hava",
  name_localizations: { en: "weather" },
  description: "Bir şehrin havası",
  description_localizations: { en: "The weather of a city" },
  options: [{ type: 3, name: "sehir", name_localizations: { en: "city" }, description: "Şehir",
    description_localizations: { en: "City" }, required: true, autocomplete: true }],
}];

const fold = (s) => s.toLocaleLowerCase("tr");
// Aynı yazı ikinci kez gelince liste yeniden taranmaz.
const cache = new Map();
function search(typed) {
  const key = fold(String(typed)).trim();
  if (!cache.has(key)) {
    if (cache.size > 1000) cache.clear();
    cache.set(key, cities.filter((c) => fold(c.tr).startsWith(key) || c.en.toLowerCase().startsWith(key)).slice(0, 25));
  }
  return cache.get(key);
}

// Odaktaki seçenek alt komutun içinde de olabilir.
const focused = (options = []) => options.find((o) => o.focused) ?? options.map((o) => focused(o.options)).find(Boolean);

async function onInteraction(d) {
  if (d.data.name !== "hava") return;
  const path = `/interactions/${d.id}/${d.token}/callback`;
  if (d.type === 4) {
    const choices = search(focused(d.data.options)?.value ?? "").map((c) => ({
      name: c.tr, name_localizations: { en: c.en }, value: c.value,
    }));
    await call("POST", path, { type: 8, data: { choices } });
    return;
  }
  if (d.type !== 2) return;
  // Kişi listeden seçmek zorunda değil: değer burada yeniden doğrulanır.
  const value = d.data.options?.find((o) => o.name === "sehir")?.value;
  const city = cities.find((c) => c.value === value) ?? search(value)[0];
  const en = d.locale === "en";
  if (!city) {
    await call("POST", path, { type: 4, data: { flags: 64,
      content: en ? "I do not know that city. Pick one from the list." : "Bu şehri bilmiyorum. Listeden birini seç." } });
    return;
  }
  await call("POST", path, { type: 4, data: { content: en ? `Weather in ${city.en}: sunny.` : `${city.tr} için hava: güneşli.` } });
}

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("/hava kaydedildi"))
        .catch(console.error);
    }
    if (t === "INTERACTION_CREATE") onInteraction(d).catch((err) => console.error(err.message));
  },
});
```

Çalıştırmak için:

```bash
BOT_TOKEN='kopyaladigin_token' node sehir-botu.mjs
```

Örnek kişinin yazdığını konsola yazmaz; hata mesajında da yalnızca kod ve açıklama vardır.

## Limitler

| Anahtar | Değer | Saniye | Anlamı |
|---|---|---|---|
| `user` | 5 | 1 | Kişi başına otomatik tamamlama isteği |
| `app` | 20 | 1 | Uygulaman başına, tüm kişilerden |
| `shared` | 200 | 1 | Tüm botlar birlikte |
| `choices` | 25 | | Yanıt başına öneri |
| `autocomplete_value` | 100 | | Odaktaki yazı, karakter |

İlk üçü kişinin isteğini sınırlar: aşılırsa sana bir şey gelmez, kişi `429` alır ve uygulaması bir sonraki yazıda yeniden dener. İnsanların mesajlaşması bundan etkilenmez.

Öneri yanıtların gerçek yanıtlarınla aynı bütçeyi paylaşır: `callback` rotası `interaction` sınıfındadır (saniyede 50) ve botunun genel sınırına (saniyede 50) sayılır. Bu yüzden uygulama sınırı 20'dedir; öneriler bütçenin tamamını yemez, komut ve basma yanıtlarına yer kalır. Öneri yanıtlarını hızlı ve tek tut ki bütçe gerçek yanıtlara kalsın.

## Hatalar

| Kod | Ne oldu | Ne yap |
|---|---|---|
| `interaction_response_invalid` | Otomatik tamamlamaya `type: 8` dışında yanıt verdin, `type: 8`'de `choices` yok ya da yanında `content`, `embeds`, `flags` gibi mesaj alanı var, başka bir etkileşime `type: 8` gönderdin ya da bu token'la takip mesajı veya düzenleme denedin | Otomatik tamamlamaya yalnızca `{"type":8,"data":{"choices":[...]}}` gönder. |
| `validation_failed`, `choices`: `too_long` | 25'ten çok öneri | İlk 25'i gönder. |
| `validation_failed`, `choices[0].value`: `invalid` | Değer seçeneğin türünde değil, örneğin metin seçeneğine sayı | Seçeneğin türünde değer gönder. |
| `validation_failed`, `choices[0].name`: `too_long` | Ad 100 karakteri aşıyor | Adı kısalt. |
| `interaction_already_acknowledged` | Aynı yazıya ikinci kez yanıt verdin | Her otomatik tamamlamaya bir kez yanıt ver. |
| `interaction_timeout` | 3 saniye geçti | Listeyi bellekte tut, önbellekle. Yeniden deneme. |
| `rate_limited`, `X-RateLimit-Scope: shared` | Aynı anda 8 bot isteği sınırı doldu | Bu yazıyı bırak; sıradaki gelecek. |
| `rate_limited`, `X-RateLimit-Scope: bot` ya da `global` | Botunun `interaction` sınıfı ya da genel sınırı doldu; öneriler ve gerçek yanıtlar aynı bütçeden harcar | Öneriyi bırak, bekleme. Gerçek yanıtlar için `retry_after` kadar bekle. |

Kişinin alabileceği hatalar (`validation_failed`, `command_forbidden`, `command_outdated`, `rate_limited`, `interaction_busy`) sana gelmez; [Hatalar](https://noluyo.net/gelistirici/hatalar.md#komut-calistirma) sayfasında.

## 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.
> - Otomatik tamamlama `INTERACTION_CREATE` olayında `type: 4` ile gelir ve 3 saniye içinde tek bir `{"type":8,"data":{"choices":[...]}}` ile yanıtlanır.
> - Yazılan, `data.options` içinde `focused: true` olan seçeneğin `value`'sudur; alt komutun `options`'ında da olabilir. Zorunlu seçenekler eksik olabilir.
> - En çok 25 öneri; `value` seçeneğin türünde, metin en çok 100 karakter. Boş liste geçerlidir.
> - Kişinin yazdığı saklanmaz ve loglanmaz.
> - Liste bellekte tutulur ya da önbelleklenir; her tuşta veritabanına veya dış servise gidilmez, aynı yazı için kopya sorgu yapılmaz. Hata gelince yeniden denenmez.
> - Komut çalıştırılınca (`type: 2`) değer yeniden doğrulanır: kişi listeden seçmek zorunda değildir.
> - Otomatik tamamlamanın token'ı takip mesajı ya da düzenleme için kullanılmaz.

## Sırada ne var

- [Komutlar](https://noluyo.net/gelistirici/komutlar.md): seçenek türleri, alt komutlar ve kayıt rotaları
- [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md): komut seçildikten sonra gönderilen asıl yanıt türleri
- [Hız sınırları](https://noluyo.net/gelistirici/rate-limit.md): otomatik tamamlama ve etkileşim hız sınırları
