aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord Botları

Discord Profil Kartı: node-canvas ile Seviye Kartı

Bir discord profil kartı, kullanıcının avatarını, seviyesini ve sunucudaki rütbesini tek bir görselde toplayan o şık karttır. Hazır botlarda gördüğün bu kartları kendi botunda node-canvas ile çizebilirsin: arka plan, daire içine kırpılmış avatar, XP ilerleme çubuğu ve özel fontlar. Bu yazıda kartı sıfırdan oluşturmayı, oluşan görseli bir PNG olarak Discord'a göndermeyi ve performansı korumak için dikkat etmen gereken noktaları adım adım göreceksin.

Neden sunucu tarafında görsel çiziyoruz?

Discord embed'leri metin, alan ve tek bir resimle sınırlıdır; dinamik bir ilerleme çubuğunu ya da kullanıcıya özel düzeni embed ile çizemezsin. Çözüm, görseli sunucuda node-canvas ile üretip mesaja bir dosya (attachment) olarak eklemektir. node-canvas, tarayıcıdaki Canvas 2D API'sinin Node.js karşılığıdır; aynı fillRect, arc, drawImage çağrılarını kullanırsın ama çıktıyı bir Buffer olarak alırsın.

Kurulum için iki yaygın paket var. Klasik seçenek canvas paketidir (Cairo tabanlı, derleme gerektirebilir). Daha hızlı ve önceden derlenmiş ikili dosyalarla gelen modern alternatif ise @napi-rs/canvas'tır. API'leri büyük ölçüde aynıdır; bu yazıda klasik node-canvas üzerinden gideceğiz.

npm install canvas discord.js

Tuvali hazırlamak

İlk adım sabit boyutlu bir tuval oluşturmak. 934×282 piksel, bu tür kartlar için yaygın ve oransal olarak rahat bir boyuttur. getContext('2d') ile çizim bağlamını alır, ardından arka planı doldururuz.

const { createCanvas, loadImage } = require('canvas');

function roundRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
}

const canvas = createCanvas(934, 282);
const ctx = canvas.getContext('2d');

// Arka plan
ctx.fillStyle = '#23272a';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Yarı saydam iç panel
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
roundRect(ctx, 22, 22, 890, 238, 16);
ctx.fill();

roundRect yardımcı fonksiyonu yuvarlatılmış köşeler için arcTo kullanır. Modern Node sürümlerinde tuvalin yerleşik ctx.roundRect() metodu da vardır, ancak kendi fonksiyonunu yazmak her ortamda tutarlı sonuç verir.

Avatarı daire içine kırpmak

Kartın yıldızı avatardır. Avatarı dairesel göstermek için bir kırpma yolu (clip) tanımlar, sonra resmi o yolun içine çizeriz. discord.js'te kullanıcının avatar URL'sini user.displayAvatarURL() ile alırsın; extension: 'png' vermek, node-canvas'ın okuyamayacağı animasyonlu WebP/GIF sorununu önler.

const avatarURL = interaction.user.displayAvatarURL({
  extension: 'png',
  size: 256,
});
const avatar = await loadImage(avatarURL);

const cx = 141; // dairenin merkez X
const cy = 141; // dairenin merkez Y
const radius = 80;

ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
ctx.drawImage(avatar, cx - radius, cy - radius, radius * 2, radius * 2);
ctx.restore();

// Avatarın etrafına çerçeve
ctx.strokeStyle = '#5865f2';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.stroke();

Buradaki save() / restore() çifti kritik: clip() tüm sonraki çizimleri daireyle sınırlar, bu yüzden avatarı çizdikten hemen sonra bağlamı eski haline döndürmen gerekir; aksi halde metinler ve çubuk da dairenin içine hapsolur.

Metin, seviye ve rütbe yazmak

Profesyonel görünen bir kart için varsayılan sistem fontu yetmez. Özel bir font dosyasını (.ttf / .otf) registerFont ile kaydeder, sonra ctx.font içinde aynı family adıyla kullanırsın. Bu çağrı, tuval oluşturmadan önce yapılmalıdır.

const { registerFont } = require('canvas');
registerFont('./assets/Inter-Bold.ttf', { family: 'Inter', weight: 'bold' });
registerFont('./assets/Inter-Regular.ttf', { family: 'Inter' });

// ... kart çizimi içinde:
ctx.fillStyle = '#ffffff';
ctx.font = '40px "Inter"';
ctx.fillText(interaction.user.username, 250, 110);

ctx.fillStyle = '#b9bbbe';
ctx.font = '28px "Inter"';
ctx.fillText(`Seviye ${level}`, 250, 150);

// Rütbeyi sağa hizala
ctx.textAlign = 'right';
ctx.fillStyle = '#5865f2';
ctx.font = 'bold 34px "Inter"';
ctx.fillText(`#${rank}`, 890, 110);
ctx.textAlign = 'left'; // hizalamayı sıfırla

textAlign bağlamın genel durumunu değiştirir; sağa hizaladıktan sonra 'left''e döndürmezsen sonraki tüm yazılar sağdan hizalanır.

XP ilerleme çubuğu

İlerleme çubuğu iki dikdörtgenden oluşur: gri bir zemin ve onun üzerine oranla çizilen renkli bir dolgu. Oranı, kullanıcının mevcut XP'sini bir sonraki seviye için gereken XP'ye bölerek hesaplarsın.

const barX = 250, barY = 185, barW = 600, barH = 34;
const ratio = Math.min(currentXP / requiredXP, 1);

// Zemin
ctx.fillStyle = '#484b52';
roundRect(ctx, barX, barY, barW, barH, barH / 2);
ctx.fill();

// Dolu kısım (en az yarım daire genişliği)
const fillW = Math.max(ratio * barW, barH);
ctx.fillStyle = '#5865f2';
roundRect(ctx, barX, barY, fillW, barH, barH / 2);
ctx.fill();

// Üzerine XP metni
ctx.fillStyle = '#ffffff';
ctx.font = '20px "Inter"';
ctx.textAlign = 'center';
ctx.fillText(`${currentXP} / ${requiredXP} XP`, barX + barW / 2, barY + 24);
ctx.textAlign = 'left';

Math.max(ratio * barW, barH), XP düşük olduğunda yuvarlatılmış çubuğun garip görünmesini önler; dolgu en az kendi yüksekliği kadar geniş kalır.

Kartı Discord'a göndermek

Son adımda tuvali bir PNG buffer'ına çevirir, AttachmentBuilder ile dosyaya dönüştürür ve etkileşimi yanıtlarsın. Görsel oluşturma birkaç yüz milisaniye sürebileceğinden, üç saniyelik etkileşim zaman aşımına yakalanmamak için önce deferReply() çağırmak güvenlidir.

const { AttachmentBuilder } = require('discord.js');

await interaction.deferReply();

// ... yukarıdaki tüm çizim adımları ...

const buffer = canvas.toBuffer('image/png');
const file = new AttachmentBuilder(buffer, { name: 'profil.png' });

await interaction.editReply({ files: [file] });

Aynı düzeni bir /rank komutuna, hoş geldin mesajına ya da bir seviye atlama bildirimine bağlayabilirsin. Veriyi (seviye, XP, rütbe) genellikle SQLite veya MySQL'den çekersin; kart yalnızca o veriyi görselleştiren katmandır.

Sık Sorulan Sorular

node-canvas mı yoksa @napi-rs/canvas mı kullanmalıyım?

İkisi de aynı işi görür. canvas (node-canvas) olgun ve yaygındır ama bazı sistemlerde Cairo derlemesi gerektirir. @napi-rs/canvas önceden derlenmiş ikililerle gelir, kurulumu sorunsuzdur ve genelde daha hızlıdır. Yeni bir projede ikincisini denemen makul olur; API neredeyse aynıdır.

Avatar bazen yüklenmiyor, neden?

En sık neden animasyonlu avatarlardır: varsayılan URL bir .gif döndürebilir ve node-canvas bunu çizemez. displayAvatarURL({ extension: 'png' }) diyerek her zaman statik PNG iste. Ayrıca avatar URL'sini await loadImage(...) ile beklediğinden emin ol.

Her komutta kart üretmek botu yavaşlatır mı?

Tek tek çağrılar hafiftir, ancak yoğun sunucularda CPU'yu doldurabilir. Aynı kullanıcının kartını kısa süre önbelleğe al, ortak arka planı bir kez loadImage ile yükleyip tekrar kullan ve gerekirse görsel üretimini bir kuyruğa (queue) taşı.

Botuna profesyonel bir seviye sistemi mi istiyorsun? Avatar, XP çubuğu ve sıralama içeren özel profil kartları, ekonomi ve moderasyon sistemleriyle birlikte uçtan uca kurabilirim. Benimle iletişime geç ve projeni konuşalım.

Bu kategorideki tüm yazılar →

Devamı için