> ## Documentation Index
> Fetch the complete documentation index at: https://docs.evox.wraithesports.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Entegrasyon plugini yazma

> T1'de tam Node gücüyle OBS, Twitch, Spotify gibi Market-tarzı entegrasyon pluginleri yazın: npm kütüphaneleri, dış servis bağlantısı, tuş görseli ve kadran.

Bu sayfa **Market'te gördüğünüz türden** entegrasyon pluginlerini (OBS sahne değiştirici, Twitch/Spotify kontrolü, Discord, hotkey) sıfırdan nasıl yazacağınızı anlatır.

<Warning>Entegrasyonlar **T1 (trusted-node)** ile yazılır ve **sandbox değildir** — plugin Windows kullanıcı hesabınızın erişebildiği her şeye erişir. Yalnız kendi yazdığınız/incelediğiniz kodu çalıştırın. Public T2 sandbox modeli farklıdır (bkz. <a href="/sdk/trust-tiers">Trust tier'ları</a>).</Warning>

## Temel model: T1 = tam Node process

Önemli olan tek fikir: **evoX, T1 plugininizi ayrı bir tam Node.js process'i olarak çalıştırır.** Yani:

* **İstediğiniz npm kütüphanesini** kullanabilirsiniz (`npm install obs-websocket-js ws node-fetch` …).
* Dış servislere **doğrudan** bağlanabilirsiniz — WebSocket, HTTPS API, yerel port. evoX araya girmez.
* **SDK yalnız host tarafını yönetir:** hangi event geldi (tuşa basıldı, kadran döndü), tuşta ne görünüyor (başlık/görsel/durum), ayarlar ve loglar.

Yani iş bölümü: **dış servis mantığı sizin Node kodunuz + npm kütüphaneleriniz; tuş/ayar/olay köprüsü SDK.**

```text theme={}
[ Dış servis: OBS / Twitch / Spotify ]
        ▲  (sizin npm kütüphaneniz: ws, api client — doğrudan)
        │
   runtime.mjs  (T1 tam Node)
        │
        ▼  (@evoxapp/plugin-sdk: event + setTitle/setImage + settings)
[ evoX host  →  cihaz tuşu ]
```

## Proje kurulumu

```bash theme={}
npx @evoxapp/plugin-cli init obs-switcher --runtime trusted-node --name "OBS Sahne"
cd obs-switcher
npm install @evoxapp/plugin-sdk obs-websocket-js
```

`manifest.json`'da aksiyonu ve ayarlarını bildirin. Ayar tipleri zengindir — `secret`, `oauth`, `select`, `color`, `number`, `toggle`, `hotkey`, `url`, `path` desteklenir:

```json theme={}
{
  "actions": [
    {
      "id": "scene",
      "name": "Sahne Seç",
      "controllers": ["Keypad"],
      "settingsSchema": [
        { "key": "host", "type": "text", "label": "OBS Host", "default": "127.0.0.1" },
        { "key": "port", "type": "number", "label": "Port", "default": 4455 },
        { "key": "password", "type": "secret", "label": "OBS Parolası" },
        { "key": "sceneName", "type": "text", "label": "Sahne adı" }
      ]
    }
  ]
}
```

## Örüntü 1 — WebSocket entegrasyonu (OBS)

`runtime.mjs`: bağlantıyı ilk `willAppear`'da kur, `keyDown`'da komut gönder, `willDisappear`'da temizle.

```js theme={}
import { EvoxPluginClient } from '@evoxapp/plugin-sdk';
import { createNodeProcessIpcPluginTransport } from '@evoxapp/plugin-sdk/node';
import OBSWebSocket from 'obs-websocket-js';

const client = new EvoxPluginClient(createNodeProcessIpcPluginTransport(), { sdkVersion: '0.1.0' });
const obs = new OBSWebSocket();
let connected = false;

async function ensureConnected(settings) {
  if (connected) return;
  await obs.connect(`ws://${settings.host || '127.0.0.1'}:${settings.port || 4455}`, settings.password || undefined);
  connected = true;
}

client.on('action.willAppear', async (event) => {
  if (event.context?.actionId !== 'scene') return;
  await client.setTitle(event.context.bindingInstanceId, 'OBS');
});

client.on('action.keyDown', async (event) => {
  if (event.context?.actionId !== 'scene') return;
  const id = event.context.bindingInstanceId;
  try {
    const settings = await client.getSettings(id);
    await ensureConnected(settings);
    await obs.call('SetCurrentProgramScene', { sceneName: settings.sceneName });
    await client.showSuccess(id);
  } catch (error) {
    console.error('[obs] ' + String(error?.message || error));
    connected = false;
    await client.showError(id);
  }
});

client.on('plugin.stop', async () => { try { await obs.disconnect(); } catch {} });

await client.start();
```

`ws://127.0.0.1` bağlantısı **doğrudan** kuruluyor — T1 tam Node olduğu için bir SDK "network capability"si gerekmez. (Bu, public T2 sandbox'ta böyle değildir; orada host-brokered `network.fetch` kullanılır.)

## Örüntü 2 — API + OAuth (Twitch/Spotify tarzı)

İki yol var:

1. **Kendi token yönetiminiz:** T1 tam Node olduğu için tokeni `secret` ayarından okuyup doğrudan `fetch` ile kullanabilirsiniz.
2. **Host-brokered OAuth:** SDK `beginOAuth(provider)` / `getOAuthStatus(provider)` ile evoX'in credential vault'unu kullanırsınız; ham token plugininize dönmez, host isteğe enjekte eder.

```js theme={}
client.on('action.keyDown', async (event) => {
  if (event.context?.actionId !== 'nowPlaying') return;
  const id = event.context.bindingInstanceId;
  const settings = await client.getSettings(id);
  const res = await fetch('https://api.spotify.com/v1/me/player/currently-playing', {
    headers: { Authorization: `Bearer ${settings.token}` },
  });
  const data = await res.json();
  await client.setTitle(id, data?.item?.name?.slice(0, 12) || '—');
});
```

## Tuş görseli — `setImage` (albüm kapağı gibi dinamik görsel)

Başlık dışında tuşa **dinamik görsel** basabilirsiniz. İki biçim:

```js theme={}
// 1) Pakete gömülü asset (manifest-hash id ile):
await client.setImage(id, { kind: 'asset', assetId: 'scene-live' });

// 2) Çalışma anında üretilen/indirilen raster (base64):
const bytes = Buffer.from(await (await fetch(albumArtUrl)).arrayBuffer());
await client.setImage(id, { kind: 'raster', mimeType: 'image/png', dataBase64: bytes.toString('base64') });
```

Çok-durumlu aksiyonlarda `setState(id, n)` ile durum (0'dan başlar) değiştirilir.

## Kadran (Knob) desteği

Aksiyonu `"controllers": ["Knob"]` ile bildirin ve `action.dialRotate` / `action.dialDown` dinleyin — ses/parlaklık gibi sürekli değerler için idealdir.

```js theme={}
let volume = 50;
client.on('action.dialRotate', async (event) => {
  if (event.context?.actionId !== 'volume') return;
  volume = Math.max(0, Math.min(100, volume + (event.payload?.ticks || 0)));
  await client.setTitle(event.context.bindingInstanceId, `%${volume}`);
});
```

## Yaşam döngüsü ve temizlik

* **Bağlantıyı** `willAppear`'da tembel (lazy) kur; `plugin.stop`'ta kapat.
* **Polling** gerekiyorsa `willAppear`'da başlat, `willDisappear`'da durdur; hata halinde exponential backoff.
* Her handler'ı `try/catch` ile sar; hatayı `console.error` ile logla (Geliştirici penceresinde görünür) ve `showError` ile kullanıcıya bildir.
* Kimliği host atar: `event.context.bindingInstanceId` dışında bir context'e yazma; tanımadığın `actionId`'yi yok say.

## Sonraki adım

<Card title="Kendi evoX'inde test et" href="/sdk/quickstart">Projeyi Geliştirici penceresinden yükle, tuşa ata, yeniden yükle ve logları gör.</Card>

Referanslar: <a href="/sdk/events-reference">Event ve host-RPC</a> · <a href="/sdk/manifest">Manifest</a> · <a href="/sdk/capabilities">Capability modeli (T2)</a>.
