N'oluyo · Geliştirici Dokümanları · Son güncelleme: · https://noluyo.net/gelistirici

Embed'ler ve bileşenler

Embed, bir mesajın metninin altında duran karttır: başlık, açıklama, alanlar, görsel ve renkli bir şerit taşır. Bileşen ise mesaja eklenen buton ve menüdür. İkisini de yalnızca botlar gönderebilir.

Embed'ler ve bileşenler

Embed, bir mesajın metninin altında duran karttır: başlık, açıklama, alanlar, görsel ve renkli bir şerit taşır. Bileşen ise mesaja eklenen buton ve menüdür. İkisini de yalnızca botlar gönderebilir.

İlk embed

Çalışan en küçük istek şudur; content yazmak gerekmez:

curl -X POST "https://noluyo.net/api/v1/channels/$CHANNEL_ID/messages" \
  -H "Authorization: Bot $BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"embeds":[{"title":"Merhaba","description":"İlk embed","color":3368703}]}'

Yanıttaki mesajda embeds dizisi gelir ve application_id botun uygulamasını gösterir. Aynı mesaj gateway'de MESSAGE_CREATE olarak da akar.

Embed alanları

Bir mesajda en çok 10 embed olur. Hepsinin metinleri toplam 6000 karakteri, hepsinin JSON'u 16384 baytı (UTF-8) geçemez. Bir embed'de başlık, açıklama, alan, yazar, alt bilgi, görsel veya küçük görselden en az biri bulunmalıdır. Sınır sütunundaki sayılar karakter ya da öğe sayısıdır.

AlanTürSınırZorunluAçıklama
titlemetin256hayırBaşlık
descriptionmetin4096hayırAna metin
urlmetin2048hayırBaşlığın bağlandığı https adresi
colorsayı16777215hayırŞeridin rengi; ondalık sayı olarak, aşağıya bak
timestampmetin-hayırRFC 3339 zamanı
fieldsliste25hayırAd ve değer çiftleri
fields[].namemetin256evetAlanın adı
fields[].valuemetin1024evetAlanın değeri
fields[].inlinedoğru/yanlış-hayırtrue ise alanlar yan yana durur
footer.textmetin2048evetAlt bilgi metni
footer.icon_urlmetin-hayırattachment:// ile bir görsel dosyası
author.namemetin256evetYazarın adı
author.urlmetin2048hayırYazarın bağlandığı https adresi
author.icon_urlmetin-hayırattachment:// ile bir görsel dosyası
image.urlmetin-hayırattachment:// ile bir görsel dosyası
thumbnail.urlmetin-hayırattachment:// ile bir görsel dosyası

Bilmediğin bir alan yazarsan görmezden gelinir ve kaydedilmez. Metinlerdeki kontrol karakterleri (satır sonu ve sekme dışında) hata verir. Başında ve sonundaki boşluklar silinir.

Görsel göndermek

Embed'in görseli, aynı mesajla yüklediğin bir dosyadır. Başka bir sitedeki adres kabul edilmez: görseli okuyanın tarayıcısı dışarıdaki bir sunucuya gitmesin, okuyanın IP adresi üçüncü bir tarafa ulaşmasın diye N'oluyo yalnızca kendi sakladığı dosyaları gösterir. https://... ile başlayan bir görsel adresi invalid ile reddedilir.

Dosyalı mesaj multipart/form-data ile gider. JSON payload_json parçasına, dosyalar files[0], files[1] gibi parçalara konur:

curl -X POST "https://noluyo.net/api/v1/channels/$CHANNEL_ID/messages" \
  -H "Authorization: Bot $BOT_TOKEN" \
  -F 'payload_json={"embeds":[{"title":"Haftalık grafik","image":{"url":"attachment://grafik.png"}}]}' \
  -F 'files[0]=@grafik.png'
  • -F 'payload_json=...': mesajın JSON'u. Embed'deki attachment://grafik.png, aynı istekteki grafik.png dosyasına işaret eder.
  • -F 'files[0]=@grafik.png': yüklenen dosya. Kanalda AttachFiles izni gerekir.
  • Yanıtta image.attachment_id, dosyanın mesajın attachments listesindeki kimliğidir. image.url yanıtta da attachment:// olarak kalır; görseli bulmak için kimliği kullan.

Ad şu kurallarla eşleştirilir:

  • Dosya adı yüklenirken temizlenir: harf, rakam, -, _ ve bir . kalır, her diğer karakter _ olur. haftalık grafik.png böylece haftalık_grafik.png olur. attachment:// adresindeki ad da aynı biçimde temizlenir, yani iki yazım da aynı dosyayı bulur.
  • Yalnızca PNG, JPEG, GIF ve WebP görselleri gösterilebilir. PDF veya başka bir dosyaya başvurmak invalid verir.
  • Mesaja eklenmemiş bir ada başvurmak invalid verir.
  • Aynı adı taşıyan iki dosya varsa o ada başvurmak invalid verir; dosyaları farklı adlarla yükle.

Renk ve zaman

color ondalık bir sayıdır, #RRGGBB yazımının sayıya çevrilmiş halidir. Onaltılık renkten çevirmek için:

const color = parseInt("3366FF", 16); // 3368703

timestamp RFC 3339 biçimindedir, örneğin 2026-11-07T18:30:00Z. JavaScript'te new Date().toISOString() doğru biçimi verir. Zaman UTC olarak saklanır ve okuyanın saatine çevrilerek gösterilir.

Metin, markdown ve etiketler

Metinler markdown biçimlendirmesiyle yazılabilir; sunucu metni olduğu gibi saklar, çizimi istemci yapar. Embed içinde yazdığın <@kullanıcı_id>, <@&rol_id> ve @everyone kimseyi etiketlemez; bildirim göndermez, okunmamış sayacını artırmaz.

Kimi etiketlersin

allowed_mentions yalnızca daraltır: botun @everyone etiketleme izni yoksa parse içinde everyone yazmak bir şey değiştirmez. Etiket metinde kalır, yalnızca bildirim gitmez.

GönderdiğinSonuç
hiçbiriBir kişinin yazdığı mesajla aynı: metindeki her etiket bildirim gönderir.
"parse": []Hiçbir etiket bildirim göndermez.
"parse": ["users"]Yalnızca kullanıcı etiketleri.
"parse": ["roles"], ["everyone"]Yalnızca rol etiketleri, yalnızca @everyone ve @here.
"users": ["id"]Yalnızca listedeki kullanıcılar. roles için aynısı. Listeler en çok 100 kimlik taşır.
"replied_user": falseYanıt, yanıtlanan kişiye bildirim göndermez.

parse içinde users varken users listesi (rol için de aynısı) yazılamaz; istek validation_failed ile döner, parse içinde bilinmeyen bir değer de öyle. Aşağıdaki mesaj metinde kullanıcıyı etiketler ama kimseye bildirim göndermez, okunmamış sayacını da artırmaz:

{ "content": "<@111500000000000001> toplantı 15:00'te", "allowed_mentions": { "parse": [] } }

Mesajı PATCH ile düzenlerken content değişiyorsa allowed_mentions o istekte de gönder; düzenleme kimseye yeni bildirim göndermez ama kimin etiketli sayıldığı güncellenir. allowed_mentions göndermezsen düzenlenen metin, bir kişinin yazdığı metin gibi çözülür.

Özel emojiler (<:ad:id>) content ve embed metinlerinde botun kullanabildiği emojilerle sınırlıdır; kullanamadığı emoji :ad: düz metnine döner. Embed'ler ve bileşenler birlikte en çok 50 özel emoji taşır, fazlası too_long döner. Emoji sunucudaki güncel adıyla yazılır; ad senin yazdığından uzunsa 16384 bayt sınırı bu haliyle denetlenir.

Webhook'tan embed

Webhook bağlantısına (POST /api/v1/webhooks/{id}/{token}) gönderdiğin embeds de aynı kurallarla gösterilir; content yazmak gerekmez. İki fark var:

  • Görseller düşer. image, thumbnail, author.icon_url ve footer.icon_url hata vermez ama saklanmaz; dış adres okuyanlara hiç ulaşmaz. Başlık, açıklama ve alanlar durur. Yalnızca görselden oluşan bir embed görselle birlikte kaybolur.
  • Bileşen yoktur ve mesaj bir uygulamaya bağlanmaz; application_id gelmez. flags ve allowed_mentions eskisi gibi alınıp yok sayılır.

Bugün çalışan bir isteğin kırılmaması için embed'ler hoşgörüyle okunur. Bilmediğimiz alanlar (type, video gibi) yok sayılır. Kurallara uymayan bir embed varsa (title 257 karakter, http:// adres, yalnızca color taşıyan embed ya da liste olmayan embeds gibi) mesajın bütün embed'leri düşer ve content yine gönderilir. 400 yalnızca geriye hiçbir şey kalmayınca gelir: content boşsa hata embed'lerin neyinin yanlış olduğunu söyler. Embed'ler botlarınkiyle aynı yayılma bütçesine sayılır. Sunucunun otomatik moderasyonu embed metnine de bakar.

Kişi ne görür

Embed, mesajın metninin altında bir kart olarak çizilir. Kartın sol kenarında color renginde ince bir şerit durur; color yoksa şerit kişinin seçtiği temanın rengini alır. Yukarıdan aşağıya sıra şudur: yazar, başlık, açıklama, alanlar, görsel, altbilgi ve zaman. thumbnail kartın sağ üstünde küçük bir kare olur, dar ekranda biraz daha küçülür.

inline: true alanlar yan yana dizilir: geniş kartta üç sütun, telefonda tek sütun. Satır içi olmayan alan tüm genişliği kaplar. url verdiysen title ve author.name yeni sekmede açılan bağlantı olur. Açıklamada ve alan değerlerinde markdown çalışır, HTML etiketleri düz metin olarak görünür. Uzun bir kelime kartın dışına taşmaz, satıra bölünür.

Embed'in gösterdiği dosya (image, thumbnail, author ve footer simgesi) mesajın dosya listesinde ikinci kez görünmez. Embed'e bağlanmayan dosyalar kartın altında her zamanki gibi durur. Görsele basınca büyür. Bir mesajdaki 10 embed alt alta dizilir.

Metni olmayan bir mesajda sabitlenmiş mesajlar, arama sonuçları, yanıt satırı ve bildirim ilk embed'in başlığını, başlık yoksa açıklamasını gösterir.

Eski bir uygulama sürümü embed'i çizmez: dosyaları gösterir, yalnızca embed'den oluşan mesaj boş görünür. Kaçırılmaması gereken bir bilgiyi content'e de yaz.

Bir mesajda neler birlikte olur

BirleşimOlur muNot
content ve embedEvetMetin üstte, embed altta durur
Yalnız embedEvetcontent gerekmez
Embed ve dosyaEvetEmbed, dosyayı attachment:// ile gösterir; dosya attachments listesinde de kalır
Yalnız bileşenHayırcontent, embed veya dosya gerekir; content için required döner
Embed ve anket, iletme ya da çıkartmaHayırembeds için invalid döner
Bileşen ve anket, iletme ya da çıkartmaHayırembeds için invalid döner

Bir kişi embeds, components, flags veya genişletilmiş allowed_mentions gönderirse istek validation_failed ile reddedilir. Bir mesaj iletilirse metni ve dosyaları gider, embed ve bileşenleri gitmez; yalnızca embed'den oluşan bir mesaj iletilemez.

Bileşenler

Bileşenler eylem satırlarına konur. Bir mesajda en çok 5 satır olur; satır 1-5 buton ya da tam bir menü taşır:

AlanTürSınırZorunluAçıklama
componentsliste5hayırEylem satırları; her satırın type değeri 1
components[].componentsliste5evetSatırdaki butonlar ya da tek bir menü
typesayı-evet2 buton, 3 metin menüsü, 5 kullanıcı, 6 rol, 8 kanal menüsü
stylesayı-buton için1 birincil, 2 ikincil, 3 başarılı, 4 tehlike, 5 bağlantı
labelmetin80buton içinButon yazısı
custom_idmetin100bağlantı dışındaMesajda tekil olmalı
urlmetin2048bağlantı içinhttps adresi
emojinesne-hayır{"name":"👍"} ya da botun kullanabildiği {"id":"..."}
placeholdermetin150hayırMenüde seçim yokken görünen yazı
optionsliste25metin menüsü içinSeçenekler
options[].labelmetin100evetSeçenek yazısı
options[].valuemetin100evetSeçenek değeri; menüde tekil
min_values, max_valuessayı25hayırSeçilebilecek en az ve en çok değer; varsayılan 1

Tüm bileşenlerin JSON'u 16384 baytı geçemez. Bileşenler content, embed veya dosyayla birlikte gönderilir.

Bir kişi butona bastığında bot INTERACTION_CREATE alır. Akış, menü türleri ve botun alacakları Bileşenler sayfasında.

Düzenleme

PATCH /channels/{channel_id}/messages/{message_id} ile botun kendi mesajı düzenlenir. content, embeds ve components ayrı ayrı gönderilebilir:

curl -X PATCH "https://noluyo.net/api/v1/channels/$CHANNEL_ID/messages/$MESSAGE_ID" \
  -H "Authorization: Bot $BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"components":[]}'
  • Göndermediğin alan olduğu gibi kalır. components yazıp başka bir şey yazmazsan metin ve embed'ler aynı kalır.
  • Boş liste ("embeds":[] ya da "components":[]) o alanı kaldırır.
  • Yalnızca components değişirse mesaj düzenlendi sayılmaz; content veya embeds değişirse edited_timestamp dolar.
  • Embed'deki attachment:// yalnızca mesajın var olan dosyalarına bakar. Yeni dosya yüklemek isteyen multipart istek invalid_body ile reddedilir; yeni görsel yeni bir mesajla gönderilir.
  • Mesajda metin, embed ve dosya kalmıyorsa content için required döner.

Mesaj içeriği intent'i

MESSAGE_CONTENT intent'i olmayan bir bot, başka kişilerin ve botların mesajlarındaki embeds ve components alanlarını görmez; gateway olaylarında da REST yanıtlarında da bu alanlar gelmez. Kendi mesajları ve kendisini etiketleyen mesajlar eksiksiz gelir. flags ve application_id her zaman gelir. Ayrıntı için Intent'ler sayfasına bak.

Limit aşımı

Sınırı aşan alan, adıyla birlikte döner:

{
  "code": "validation_failed",
  "message": "One or more fields are invalid.",
  "errors": { "embeds[0].title": "too_long" }
}

Hata anahtarı sorunlu alanın yoludur. Yapılacak şey metni kısaltmak ya da içeriği birkaç mesaja bölmektir. Toplam sınırlar aşılırsa anahtar embeds veya components olur.

Embed'ler ayrıca ortak bir tavana sayılır: her mesajın embed ve bileşenleri sunucunun üye sayısıyla çarpılarak tüm botların ve webhook'ların birlikte kullandığı bir bütçeden düşer; bir sahibin botlarına bunun bir payı düşer. Payın dolunca X-RateLimit-Scope: bot, bütçenin tamamı dolunca shared başlığıyla 429 gelir; ikincisi botunun suçu değildir. retry_after kadar bekleyip yeniden dene. Bütçenin 30 saniyesinden fazla tutan mesaj 400 fanout_too_large alır; max_bytes o kanalın alabileceği en büyük embed ve bileşen boyutunu söyler. Kalabalık bir sunucuya giden büyük bir mesaj o saniyede kalanı alır, bütçe onu ödeyene kadar tüm botların sayılan mesajlarını bekletir, sen de bir sonraki büyük mesajından önce bunun dört katı beklersin. Ayrıntı Hız sınırları sayfasında.

Node.js ile tam örnek

Node.js 22 veya üstü yeter, ek paket gerekmez. Token'ı koda yazma; ortam değişkeninden okunur. grafik.png aynı klasörde olmalı:

import { readFile } from "node:fs/promises";

const API = "https://noluyo.net/api/v1";
const token = process.env.BOT_TOKEN;
const channelId = process.env.CHANNEL_ID;
if (!token || !channelId) throw new Error("BOT_TOKEN ve CHANNEL_ID tanımlı olmalı");

async function call(method, path, body) {
  for (;;) {
    const res = await fetch(API + path, {
      method,
      headers: { Authorization: `Bot ${token}`, ...(body instanceof FormData ? {} : { "Content-Type": "application/json" }) },
      body: body instanceof FormData ? body : JSON.stringify(body),
    });
    const data = await res.json();
    if (res.status === 429) {
      await new Promise((done) => setTimeout(done, data.retry_after * 1000));
      continue;
    }
    if (!res.ok) throw new Error(`${res.status} ${data.code} ${JSON.stringify(data.errors ?? {})}`);
    return data;
  }
}

const form = new FormData();
form.set(
  "payload_json",
  JSON.stringify({
    embeds: [
      {
        title: "Haftalık grafik",
        description: "Geçen haftanın özeti",
        color: parseInt("3366FF", 16),
        timestamp: new Date().toISOString(),
        fields: [{ name: "Toplam", value: "128", inline: true }],
        image: { url: "attachment://grafik.png" },
      },
    ],
    components: [
      { type: 1, components: [{ type: 2, style: 5, label: "Ayrıntılar", url: "https://noluyo.net" }] },
    ],
  }),
);
form.set("files[0]", new Blob([await readFile("grafik.png")], { type: "image/png" }), "grafik.png");

const message = await call("POST", `/channels/${channelId}/messages`, form);
console.log("gönderildi:", message.id);

// Başlığı değiştir; görsel ve bileşenler olduğu gibi kalır.
const embeds = message.embeds.map((e) => ({ ...e, title: "Güncel grafik" }));
await call("PATCH", `/channels/${channelId}/messages/${message.id}`, { embeds });

Düzenleme isteğinde image.attachment_id gibi yanıttan gelen alanlar gönderilince yok sayılır; görsel attachment:// adresiyle aynı dosyaya yeniden bağlanır.

Sık hatalar

HataNedenNe yapmalı
embeds[0].image.url: invalidAdres attachment:// ile başlamıyor, dosya mesajla gönderilmemiş, görsel değil ya da aynı adlı iki dosya varDosyayı aynı istekte, görsel olarak ve tekil adla yükle
embeds: too_longToplam 6000 karakter ya da 16384 bayt aşıldı, ya da 50'den çok özel emoji varMetni kısalt ya da birkaç mesaja böl
embeds[0]: requiredEmbed boşEn az bir alan doldur
content: requiredYalnız bileşen gönderildi ya da düzenleme sonrası mesaj boş kalıyorMetin, embed veya dosya ekle
components[0].components[0].emoji: invalidBotun kullanamadığı özel emojiBotun kullanabildiği bir emoji ya da {"name":"👍"} yaz
ephemeral_forbiddenKanala giden mesajda flags: 64flags alanını çıkar
automod_blockedSunucunun otomatik moderasyonu embed metnini ya da buton yazısını engellediMetni değiştir
invalid_bodyembeds liste olmalı ama başka tür gönderildiGövdeyi kontrol et
429 ve X-RateLimit-Scope: sharedOrtak bütçe doluretry_after kadar bekle

Bu sayfayı yapay zekaya verirsen

Sırada ne var