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.
| Alan | Tür | Sınır | Zorunlu | Açıklama |
|---|---|---|---|---|
title | metin | 256 | hayır | Başlık |
description | metin | 4096 | hayır | Ana metin |
url | metin | 2048 | hayır | Başlığın bağlandığı https adresi |
color | sayı | 16777215 | hayır | Şeridin rengi; ondalık sayı olarak, aşağıya bak |
timestamp | metin | - | hayır | RFC 3339 zamanı |
fields | liste | 25 | hayır | Ad ve değer çiftleri |
fields[].name | metin | 256 | evet | Alanın adı |
fields[].value | metin | 1024 | evet | Alanın değeri |
fields[].inline | doğru/yanlış | - | hayır | true ise alanlar yan yana durur |
footer.text | metin | 2048 | evet | Alt bilgi metni |
footer.icon_url | metin | - | hayır | attachment:// ile bir görsel dosyası |
author.name | metin | 256 | evet | Yazarın adı |
author.url | metin | 2048 | hayır | Yazarın bağlandığı https adresi |
author.icon_url | metin | - | hayır | attachment:// ile bir görsel dosyası |
image.url | metin | - | hayır | attachment:// ile bir görsel dosyası |
thumbnail.url | metin | - | hayır | attachment:// 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'dekiattachment://grafik.png, aynı istektekigrafik.pngdosyasına işaret eder.-F 'files[0]=@grafik.png': yüklenen dosya. KanaldaAttachFilesizni gerekir.Yanıtta
image.attachment_id, dosyanın mesajınattachmentslistesindeki kimliğidir.image.urlyanıtta daattachment://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.pngböylecehaftalık_grafik.pngolur.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
invalidverir.Mesaja eklenmemiş bir ada başvurmak
invalidverir.Aynı adı taşıyan iki dosya varsa o ada başvurmak
invalidverir; 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); // 3368703timestamp 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ğin | Sonuç |
|---|---|
| hiçbiri | Bir 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": false | Yanı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_urlvefooter.icon_urlhata 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_idgelmez.flagsveallowed_mentionseskisi 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şim | Olur mu | Not |
|---|---|---|
content ve embed | Evet | Metin üstte, embed altta durur |
| Yalnız embed | Evet | content gerekmez |
| Embed ve dosya | Evet | Embed, dosyayı attachment:// ile gösterir; dosya attachments listesinde de kalır |
| Yalnız bileşen | Hayır | content, embed veya dosya gerekir; content için required döner |
| Embed ve anket, iletme ya da çıkartma | Hayır | embeds için invalid döner |
| Bileşen ve anket, iletme ya da çıkartma | Hayır | embeds 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:
| Alan | Tür | Sınır | Zorunlu | Açıklama |
|---|---|---|---|---|
components | liste | 5 | hayır | Eylem satırları; her satırın type değeri 1 |
components[].components | liste | 5 | evet | Satırdaki butonlar ya da tek bir menü |
type | sayı | - | evet | 2 buton, 3 metin menüsü, 5 kullanıcı, 6 rol, 8 kanal menüsü |
style | sayı | - | buton için | 1 birincil, 2 ikincil, 3 başarılı, 4 tehlike, 5 bağlantı |
label | metin | 80 | buton için | Buton yazısı |
custom_id | metin | 100 | bağlantı dışında | Mesajda tekil olmalı |
url | metin | 2048 | bağlantı için | https adresi |
emoji | nesne | - | hayır | {"name":"👍"} ya da botun kullanabildiği {"id":"..."} |
placeholder | metin | 150 | hayır | Menüde seçim yokken görünen yazı |
options | liste | 25 | metin menüsü için | Seçenekler |
options[].label | metin | 100 | evet | Seçenek yazısı |
options[].value | metin | 100 | evet | Seçenek değeri; menüde tekil |
min_values, max_values | sayı | 25 | hayır | Seç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.
componentsyazı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
componentsdeğişirse mesaj düzenlendi sayılmaz;contentveyaembedsdeğişirseedited_timestampdolar.Embed'deki
attachment://yalnızca mesajın var olan dosyalarına bakar. Yeni dosya yüklemek isteyenmultipartistekinvalid_bodyile reddedilir; yeni görsel yeni bir mesajla gönderilir.Mesajda metin, embed ve dosya kalmıyorsa
contentiçinrequireddö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
| Hata | Neden | Ne yapmalı |
|---|---|---|
embeds[0].image.url: invalid | Adres attachment:// ile başlamıyor, dosya mesajla gönderilmemiş, görsel değil ya da aynı adlı iki dosya var | Dosyayı aynı istekte, görsel olarak ve tekil adla yükle |
embeds: too_long | Toplam 6000 karakter ya da 16384 bayt aşıldı, ya da 50'den çok özel emoji var | Metni kısalt ya da birkaç mesaja böl |
embeds[0]: required | Embed boş | En az bir alan doldur |
content: required | Yalnız bileşen gönderildi ya da düzenleme sonrası mesaj boş kalıyor | Metin, embed veya dosya ekle |
components[0].components[0].emoji: invalid | Botun kullanamadığı özel emoji | Botun kullanabildiği bir emoji ya da {"name":"👍"} yaz |
ephemeral_forbidden | Kanala giden mesajda flags: 64 | flags alanını çıkar |
automod_blocked | Sunucunun otomatik moderasyonu embed metnini ya da buton yazısını engelledi | Metni değiştir |
invalid_body | embeds liste olmalı ama başka tür gönderildi | Gövdeyi kontrol et |
429 ve X-RateLimit-Scope: shared | Ortak bütçe dolu | retry_after kadar bekle |
Bu sayfayı yapay zekaya verirsen
Sırada ne var
Bileşenler: mesajın altına eklenen buton ve seçim menüleri
Etkileşimler: bileşen ve komut yanıtlarının yaşam döngüsü
Hız sınırları: embed yayılma bütçesi ve
429başlıkları