# Gateway

REST'te programın sorar, N'oluyo yanıtlar. Yeni bir mesaj yazıldığını öğrenmek için sormak gerekseydi kanalı durmadan okuman gerekirdi. Gateway bunu tersine çevirir: programın açık duran tek bir WebSocket bağlantısı kurar, sunucularda bir şey olduğunda haber kendiliğinden gelir.

Botun iki yolu birlikte kullanır: **gateway'den duyar, REST ile iş yapar.** Mesaj göndermek, silmek ya da rol vermek yine [REST](https://noluyo.net/gelistirici/api.md) isteğidir.

Bu sayfa bir bağlantının başından sonuna ne olduğunu sırayla anlatır. Sayfanın sonunda kopyalayıp kullanabileceğin bir istemci var. Her olayın alanları ve örnekleri [Olaylar](https://noluyo.net/gelistirici/olaylar.md) sayfasında.

> **Not:** Örnekler düz `WebSocket` kullanır. Node.js 22 ve üstünde `WebSocket` hazırdır, ek paket gerekmez. Sürümünü `node --version` ile görebilirsin.

## Akışa kısa bakış

1. `GET /gateway/bot` ile bağlanacağın adresi öğrenirsin.
2. O adrese WebSocket açarsın. İlk gelen çerçeve `op 10` (HELLO) olur ve heartbeat aralığını söyler.
3. O aralıkla `op 1` (heartbeat) göndermeye başlarsın.
4. `op 2` (identify) ile kim olduğunu ve hangi olayları istediğini söylersin.
5. `READY` gelir, ardından sunucuların tek tek `GUILD_CREATE` olarak akar.
6. Olaylar gelmeye başlar. Her olayın sıra numarasını saklarsın.
7. Bağlantı koparsa yeniden bağlanıp `op 6` (resume) ile kaldığın yerden sürersin.

## Çerçeveler

Her iki yönde de her mesaj, metin olarak yazılmış tek bir JSON nesnesidir:

```json
{ "op": 0, "d": { "id": "21531879014400001" }, "s": 42, "t": "GUILD_DELETE" }
```

| Alan | Anlamı |
|---|---|
| `op` | Çerçevenin ne olduğu. Tüm opcode'lar [Olaylar](https://noluyo.net/gelistirici/olaylar.md#opcodes) sayfasında. |
| `d` | İçerik. Biçimi `op` değerine ve olaya göre değişir. |
| `s` | Olayın sıra numarası. Yalnızca `op 0` çerçevelerinde dolu olur. |
| `t` | Olayın adı, örneğin `MESSAGE_CREATE`. Yalnızca `op 0` çerçevelerinde dolu olur. |

Senin gönderdiğin bir çerçeve en çok 16 KiB olabilir ve dakikada en çok 120 çerçeve gönderebilirsin, heartbeat'ler dahil. Bu sayıyı aşan bağlantı `4008` ile kapanır.

**Sıkıştırma botlar için yok.** Bağlantı adresine `compress` eklesen de bir şey değişmez; `zstd` botlara sunulmaz. Çerçeveler her zaman düz JSON metnidir, kod çözücü gerekmez.

## 1. Adresi al

Botlar kendi adreslerine bağlanır. Bu adresi koda yazma, her başlangıçta [`GET /gateway/bot`](https://noluyo.net/gelistirici/api-gateway.md#getGatewayBot) ile sor:

```bash
curl "https://noluyo.net/api/v1/gateway/bot" \
  -H "Authorization: Bot $BOT_TOKEN"
```

```json
{
  "url": "wss://noluyo.net/bot-gateway",
  "shards": 1,
  "session_start_limit": {
    "total": 1000,
    "remaining": 998,
    "reset_after": 41230000,
    "max_concurrency": 1
  }
}
```

- `url`: Bağlanacağın adres.
- `shards`: Kaç bağlantı açmanın önerildiği. Çoğu bot için `1`; ayrıntısı [Shard'lar](https://noluyo.net/gelistirici/shardlar.md) sayfasında.
- `session_start_limit`: Bugün kaç oturum başlatabileceğin ve sayacın kaç milisaniye sonra sıfırlanacağı. Bu rotayı okumak oturum hakkı harcamaz.

> **Uyarı:** N'oluyo'nun kendi uygulamalarının kullandığı `/gateway` adresi kişiler içindir: bot token'ını `4004` ile reddeder. Botun için doğru adresi her zaman bu rotadan öğrenirsin.

## 2. Bağlan ve HELLO'yu bekle

```js
const ws = new WebSocket(url);
```

Bağlantı açılınca sunucu hemen `op 10` gönderir:

```json
{ "op": 10, "d": { "heartbeat_interval": 41250 }, "s": null, "t": null }
```

Bundan sonra 10 saniye içinde identify ya da resume göndermelisin, yoksa bağlantı `4003` ile kapanır. Aynı IP adresinden, henüz identify etmemiş en çok 20 bağlantı açık kalabilir; fazlası `429` ile reddedilir.

## 3. Heartbeat gönder

`heartbeat_interval`, milisaniye cinsinden aralıktır. Bu aralıkla `op 1` gönder. `d` alanına son gördüğün `s` değerini koyabilirsin, sunucu onu okumaz:

```js
setInterval(() => ws.send(JSON.stringify({ op: 1, d: seq })), heartbeat_interval);
```

Sunucu her heartbeat'e `op 11` ile yanıt verir. İki durumu bilmen gerekir:

- **Yanıt gelmezse** bağlantı ölmüş demektir, kapanmış gibi görünmese de. Bir sonraki heartbeat zamanı geldiğinde hâlâ yanıt yoksa bağlantıyı bırak, yeniden bağlan ve resume yap.
- **Sen göndermezsen** sunucu aralığın bir buçuk katı kadar bekler, sonra bağlantıyı kapatır. Bu kapanış bir kod taşımaz, sen `1006` görürsün. Oturum sürdürülebilir kalır: yeniden bağlanıp resume yap.

## 4. Identify gönder

Identify, oturumu başlatan çerçevedir:

```json
{
  "op": 2,
  "d": {
    "token": "<token>",
    "intents": 33281,
    "shard": [0, 1],
    "presence": { "status": "online", "activities": [] }
  }
}
```

| Alan | Gerekli mi | Anlamı |
|---|---|---|
| `token` | Evet | Botun token'ı. REST'teki gibi `Bot ` öneki **almaz**. |
| `intents` | Evet | Hangi olayları istediğin, bitlerin toplamı olarak. Buradaki `33281`: `GUILDS` + `GUILD_MESSAGES` + `MESSAGE_CONTENT`. Bkz. [Intent'ler](https://noluyo.net/gelistirici/intentler.md). |
| `shard` | Hayır | `[id, count]`. Verilmezse `[0, 1]` olur. Bkz. [Shard'lar](https://noluyo.net/gelistirici/shardlar.md). |
| `presence` | Hayır | Botun başlangıç durumu: `online`, `idle`, `dnd` ya da `invisible`. |

Her başarılı identify günlük oturum haklarından birini harcar, reddedilen identify harcamaz. Oturumlar en çok 5 saniyede bir başlatılabilir; daha sık denersen bağlantı `4008` ile kapanır.

## 5. READY ve GUILD_CREATE

Identify kabul edilince ilk olay `READY` gelir:

```json
{
  "op": 0,
  "s": 1,
  "t": "READY",
  "d": {
    "v": 1,
    "user": { "id": "21879171579904005", "username": "selam_botu", "bot": true },
    "session_id": "6f1c0e2ab7d94c3f8e5a1d0b9c7e4a26",
    "application": { "id": "21879167385600004" },
    "shard": [0, 1],
    "guilds": [{ "id": "21531879014400001", "unavailable": true }]
  }
}
```

`session_id` değerini sakla, resume için gerekir. `user.id` de işine yarar: bot kendi mesajlarını da duyar ve onlara yanıt vermemek için bu ID ile karşılaştırman gerekir.

`guilds` listesi yalnızca ID'leri verir ve hepsi `unavailable` görünür. Bu, sunucuların kapalı olduğu anlamına gelmez, henüz gönderilmedikleri anlamına gelir. Asıl içerikleri ardından **tek tek** `GUILD_CREATE` olarak gelir, senin okuma hızına göre. Yani `READY` yalnızca neyin geleceğini söyler: listedeki her sunucu için bir `GUILD_CREATE` bekle.

Bu akışta üç şey yeni başlayanları şaşırtır:

- **Bir sunucunun olayları, sırası gelene kadar gelmez.** Sıra ona yaklaşınca başlar. Yani `READY`'den hemen sonra her sunucuyu dinliyor sayılmazsın; o arada yazılan mesajları görmek istersen REST ile okumalısın.
- **Olay, `GUILD_CREATE`'ten önce gelebilir.** Bir sunucunun ilk olayı onun `GUILD_CREATE`'inden hemen önce ulaşabilir. Kodun, tanımadığı bir `guild_id` gördüğünde hata vermemeli.
- **`GUILD_CREATE`, aldığın bir olaydan eski olabilir.** Sunucu önce olayları sana açar, sonra sunucunun o anki halini okur, sonra gönderir. Bu arada olan bir değişiklik önce olay olarak gelir, sonra gelen `GUILD_CREATE` onu içermeyebilir. Bazen de tersi olur: aynı değişiklik hem anlık görüntüde hem olayda bulunur.

Sunucuların bilgisini bellekte tutuyorsan güvenli yol şudur: Bir sunucu için `GUILD_CREATE`'ten önce gelen olayları kenara yaz. `GUILD_CREATE` gelince bilgiyi ondan kur, sonra kenardaki olayları sırayla uygula. Kodunu, aynı olay iki kez uygulansa da bir şey bozulmayacak biçimde yaz: "ekle" yerine "yoksa ekle, varsa güncelle" mantığıyla.

> **Not:** `GUILD_CREATE` almak için `GUILDS` intent'i gerekir. Bu intent'i istemezsen `READY`'deki sunucular için `GUILD_CREATE` bekleme, hiç gelmez. Sunucuların diğer olayları yine sırası gelince başlar.

Bot sonradan bir sunucuya eklenirse aynı `GUILD_CREATE` o an gelir. Çıkarılırsa ya da sunucu silinirse `GUILD_DELETE` gelir. Bu, `READY`'de olup henüz gönderilmemiş bir sunucu için de geçerlidir; bu yüzden tüm sunucuları beklerken `GUILD_DELETE`'i de o sunucunun cevabı say. Aynı sunucu için iki kez gelebilir, ikincisi zarar vermeyecek şekilde yaz. `GUILD_CREATE` üye olarak yalnızca botu listeler; diğer üyeler için aşağıdaki `op 8` var. `GUILD_VOICE_STATES` istemediysen `voice_states` boştur.

## 6. Olayları dinle ve sıra numarasını sakla

Her `op 0` çerçevesindeki `s` değerini sakla, her zaman en son gördüğünü tut. Resume bunu ister.

```js
ws.onmessage = (e) => {
  const { op, d, s, t } = JSON.parse(e.data);
  if (s) seq = s;
  if (op === 0 && t === "MESSAGE_CREATE") console.log(d.author.username, d.content);
};
```

Hangi olayların geldiği [Intent'ler](https://noluyo.net/gelistirici/intentler.md) ile seçtiklerine bağlıdır. `MESSAGE_CREATE`'te `content` alanının boş gelmesi hata değildir, aynı sayfadaki mesaj içeriği kuralına bak.

## 7. Bağlantı koparsa resume yap

Bağlantılar kopar: ağ değişir, sunucu yeniden başlar, programın yavaş kalır. Olay kaçırmamak için yeniden identify etmek yerine oturumu sürdür. Yeni bir bağlantı aç, HELLO'yu bekle, identify yerine şunu gönder:

```json
{
  "op": 6,
  "d": {
    "token": "<token>",
    "session_id": "6f1c0e2ab7d94c3f8e5a1d0b9c7e4a26",
    "seq": 42
  }
}
```

Kaçırdığın olaylar sırayla gelir, ardından `RESUMED` gelir. Resume, oturum hakkı harcamaz. Heartbeat'i yeni bağlantının HELLO'sundaki aralıkla yeniden başlat.

Resume'un sınırları var:

- Oturum, bağlantı koptuktan sonra **2 dakika** bekler. Daha geç kalırsan `op 9` gelir.
- Sunucu oturum başına son 1000 olayı, en çok 2 MiB olacak şekilde saklar. Daha çoğunu kaçırdıysan yine `op 9` gelir.
- Oturum, identify ettiği intent'lerle sürer. Portalda intent açtıysan resume yetmez, yeni identify gerekir.
- Görmediğin `GUILD_CREATE`'ler saklanan kopyadan değil, sunucunun o anki halinden yeniden gönderilir. `GUILD_MEMBERS_CHUNK` ve `RATE_LIMITED` ise hiç gönderilmez; üye listesini yeniden iste.

Kendin kapatırken kapanış koduna dikkat et: `ws.close(1000)` oturumu **bitirir** ve bot çevrimdışı olur. Başka kütüphaneler `1001` de gönderebilir, sunucu onu da aynı sayar. Sonra resume etmek istiyorsan başka bir kod kullan, örneğin `ws.close(4000)`.

## op 7 ve op 9

**`op 7` (RECONNECT):** Sunucu kapanmak üzere. Bağlantıyı kendin kapat, yeniden bağlan, resume yap.

**`op 9` (INVALID_SESSION):** Oturum sürdürülemiyor. `d` her zaman `false` olur. Sakladığın `session_id` ve `seq` değerlerini unut, bellekteki sunucu bilgilerini de eskimiş say. Bağlantı açık kalır: birkaç saniye bekle ve aynı bağlantıda `op 2` gönder. Hemen denersen 5 saniyelik sınıra takılabilirsin.

## Kapanış kodları

Bağlantı kapanınca `onclose` içindeki `e.code` nedenini söyler. Kararını şu tabloya göre ver. Adları ve daha uzun açıklamaları [Olaylar](https://noluyo.net/gelistirici/olaylar.md#close-codes) sayfasında.

| Kod | Anlamı | Yeniden bağlan | Ne yapmalı |
|---|---|---|---|
| `4000` | Sunucuda bir hata oldu | Evet | Resume dene. |
| `4001` | Bilinmeyen ya da botlara kapalı opcode | Hayır | Kodunu düzelt. |
| `4002` | Çerçeve okunamadı ya da değerleri geçersiz | Hayır | Kodunu düzelt. |
| `4003` | Identify'dan önce heartbeat dışında bir şey gönderildi ya da 10 saniye geçti | Hayır | Sırayı düzelt. |
| `4004` | Token yanlış ya da yenilendi, veya bot ya da sahibi kapatıldı | Hayır | Yeni token al, programı yeniden başlat. |
| `4005` | Oturumu olan bağlantıda yeniden identify ya da resume | Hayır | Kodunu düzelt. |
| `4008` | Çok fazla çerçeve ya da çok sık oturum başlatma | Evet | 5 saniye bekle, gönderimini azalt. |
| `4010` | Platform bakımda | Evet | Bir süre bekleyip identify dene. |
| `4011` | Daha yeni bir oturum bunun yerini aldı | Hayır | Botun iki kopyası çalışıyor olabilir, birini durdur. |
| `4012` | `shard` yanlış ya da üst sınırın üstünde | Hayır | `shard` değerini düzelt. |
| `4013` | `intents` yok, sayı değil ya da tanımsız bit içeriyor | Hayır | `intents` değerini düzelt. |
| `4014` | İzni olmayan ayrıcalıklı intent | Hayır | Portaldan aç ya da istemeyi bırak. |
| `4015` | Bu sayı için fazla sunucu | Hayır | Daha çok shard ile bağlan. |
| `4016` | Günlük oturum hakların bitti | Evet | `reset_after` kadar bekle. |

Kapanış kodu olmadan kopan bağlantılar (`1006` gibi) ağ sorunudur ya da sunucu heartbeat'ini zamanında alamamıştır; `1012` (*Service Restart*) ise N'oluyo sunucusu güncellenirken gönderilir ve oturumun sunucu belleğinde korunduğunu gösterir: her ikisinde de `session_id` ile `seq` değerlerini koruyup ~1,5 saniye sonra yeniden bağlanarak `op 6` (`RESUME`) gönder. "Hayır" yazan kodlarda döngüye girme, aynı istek aynı sonucu verir.

## Botun durumunu değiştirmek: op 3

```json
{ "op": 3, "d": { "status": "idle", "activities": [] } }
```

`status`, `online`, `idle`, `dnd` ya da `invisible` olabilir. Bir oturum dakikada 5 kez gönderebilir. Değişiklik, botun tüm shard'lardaki sunucularında görünür.

## Ses kanalına girmek: op 4

```json
{ "op": 4, "d": { "guild_id": "21531879014400001", "channel_id": "21531879014400099", "self_mute": false } }
```

Botu bir ses kanalına sokar; `channel_id: null` gönderirsen kanaldan çıkarır. Yanıt olarak aynı oturuma LiveKit adresini ve 10 dakikalık token'ı taşıyan `VOICE_SERVER_UPDATE`, bir kural ya da sınır engellerse `VOICE_JOIN_ERROR` gelir. Bot seste yalnızca yayın yapar, kimseyi duyamaz. Çalışan örnek ve sınırlar [Ses](https://noluyo.net/gelistirici/ses.md) sayfasında.

## Üye listesi: op 8

Binlerce üyeyi her bota peşin göndermek israf olurdu, bu yüzden `GUILD_CREATE` üye listesi taşımaz. Üye listesine ihtiyacı olan bot onu `op 8` ile ister:

```json
{ "op": 8, "d": { "guild_id": "21531879014400001", "query": "ay", "limit": 10, "nonce": "arama-1" } }
```

`query` ve `user_ids` alanlarından **tam olarak biri** gönderilir:

| İstek | Ne döner | Ne ister |
|---|---|---|
| `"query": "ay"` | Adı bu sözcükle başlayanlar. `limit` en çok 100, verilmezse 100. | Bir şey istemez. |
| `"user_ids": ["..."]` | Bu kullanıcılar, en çok 100 ID. Üye olmayanlar `not_found` içinde. | Bir şey istemez. |
| `"query": ""` | Tam liste. `limit` verilirse en çok o kadar. | `GUILD_MEMBERS` intent'i |

`"presences": true` üyelerin durumlarını da ekler ve `GUILD_PRESENCES` ister. Gereken intent oturumda yoksa bağlantı `4014` ile kapanır. `nonce`, en çok 32 baytlık bir etikettir; yanıtta aynen geri gelir ve hangi isteğin yanıtı olduğunu anlamanı sağlar. `query` en çok 100 karakter olabilir. Bu sınırları ya da tablodakileri aşan istek bağlantıyı `4002` ile kapatır.

Yanıt, her biri en çok 1000 üye taşıyan `GUILD_MEMBERS_CHUNK` parçalarıyla gelir. Bitişi şöyle anlarsın:

```js
if (t === "GUILD_MEMBERS_CHUNK") {
  members.push(...d.members);
  if (d.chunk_index + 1 === d.chunk_count) console.log("liste bitti", members.length);
}
```

- **Geçerli olan, son parçadaki `chunk_count` değeridir.** İlk parçalardaki sayı bir tahmindir: liste gönderilirken üyeler ayrılabilir, o zaman son parça daha erken gelir ve kendi `chunk_count` değeriyle bittiğini söyler. Sayıyı başta saklayıp ona güvenme, her parçada yeniden bak.
- **Boş bir son parça, listenin eksik olabileceği anlamına gelir.** Liste yarıda hata alırsa, boşuna beklemeyesin diye `members` alanı boş bir son parça gelir. Ayrı bir hata alanı yoktur. Emin olmak için sınırlara uyarak listeyi yeniden iste.
- Botun içinde olmadığı ya da başka shard'daki bir sunucu için tek bir boş parça gelir.

Aynı anda tek istek yanıtlanır. Bir oturum dakikada 10 istek gönderebilir, tam liste ise sunucu başına 30 saniyede bir istenebilir. Buna uymayan istek bağlantıyı kapatmaz, `RATE_LIMITED` ile yanıtlanır.

## RATE_LIMITED

`op 3`, `op 4` ya da `op 8` sınıra takılınca bağlantı kapanmaz, sana bir olay gelir:

```json
{ "op": 0, "s": 57, "t": "RATE_LIMITED", "d": { "opcode": 8, "retry_after": 12.5, "meta": { "guild_id": "21531879014400001", "nonce": "arama-1" } } }
```

`retry_after` saniye kadar bekleyip aynı çerçeveyi yeniden gönder. Sayılar [Hız sınırları](https://noluyo.net/gelistirici/rate-limit.md) sayfasında.

## INTERACTION_CREATE

Biri botunun mesajındaki butona bastığında ya da menüden seçtiğinde gelir. Intent istemez, yalnızca sunucunun shard'ına gider; [DM'den](https://noluyo.net/gelistirici/ozel-mesajlar.md) gelirse shard 0'a. Bot çevrimdışıysa olay kaybolur. Ayrıntı [Bileşenler](https://noluyo.net/gelistirici/bilesenler.md) sayfasında.

## Tam bir istemci

Aşağıdaki dosya bu sayfadaki bağlantı işlerinin hepsini yapar: adresi sorar, heartbeat gönderir, kopunca resume yapar, `op 7` ve `op 9`'u işler, kapanış koduna göre karar verir. [Örnekler](https://noluyo.net/gelistirici/ornekler.md) sayfasındaki botlar bu dosyayı kullanır. `gateway.mjs` adıyla kaydet:

```js title=gateway.mjs
const API = "https://noluyo.net/api/v1";
const { BOT_TOKEN } = process.env;

// Bu kodlarda yeniden bağlanmak bir şey düzeltmez.
const FATAL = [4001, 4002, 4003, 4004, 4005, 4011, 4012, 4013, 4014, 4015];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function gatewayInfo() {
  for (let attempt = 0; ; attempt++) {
    try {
      const res = await fetch(`${API}/gateway/bot`, {
        headers: { Authorization: `Bot ${BOT_TOKEN}` },
      });
      if (res.status >= 500 && attempt < 5) {
        await sleep(Math.min(10000, 1000 * 2 ** attempt));
        continue;
      }
      if (!res.ok) throw new Error(`GET /gateway/bot: ${res.status}`);
      return res.json();
    } catch (err) {
      if (attempt >= 5 || String(err.message).startsWith("GET /gateway/bot: 4")) throw err;
      await sleep(Math.min(10000, 1000 * 2 ** attempt));
    }
  }
}

export async function connect({ intents, shard, onEvent }) {
  const { url } = await gatewayInfo();
  let sessionId = null;
  let seq = null;
  let current = null;
  let failures = 0;

  function open() {
    const ws = new WebSocket(url);
    current = ws;
    const send = (op, d) => {
      if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op, d }));
    };
    const identify = () => send(2, { token: BOT_TOKEN, intents, shard });
    let heartbeat;
    let reidentifyTimer;
    let acked = true;

    const clearTimers = () => {
      clearInterval(heartbeat);
      clearTimeout(reidentifyTimer);
    };

    // Bu bağlantıyı bırakır. 4000 ile kapattığımız için oturum sürdürülebilir kalır.
    function reopen() {
      clearTimers();
      ws.onmessage = ws.onclose = ws.onerror = null;
      try {
        if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close(4000);
      } catch {}
      setTimeout(open, 1000);
    }

    ws.onerror = () => {};

    ws.onmessage = (e) => {
      const { op, d, s, t } = JSON.parse(e.data);
      if (s) seq = s;
      if (op === 0) {
        if (t === "READY") sessionId = d.session_id;
        onEvent(t, d);
      } else if (op === 10) {
        failures = 0;
        heartbeat = setInterval(() => {
          // Önceki heartbeat yanıtsız kaldı: bağlantı ölmüş.
          if (!acked) return reopen();
          acked = false;
          send(1, seq);
        }, d.heartbeat_interval);
        if (sessionId) send(6, { token: BOT_TOKEN, session_id: sessionId, seq });
        else identify();
      } else if (op === 11) {
        acked = true;
      } else if (op === 7) {
        reopen();
      } else if (op === 9) {
        sessionId = seq = null;
        clearTimeout(reidentifyTimer);
        reidentifyTimer = setTimeout(identify, 5000);
      }
    };

    ws.onclose = async (e) => {
      clearTimers();
      console.error("gateway kapandı", e.code, e.reason);
      if (FATAL.includes(e.code)) process.exit(1);
      if (e.code === 4010) sessionId = seq = null;
      let wait = e.code === 1012 ? 1500 : Math.min(15000, 1000 * 2 ** Math.min(failures++, 4));
      if (e.code === 4008) wait = Math.max(5000, wait);
      if (e.code === 4016) {
        try {
          wait = (await gatewayInfo()).session_start_limit.reset_after;
        } catch {
          wait = 60000;
        }
      }
      setTimeout(open, wait);
    };
  }

  open();
  return {
    send: (op, d) => {
      if (current?.readyState === WebSocket.OPEN) current.send(JSON.stringify({ op, d }));
    },
  };
}
```

Kullanımı şöyle:

```js
import { connect } from "./gateway.mjs";

await connect({
  intents: (1 << 0) | (1 << 9),
  onEvent(t, d) {
    if (t === "READY") console.log(d.user.username, "bağlandı");
  },
});
```

`onEvent` içinde fırlatılan bir hata programı durdurur, kendi işlerini `try` içinde yap.

## Bu sayfayı yapay zekaya verirsen

> **İpucu:** Yapay zekaya ham WebSocket istemcisi yazdırırken şu kuralları özellikle hatırlat:
>
> - Gateway adresi koda sabit yazılmaz; her açılışta `GET https://noluyo.net/api/v1/gateway/bot` ucundan alınır ve bot gateway'inde zlib akış sıkıştırması yoktur (düz UTF-8 JSON çerçeveleri kullanılır).
> - `op 10` içindeki `heartbeat_interval` süresine uyulur, kopmalarda önce `op 6` (`RESUME`) denenir ve `4004` / `4014` kapanış kodlarında yeniden bağlanma döngüsüne girilmez.

## Sırada ne var

- [Intent'ler](https://noluyo.net/gelistirici/intentler.md): hangi olayların geleceğini seçmek ve mesaj içeriği kuralı
- [Shard'lar](https://noluyo.net/gelistirici/shardlar.md): büyük botları birden çok WebSocket bağlantısına bölmek
- [JavaScript kütüphanesi](https://noluyo.net/gelistirici/kutuphane-js.md) ve [Python kütüphanesi](https://noluyo.net/gelistirici/kutuphane-python.md): hazır gateway ve `RESUME` yönetimi
