Discord Botu Nasıl Yapılır: Genel Bakış ve Eğitim

Yayınlanan: 2022-03-11

Discord, kendisini "oyuncular için hepsi bir arada sesli ve yazılı sohbet" olarak faturalandıran gerçek zamanlı bir mesajlaşma platformudur. Kaygan arayüzü, kullanım kolaylığı ve kapsamlı özellikleri nedeniyle Discord hızlı bir büyüme yaşadı ve video oyunlarına çok az ilgi duyanlar arasında bile giderek daha popüler hale geliyor. Mayıs 2017 ile Mayıs 2018 arasında, kullanıcı tabanı 45 milyon kullanıcıdan 130 milyonun üzerine çıktı ve günlük kullanıcı sayısı Slack'in iki katından fazla oldu.

Bir sohbet robotu geliştiricisi açısından Discord'un en çekici özelliklerinden biri, Discord'u dış dünyayla entegre etmeye ve kullanıcılara daha ilgi çekici bir deneyim sunmaya yardımcı olan programlanabilir botlar için güçlü desteğidir. Botlar Discord'da her yerde bulunur ve moderasyon yardımı, oyunlar, müzik, internet aramaları, ödeme işlemleri ve daha fazlasını içeren çok çeşitli hizmetler sunar.

Bu Discord bot eğitiminde, JavaScript'te basit bir Discord botu yazacağımız bir eğitime geçmeden önce Discord kullanıcı arayüzünü ve botlar için REST ve WebSocket API'lerini tartışarak başlayacağız. Son olarak, belirli ölçütlere göre Discord'un en popüler botunun geliştiricisinden ve önemli altyapısını ve kod tabanını geliştirme ve sürdürme konusundaki deneyimlerini öğreneceğiz.

Discord Kullanıcı Arayüzü

Teknik ayrıntıları tartışmadan önce, bir kullanıcının Discord ile nasıl etkileşime girdiğini ve Discord'un kendisini kullanıcılara nasıl sunduğunu anlamak önemlidir. Kendisini botlara sunma şekli kavramsal olarak benzerdir (ancak elbette görsel değildir). Aslında, resmi Discord uygulamaları, botların kullandığı API'ler üzerine kuruludur. Normal bir kullanıcı hesabının içinde küçük bir değişiklikle bot çalıştırmak teknik olarak mümkündür, ancak bu Discord'un hizmet koşulları tarafından yasaklanmıştır. Botların bot hesaplarında çalışması gerekir.

Chrome'un içinde çalışan Discord uygulamasının tarayıcı sürümü 1'e bir göz atın.

Anlaşmazlık Web Kullanıcı Arayüzü

1 Masaüstü uygulaması için Discord Kullanıcı Arayüzü, Electron ile paketlenmiş web uygulamasıyla hemen hemen aynıdır. iOS uygulaması React Native ile oluşturulmuştur. Android uygulaması, yerel Android Java kodudur.

Hadi parçalayalım.

1. Sunucu Listesi

En solda, üyesi olduğum sunucuların listesi var. Slack'e aşina iseniz, bir sunucu Slack çalışma alanına benzer ve sunucudaki bir veya daha fazla kanal içinde birbirleriyle etkileşime girebilen bir grup kullanıcıyı temsil eder. Bir sunucu, yaratıcısı ve/veya seçtikleri ve sorumlulukları devretmeyi seçtikleri personel tarafından yönetilir. Oluşturan ve/veya personel sunucudaki kuralları, kanalların yapısını tanımlar ve kullanıcıları yönetir.

Benim durumumda, Discord API sunucusu sunucu listemin başında. Yardım almak ve diğer geliştiricilerle konuşmak için harika bir yerdir. Bunun altında Test adında oluşturduğum bir sunucu var. Oluşturduğumuz botu daha sonra orada test edeceğiz. Bunun altında yeni bir sunucu oluşturmak için bir düğme var. Herkes birkaç tıklamayla bir sunucu oluşturabilir.

Discord'un kullanıcı arayüzünde kullanılan terim Sunucu iken, geliştirici belgelerinde ve API'de kullanılan terimin Guild olduğunu unutmayın. Teknik konular hakkında konuşmaya başladığımızda, Loncalar hakkında konuşmaya geçeceğiz. İki terim birbirinin yerine kullanılabilir.

2. Kanal Listesi

Sunucu listesinin hemen sağında, şu anda görüntülediğim sunucunun (bu durumda Discord API sunucusu) kanal listesi var. Kanallar keyfi sayıda kategoriye ayrılabilir. Discord API sunucusunda, kategoriler gösterildiği gibi BİLGİ, GENEL ve LIBS'yi içerir. Her kanal, kullanıcıların kanalın adandığı konuyu tartışabilecekleri bir sohbet odası işlevi görür. Şu anda görüntülediğimiz kanalın (bilgi) daha açık bir arka planı var. Son görüntülediğimizden beri yeni mesajları olan kanalların metin rengi beyazdır.

3. Kanal Görünümü

Bu, şu anda izlediğimiz kanalda kullanıcıların ne hakkında konuştuğunu görebileceğimiz kanal görünümüdür. Burada sadece kısmen görünen bir mesaj görebiliriz. Bireysel Discord bot kitaplıkları için sunucuları desteklemek için bağlantıların bir listesidir. Sunucu yöneticileri bu kanalı benim gibi normal kullanıcılar mesaj gönderemeyecek şekilde yapılandırdı. Yöneticiler, bu kanalı, bazı önemli bilgileri kolayca görülebilecek ve sohbet tarafından boğulmayacak şekilde yayınlamak için bir bülten tahtası olarak kullanır.

4. Kullanıcı Listesi

En sağda bu sunucuda şu anda çevrimiçi olan kullanıcıların bir listesi var. Kullanıcılar farklı kategorilere ayrılmıştır ve adları farklı renklerdedir. Bu, sahip oldukları rollerin bir sonucudur. Rol, kullanıcının hangi kategori (varsa) altında görünmesi gerektiğini, ad renginin ne olması gerektiğini ve sunucuda hangi izinlere sahip olduğunu açıklar. Bir kullanıcının birden fazla rolü olabilir (ve çoğu zaman vardır) ve bu durumda ne olacağını belirleyen bazı öncelik matematiği vardır. En azından, her kullanıcı @everyone rolüne sahiptir. Diğer roller, sunucu personeli tarafından oluşturulur ve atanır.

5. Metin Girişi

Bu, izin verilirse mesaj yazıp gönderebileceğim metin girişi. Bu kanalda mesaj gönderme iznim olmadığı için buraya yazamıyorum.

6. Kullanıcı

Bu mevcut kullanıcı. Kullanıcı adımı kafamın karışmaması ve isim seçme konusunda berbat olduğum için "Ben" olarak ayarladım. Kullanıcı adımın altında, ayrımcım olan bir sayı (#9484) var. “Ben” adında başka birçok kullanıcı olabilir ama tek “Ben#9484” benim. Ayrıca sunucu bazında kendime bir rumuz belirlemem de mümkün, böylece farklı sunucularda farklı isimlerle tanınabilirim.

Bunlar, Discord kullanıcı arayüzünün temel parçalarıdır, ancak çok daha fazlası da vardır. Bir hesap oluşturmadan bile Discord'u kullanmaya başlamak kolaydır, bu yüzden biraz araştırma yapmaktan çekinmeyin. Discord ana sayfasını ziyaret ederek, “Discord'u bir tarayıcıda aç” seçeneğini tıklayarak ve bir kullanıcı adı seçerek ve muhtemelen bir veya iki “otobüs resimlerine tıklayın” turu oynayarak Discord'a girebilirsiniz.

Anlaşmazlık API'sı

Discord API'si iki ayrı parçadan oluşur: WebSocket ve REST API'leri. Genel olarak konuşursak, WebSocket API, Discord'dan olayları gerçek zamanlı olarak almak için kullanılırken, REST API, Discord içindeki eylemleri gerçekleştirmek için kullanılır.

discord bot iletişim döngüsü nasıl yapılır

WebSocket API'si

WebSocket API, mesaj oluşturma, mesaj silme, kullanıcı kick/ban olayları, kullanıcı izni güncellemeleri ve çok daha fazlası dahil olmak üzere Discord'dan olayları almak için kullanılır. Öte yandan bir bottan WebSocket API'sine iletişim daha sınırlıdır. Bir bot, bağlantı istemek, kendini tanımlamak, kalp atışı yapmak, sesli bağlantıları yönetmek ve birkaç temel şey daha yapmak için WebSocket API'sini kullanır. Discord'un ağ geçidi belgelerinde daha fazla ayrıntı okuyabilirsiniz (WebSocket API'sine yapılan tek bir bağlantı, ağ geçidi olarak adlandırılır). Diğer eylemleri gerçekleştirmek için REST API kullanılır.

WebSocket API'sinden gelen olaylar, olayın türüne bağlı olan bilgileri içeren bir yük içerir. Örneğin, tüm Mesaj Oluşturma etkinliklerine, mesajın yazarını temsil eden bir kullanıcı nesnesi eşlik eder. Ancak, kullanıcı nesnesi tek başına kullanıcı hakkında bilinmesi gereken tüm bilgileri içermez. Örneğin, kullanıcının izinleri hakkında herhangi bir bilgi bulunmamaktadır. Daha fazla bilgiye ihtiyacınız varsa, bunun için REST API'sini sorgulayabilirsiniz, ancak bir sonraki bölümde daha ayrıntılı olarak açıklanan nedenlerden dolayı, bunun yerine önceki olaylardan alınan yüklerden oluşturmanız gereken önbelleğe genel olarak erişmeniz gerekir. Guild Create , Guild Role Update ve Channel Update dahil ancak bunlarla sınırlı olmamak üzere, bir kullanıcının izinleriyle ilgili yükleri sağlayan bir dizi olay vardır.

Bir bot, WebSocket bağlantısı başına maksimum 2.500 loncada bulunabilir. Bir botun daha fazla loncada bulunmasına izin vermek için, botun parçalama uygulaması ve Discord'a birkaç ayrı WebSocket bağlantısı açması gerekir. Botunuz tek bir düğümde tek bir işlemin içinde çalışıyorsa, bu sizin için gereksiz görünebilecek ek karmaşıklıktır. Ancak botunuz çok popülerse ve arka ucunun ayrı düğümler arasında dağıtılması gerekiyorsa, Discord'un parçalama desteği bunu normalde olduğundan çok daha kolay hale getirir.

REST API'si

Discord REST API, botlar tarafından mesaj gönderme, kullanıcıları tekmeleme/yasaklama ve kullanıcı izinlerini güncelleme (genel olarak WebSocket API'sinden alınan olaylara benzer) gibi çoğu eylemi gerçekleştirmek için kullanılır. REST API, bilgi sorgulamak için de kullanılabilir; ancak, botlar bunun yerine esas olarak WebSocket API'sinden gelen olaylara dayanır ve WebSocket olaylarından alınan bilgileri önbelleğe alır.

Bunun birkaç nedeni var. Örneğin, her Message Create olayı alındığında kullanıcı bilgilerini almak için REST API'sini sorgulamak, REST API'nin hız sınırları nedeniyle ölçeklenmez. WebSocket API gerekli bilgileri sağladığından ve önbelleğinizde olması gerektiğinden, çoğu durumda gereksizdir.

Ancak bazı istisnalar vardır ve bazen önbelleğinizde bulunmayan bilgilere ihtiyaç duyabilirsiniz. Bir bot başlangıçta bir WebSocket ağ geçidine bağlandığında, botun o parçada bulunduğu lonca başına bir Ready olayı ve bir Guild Create olayı başlangıçta bota gönderilir, böylece önbelleğini mevcut durumla doldurabilir. Lonca Oluşturma etkinlikleri, yoğun nüfuslu loncalar için yalnızca çevrimiçi kullanıcılar hakkında bilgi içerir. Botunuzun çevrimdışı bir kullanıcı hakkında bilgi alması gerekiyorsa, ilgili bilgiler önbelleğinizde bulunmayabilir. Bu durumda REST API'ye istekte bulunmak mantıklıdır. Veya, sık sık çevrimdışı kullanıcılar hakkında bilgi almaya ihtiyaç duyuyorsanız, çevrimdışı lonca üyeleri istemek için WebSocket API'sine bir Lonca Üyeleri İste işlem kodu göndermeyi tercih edebilirsiniz.

Başka bir istisna, uygulamanızın WebSocket API'sine hiç bağlı olmamasıdır. Örneğin, botunuzun, kullanıcıların oturum açabileceği ve sunucularında bot ayarlarını değiştirebileceği bir web panosu varsa. Web panosu, WebSocket API'sine herhangi bir bağlantı olmadan ve Discord'dan veri önbelleği olmadan ayrı bir işlemde çalışıyor olabilir. Yalnızca ara sıra birkaç REST API isteği yapması gerekebilir. Bu tür bir senaryoda, ihtiyacınız olan bilgiyi almak için REST API'ye güvenmek mantıklıdır.

API Sarmalayıcıları

Teknoloji yığınınızın her düzeyi hakkında biraz bilgi sahibi olmak her zaman iyi bir fikir olsa da, Discord WebSocket ve REST API'lerini doğrudan kullanmak zaman alıcı, hataya açık, genellikle gereksiz ve aslında tehlikelidir.

Discord, resmi olarak incelenmiş kitaplıkların derlenmiş bir listesini sağlar ve şu konuda uyarır:

API'yi kötüye kullanan veya aşırı hız sınırlarına neden olan özel uygulamaların veya uyumlu olmayan kitaplıkların kullanılması kalıcı bir yasaklamayla sonuçlanabilir.

Discord tarafından resmi olarak incelenen kitaplıklar genellikle olgun, iyi belgelenmiştir ve Discord API'sinin tam kapsamına sahiptir. Çoğu bot geliştiricisinin, merak veya cesaret dışında özel bir uygulama geliştirmek için hiçbir zaman iyi bir nedeni olmayacak!

Şu anda, resmi olarak denetlenen kitaplıklar Crystal, C#, D, Go, Java, JavaScript, Lua, Nim, PHP, Python, Ruby, Rust ve Swift için uygulamaları içeriyor. Seçtiğiniz dil için iki veya daha fazla farklı kitaplık olabilir. Hangisini kullanacağınızı seçmek zor bir karar olabilir. İlgili belgelere göz atmanın yanı sıra, resmi olmayan Discord API sunucusuna katılmak ve her kitaplığın arkasında ne tür bir topluluk olduğuna dair bir fikir edinmek isteyebilirsiniz.

Discord Botu Nasıl Yapılır

Hadi işe başlayalım. Sunucumuzda takılan ve Ko-fi'den web kancalarını dinleyen bir Discord botu oluşturacağız. Ko-fi, PayPal hesabınıza kolayca bağış kabul etmenizi sağlayan bir hizmettir. Bir işletme hesabına sahip olmanız gereken PayPal'ın aksine, orada web kancaları kurmak çok basittir, bu nedenle tanıtım amaçlı veya küçük ölçekli bağış işlemleri için harikadır.

Bir kullanıcı 10 ABD doları veya daha fazla bağış yaptığında, bot onlara ad rengini değiştiren ve onları çevrimiçi kullanıcılar listesinin en üstüne taşıyan bir Premium Member rolü atayacaktır. Bu proje için Node.js ve Eris adında bir Discord API kitaplığı kullanacağız (dokümantasyon bağlantısı: https://abal.moe/Eris/). Eris, tek JavaScript kitaplığı değildir. Bunun yerine discord.js'yi seçebilirsiniz. Yazacağımız kod her iki şekilde de çok benzer olacaktır.

Bir kenara, başka bir bağış işlemcisi olan Patreon, resmi bir Discord botu sağlar ve katılımcı faydaları olarak Discord rollerinin yapılandırılmasını destekler. Benzer bir şey uygulayacağız, ancak elbette daha temel.

Eğitimin her adımının kodu GitHub'da (https://github.com/mistval/premium_bot) mevcuttur. Bu gönderide gösterilen adımlardan bazıları, kısalık için değiştirilmemiş kodu atlar, bu nedenle bir şeyi kaçırmış olabileceğinizi düşünüyorsanız, sağlanan GitHub bağlantılarını izleyin.

Bot Hesabı Oluşturma

Kod yazmaya başlamadan önce bir bot hesabına ihtiyacımız var. Bot hesabı oluşturmadan önce bir kullanıcı hesabına ihtiyacımız var. Bir kullanıcı hesabı oluşturmak için buradaki talimatları izleyin.

Ardından, bir bot hesabı oluşturmak için şunları yaparız:

1) Geliştirici portalında bir uygulama oluşturun.

geliştiriciler portalının ekran görüntüsü

2) Başvuruyla ilgili bazı temel ayrıntıları girin (burada gösterilen MÜŞTERİ Kimliğini not edin; buna daha sonra ihtiyacımız olacak).

temel ayrıntıları doldurmanın ekran görüntüsü

3) Uygulamaya bağlı bir bot kullanıcısı ekleyin.

bot kullanıcısı eklemenin ekran görüntüsü

4) PUBLIC BOT anahtarını kapatın ve gösterilen bot jetonunu not edin (buna daha sonra da ihtiyacımız olacak). Örneğin, bir Toptal Blog gönderisinde bir resimde yayınlayarak bot jetonunuzu sızdırırsanız, onu hemen yeniden oluşturmanız zorunludur. Bot jetonunuza sahip olan herkes botunuzun hesabını kontrol edebilir ve siz ve kullanıcılarınız için potansiyel olarak ciddi ve kalıcı sorunlara neden olabilir.

"vahşi bir bot ortaya çıktı" ekran görüntüsü

5) Botu test loncanıza ekleyin. Bir loncaya bot eklemek için, müşteri kimliğini (daha önce gösterilen) aşağıdaki URI'de değiştirin ve bir tarayıcıda ona gidin.

https://discordapp.com/api/oauth2/authorize?scope=bot&client_id=XXX

Botu test loncanıza ekleyin

Yetkilendir'e tıkladıktan sonra bot artık benim test loncamda ve onu kullanıcılar listesinde görebiliyorum. Çevrimdışı, ancak bunu yakında düzelteceğiz.

Proje Oluşturma

Node.js'nin kurulu olduğunu varsayarsak, bir proje oluşturun ve Eris'i (kullanacağımız bot kitaplığı), Express'i (bir web kancası dinleyicisi oluşturmak için kullanacağımız bir web uygulaması çerçevesi) ve gövde ayrıştırıcısını (web kancası gövdelerini ayrıştırmak için) kurun. ).

 mkdir premium_bot cd premium_bot npm init npm install eris express body-parser

Botu Çevrimiçi ve Duyarlı Hale Getirmek

Bebek adımlarıyla başlayalım. İlk önce botu çevrimiçi hale getireceğiz ve bize yanıt vereceğiz. Bunu 10-20 satırlık kodda yapabiliriz. Yeni bir bot.js dosyasının içinde, bir Eris Client örneği oluşturmamız, ona bot jetonumuzu iletmemiz (yukarıda bir bot uygulaması oluşturduğumuzda edinildi), Client örneğindeki bazı olaylara abone olmamız ve Discord'a bağlanmasını söylememiz gerekiyor. . Gösteri amacıyla, bot jetonumuzu bot.js dosyasına sabit kodlayacağız, ancak ayrı bir yapılandırma dosyası oluşturmak ve onu kaynak kontrolünden muaf tutmak iyi bir uygulamadır.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step1.js)

 const eris = require('eris'); // Create a Client instance with our bot token. const bot = new eris.Client('my_token'); // When the bot is connected and ready, log to console. bot.on('ready', () => { console.log('Connected and ready.'); }); // Every time a message is sent anywhere the bot is present, // this event will fire and we will check if the bot was mentioned. // If it was, the bot will attempt to respond with "Present". bot.on('messageCreate', async (msg) => { const botWasMentioned = msg.mentions.find( mentionedUser => mentionedUser.id === bot.user.id, ); if (botWasMentioned) { try { await msg.channel.createMessage('Present'); } catch (err) { // There are various reasons why sending a message may fail. // The API might time out or choke and return a 5xx status, // or the bot may not have permission to send the // message (403 status). console.warn('Failed to respond to mention.'); console.warn(err); } } }); bot.on('error', err => { console.warn(err); }); bot.connect();

Her şey yolunda giderse, bu kodu kendi bot jetonunuzla çalıştırdığınızda, Connected and ready. konsola yazdırılacak ve botunuzun test sunucunuzda çevrimiçi olduğunu göreceksiniz. Botunuzdan 2'ye sağ tıklayıp “Bahset” seçeneğini seçerek veya adının önüne @ yazarak bahsedebilirsiniz. Bot, “Mevcut” diyerek yanıt vermelidir.

botunuz mevcut

2 Bahsetme, orada olmasalar bile başka bir kullanıcının dikkatini çekmenin bir yoludur. Normal bir kullanıcı, bahsedildiğinde, masaüstü bildirimi, mobil anında iletme bildirimi ve/veya Discord'un sistem tepsisindeki simgesinin üzerinde görünen küçük bir kırmızı simge ile bilgilendirilecektir. Bir kullanıcının bilgilendirilme şekli/yolları, ayarlarına ve çevrimiçi durumlarına bağlıdır. Botlar ise adı geçtiğinde herhangi bir özel bildirim almazlar. Diğer mesajlarda olduğu gibi normal bir Mesaj Oluşturma olayı alırlar ve olaya eklenmiş bahsedenleri kontrol edip bahsedilip bahsedilmediğini kontrol edebilirler.

Ödeme Komutunu Kaydet

Artık çevrimiçi bir bot alabileceğimizi bildiğimize göre, mevcut Message Create olay işleyicimizden kurtulalım ve bir kullanıcıdan ödeme aldığımızı bota bildirmemizi sağlayan yeni bir tane oluşturalım.

Botu ödeme konusunda bilgilendirmek için şuna benzeyen bir komut vereceğiz:

 pb!addpayment @user_mention payment_amount

Örneğin, benim tarafımdan yapılan 10,00$'lık bir ödemeyi kaydetmek için pb!addpayment @Me 10.00 .

Polis! parçasına komut öneki denir. Botunuza yönelik tüm komutların başlaması gereken bir önek seçmek iyi bir kuraldır. Bu, botlar için bir ad alanı ölçüsü oluşturur ve diğer botlarla çarpışmayı önlemeye yardımcı olur. Çoğu bot bir yardım komutu içerir, ancak loncanızda on bot varsa ve hepsi yardım etmek için yanıt verirse, karmaşayı hayal edin! pb'yi kullanma! Aynı öneki kullanan başka botlar da olabileceğinden, bir önek kusursuz bir çözüm değildir. Çoğu popüler bot, çarpışmayı önlemeye yardımcı olmak için öneklerinin lonca bazında yapılandırılmasına izin verir. Başka bir seçenek de, komutları daha ayrıntılı hale getirmesine rağmen, botun kendi sözünü önek olarak kullanmaktır.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step2.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const bot = new eris.Client('my_token'); const commandHandlerForCommandName = {}; commandHandlerForCommandName['addpayment'] = (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); // TODO: Handle invalid command arguments, such as: // 1. No mention or invalid mention. // 2. No amount or invalid amount. return msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`); }; bot.on('messageCreate', async (msg) => { const content = msg.content; // Ignore any messages sent as direct messages. // The bot will only accept commands issued in // a guild. if (!msg.channel.guild) { return; } // Ignore any message that doesn't start with the correct prefix. if (!content.startsWith(PREFIX)) { return; } // Extract the parts of the command and the command name const parts = content.split(' ').map(s => s.trim()).filter(s => s); const commandName = parts[0].substr(PREFIX.length); // Get the appropriate handler for the command, if there is one. const commandHandler = commandHandlerForCommandName[commandName]; if (!commandHandler) { return; } // Separate the command arguments from the command prefix and command name. const args = parts.slice(1); try { // Execute the command. await commandHandler(msg, args); } catch (err) { console.warn('Error handling command'); console.warn(err); } }); bot.on('error', err => { console.warn(err); }); bot.connect();

Hadi deneyelim.

Bot ile etkileşim

Botun sadece pb!addpayment komutuna yanıt vermesini sağlamakla kalmadık, aynı zamanda komutları işlemek için genelleştirilmiş bir model oluşturduk. Sadece commandHandlerForCommandName sözlüğüne daha fazla işleyici ekleyerek daha fazla komut ekleyebiliriz. Burada basit bir komut çerçevesi yapımına sahibiz. Komutları işlemek, bir bot yapmanın o kadar temel bir parçasıdır ki, birçok insan kendi yazmak yerine kullanabileceğiniz açık kaynaklı komut çerçeveleri yazmış ve açık kaynaklıdır. Komut çerçeveleri genellikle bekleme sürelerini, gerekli kullanıcı izinlerini, komut takma adlarını, komut açıklamalarını ve kullanım örneklerini (otomatik olarak oluşturulan bir yardım komutu için) ve daha fazlasını belirtmenize olanak tanır. Eris, yerleşik bir komut çerçevesi ile birlikte gelir.

İzinlerden bahsetmişken, botumuzda biraz güvenlik sorunu var. Herkes addpayment komutunu çalıştırabilir. Kısıtlayalım ki sadece bot sahibi kullanabilsin. commandHandlerForCommandName sözlüğünü yeniden değerlendireceğiz ve değerleri olarak JavaScript nesnelerini içermesini sağlayacağız. Bu nesneler, bir komut işleyicisi olan bir execute özelliğini ve boole değeri olan bir botOwnerOnly özelliğini içerecektir. Ayrıca, sahibinin kim olduğunu bilmesi için kullanıcı kimliğimizi botun sabitler bölümüne sabitleyeceğiz. Discord ayarlarınızda Geliştirici Modunu etkinleştirip ardından kullanıcı adınıza sağ tıklayıp Kimliği Kopyala'yı seçerek kullanıcı kimliğinizi bulabilirsiniz.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step3.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const BOT_OWNER_; const bot = new eris.Client('my_token'); const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); // TODO: Handle invalid command arguments, such as: // 1. No mention or invalid mention. // 2. No amount or invalid amount. return msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`); }, }; bot.on('messageCreate', async (msg) => { try { const content = msg.content; // Ignore any messages sent as direct messages. // The bot will only accept commands issued in // a guild. if (!msg.channel.guild) { return; } // Ignore any message that doesn't start with the correct prefix. if (!content.startsWith(PREFIX)) { return; } // Extract the parts and name of the command const parts = content.split(' ').map(s => s.trim()).filter(s => s); const commandName = parts[0].substr(PREFIX.length); // Get the requested command, if there is one. const command = commandForName[commandName]; if (!command) { return; } // If this command is only for the bot owner, refuse // to execute it for any other user. const authorIsBotOwner = msg.author.id === BOT_OWNER_ID; if (command.botOwnerOnly && !authorIsBotOwner) { return await msg.channel.createMessage('Hey, only my owner can issue that command!'); } // Separate the command arguments from the command prefix and name. const args = parts.slice(1); // Execute the command. await command.execute(msg, args); } catch (err) { console.warn('Error handling message create event'); console.warn(err); } }); bot.on('error', err => { console.warn(err); }); bot.connect();

Şimdi, bot sahibinden başka biri çalıştırmaya çalışırsa, bot öfkeyle addpayment komutunu çalıştırmayı reddedecektir.

Ardından, botun on dolar veya daha fazla bağış yapan herkese bir Premium Member rolü atamasını sağlayalım. bot.js dosyasının üst kısmında:

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step4.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const BOT_OWNER_; const PREMIUM_CUTOFF = 10; const bot = new eris.Client('my_token'); const premiumRole = { name: 'Premium Member', color: 0x6aa84f, hoist: true, // Show users with this role in their own section of the member list. }; async function updateMemberRoleForDonation(guild, member, donationAmount) { // If the user donated more than $10, give them the premium role. if (guild && member && donationAmount >= PREMIUM_CUTOFF) { // Get the role, or if it doesn't exist, create it. let role = Array.from(guild.roles.values()) .find(role => role.name === premiumRole.name); if (!role) { role = await guild.createRole(premiumRole); } // Add the role to the user, along with an explanation // for the guild log (the "audit log"). return member.addRole(role.id, 'Donated $10 or more.'); } } const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); const guild = msg.channel.guild; const userId = mention.replace(/<@(.*?)>/, (match, group1) => group1); const member = guild.members.get(userId); // TODO: Handle invalid command arguments, such as: // 1. No mention or invalid mention. // 2. No amount or invalid amount. return Promise.all([ msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`), updateMemberRoleForDonation(guild, member, amount), ]); }, };

Şimdi pb!addpayment @Me 10.00 ve bot bana Premium Member rolünü atamalıdır.

Hata! Konsolda Eksik İzinler hatası görünüyor.

 DiscordRESTError: DiscordRESTError [50013]: Missing Permissions index.js:85 code:50013

Bot, test loncasında Rolleri Yönetme iznine sahip değildir, bu nedenle rol oluşturamaz veya atayamaz. Bot'a Yönetici ayrıcalığı verebilirdik ve bir daha asla bu tür bir sorunla karşılaşmazdık, ancak herhangi bir sistemde olduğu gibi, yalnızca bir kullanıcıya (veya bu durumda bir bota) ihtiyaç duydukları minimum ayrıcalıkları vermek en iyisidir.

Sunucu ayarlarında bir rol oluşturarak, o rol için Rolleri Yönetme iznini etkinleştirerek ve rolü bot'a atayarak bota Rolleri Yönetme izni verebiliriz.

Rolleri yönetmeye başlayın

Yeni bir rol oluştur

Şimdi komutu tekrar çalıştırmayı denediğimde, rol oluşturulup bana atanıyor ve üye listesinde süslü bir isim rengim ve özel bir pozisyonum var.

Yeni bir rol atandı

Komut işleyicide, geçersiz argümanları kontrol etmemiz gerektiğini öneren bir TODO yorumumuz var. Şimdi bununla ilgilenelim.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step5.js)

 const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); const guild = msg.channel.guild; const userId = mention.replace(/<@(.*?)>/, (match, group1) => group1); const member = guild.members.get(userId); const userIsInGuild = !!member; if (!userIsInGuild) { return msg.channel.createMessage('User not found in this guild.'); } const amountIsValid = amount && !Number.isNaN(amount); if (!amountIsValid) { return msg.channel.createMessage('Invalid donation amount'); } return Promise.all([ msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`), updateMemberRoleForDonation(guild, member, amount), ]); }, };

İşte şimdiye kadar tam kod:

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step5.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const BOT_OWNER_; const PREMIUM_CUTOFF = 10; const bot = new eris.Client('my_token'); const premiumRole = { name: 'Premium Member', color: 0x6aa84f, hoist: true, // Show users with this role in their own section of the member list. }; async function updateMemberRoleForDonation(guild, member, donationAmount) { // If the user donated more than $10, give them the premium role. if (guild && member && donationAmount >= PREMIUM_CUTOFF) { // Get the role, or if it doesn't exist, create it. let role = Array.from(guild.roles.values()) .find(role => role.name === premiumRole.name); if (!role) { role = await guild.createRole(premiumRole); } // Add the role to the user, along with an explanation // for the guild log (the "audit log"). return member.addRole(role.id, 'Donated $10 or more.'); } } const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); const guild = msg.channel.guild; const userId = mention.replace(/<@(.*?)>/, (match, group1) => group1); const member = guild.members.get(userId); const userIsInGuild = !!member; if (!userIsInGuild) { return msg.channel.createMessage('User not found in this guild.'); } const amountIsValid = amount && !Number.isNaN(amount); if (!amountIsValid) { return msg.channel.createMessage('Invalid donation amount'); } return Promise.all([ msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`), updateMemberRoleForDonation(guild, member, amount), ]); }, }; bot.on('messageCreate', async (msg) => { try { const content = msg.content; // Ignore any messages sent as direct messages. // The bot will only accept commands issued in // a guild. if (!msg.channel.guild) { return; } // Ignore any message that doesn't start with the correct prefix. if (!content.startsWith(PREFIX)) { return; } // Extract the parts and name of the command const parts = content.split(' ').map(s => s.trim()).filter(s => s); const commandName = parts[0].substr(PREFIX.length); // Get the requested command, if there is one. const command = commandForName[commandName]; if (!command) { return; } // If this command is only for the bot owner, refuse // to execute it for any other user. const authorIsBotOwner = msg.author.id === BOT_OWNER_ID; if (command.botOwnerOnly && !authorIsBotOwner) { return await msg.channel.createMessage('Hey, only my owner can issue that command!'); } // Separate the command arguments from the command prefix and name. const args = parts.slice(1); // Execute the command. await command.execute(msg, args); } catch (err) { console.warn('Error handling message create event'); console.warn(err); } }); bot.on('error', err => { console.warn(err); }); bot.connect();

Bu size bir Discord botunun nasıl oluşturulacağı konusunda iyi bir temel fikir verecektir. Şimdi botu Ko-fi ile nasıl entegre edeceğimizi göreceğiz. İsterseniz, Ko-fi'deki panonuzda bir web kancası oluşturabilir, yönlendiricinizin 80 numaralı bağlantı noktasını iletecek şekilde yapılandırıldığından emin olabilir ve kendinize gerçek canlı test web kancaları gönderebilirsiniz. Ama istekleri simüle etmek için Postman'ı kullanacağım.

Ko-fi'den gelen Web kancaları, şuna benzeyen yükler sunar:

 data: { "message_id":"3a1fac0c-f960-4506-a60e-824979a74e74", "timestamp":"2017-08-21T13:04:30.7296166Z", "type":"Donation","from_name":"John Smith", "message":"Good luck with the integration!", "amount":"3.00", "url":"https://ko-fi.com" }

Webhook_listener.js adında yeni bir kaynak dosya oluşturalım ve web kancalarını dinlemek için Express'i kullanalım. Yalnızca bir Ekspres rotamız olacak ve bu tanıtım amaçlıdır, bu nedenle deyimsel bir dizin yapısı kullanma konusunda çok fazla endişelenmeyeceğiz. Tüm web sunucusu mantığını tek bir dosyaya koyacağız.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/webhook_listener_step6.js)

 const express = require('express'); const app = express(); const PORT = process.env.PORT || 80; class WebhookListener { listen() { app.get('/kofi', (req, res) => { res.send('Hello'); }); app.listen(PORT); } } const listener = new WebhookListener(); listener.listen(); module.exports = listener;

Ardından, bot.js'yi çalıştırdığımızda dinleyicinin başlaması için yeni dosyanın bot.js'nin en üstünde olmasını isteyelim.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step6.js)

 const eris = require('eris'); const webhookListener = require('./webhook_listener.js');

Botu başlattıktan sonra tarayıcınızda http://localhost/kofi'ye gittiğinizde “Merhaba” görmelisiniz.

Şimdi WebhookListener gelen verileri işlemesini ve bir olay yayınlamasını sağlayalım. Ve şimdi tarayıcımızın rotaya erişebildiğini test ettiğimize göre, Ko-fi'den gelen web kancası bir POST isteği olacağından rotayı bir POST rotasına değiştirelim.

(GitHub kod bağlantısı: https://github.com/mistval/premium_bot/blob/master/src/bot_step7.js)

 const express = require('express'); const bodyParser = require('body-parser'); const EventEmitter = require('events'); const PORT = process.env.PORT || 80; const app = express(); app.use(bodyParser.json()); class WebhookListener extends EventEmitter { listen() { app.post('/kofi', (req, res) => { const data = req.body.data; const { message, timestamp } = data; const amount = parseFloat(data.amount); const senderName = data.from_name; const paymentId = data.message_id; const paymentSource = 'Ko-fi'; // The OK is just for us to see in Postman. Ko-fi doesn't care // about the response body, it just wants a 200. res.send({ status: 'OK' }); this.emit( 'donation', paymentSource, paymentId, timestamp, amount, senderName, message, ); }); app.listen(PORT); } } const listener = new WebhookListener(); listener.listen(); module.exports = listener;

Next we need to have the bot listen for the event, decide which user donated, and assign them a role. To decide which user donated, we'll try to find a user whose username is a substring of the message received from Ko-fi. Donors must be instructed to provide their username (with the discriminator) in the message than they write when they make their donation.

At the bottom of bot.js:

(GitHub code link: https://github.com/mistval/premium_bot/blob/master/src/bot_step7.js)

 function findUserInString(str) { const lowercaseStr = str.toLowerCase(); // Look for a matching username in the form of username#discriminator. const user = bot.users.find( user => lowercaseStr.indexOf(`${user.username.toLowerCase()}#${user.discriminator}`) !== -1, ); return user; } async function onDonation( paymentSource, paymentId, timestamp, amount, senderName, message, ) { try { const user = findUserInString(message); const guild = user ? bot.guilds.find(guild => guild.members.has(user.id)) : null; const guildMember = guild ? guild.members.get(user.id) : null; return await updateMemberRoleForDonation(guild, guildMember, amount); } catch (err) { console.warn('Error handling donation event.'); console.warn(err); } } webhookListener.on('donation', onDonation); bot.connect();

In the onDonation function, we see two representations of a user: as a User, and as a Member. These both represent the same person, but the Member object contains guild-specific information about the User, such as their roles in the guild and their nickname. Since we want to add a role, we need to use the Member representation of the user. Each User in Discord has one Member representation for each guild that they are in.

Now I can use Postman to test the code.

Testing with Postman

I receive a 200 status code, and I get the role granted to me in the server.

If the message from Ko-fi does not contain a valid username; however, nothing happens. The donor doesn't get a role, and we are not aware that we received an orphaned donation. Let's add a log for logging donations, including donations that can't be attributed to a guild member.

First we need to create a log channel in Discord and get its channel ID. The channel ID can be found using the developer tools, which can be enabled in Discord's settings. Then you can right-click any channel and click “Copy ID.”

The log channel ID should be added to the constants section of bot.js.

(GitHub code link: https://github.com/mistval/premium_bot/blob/master/src/bot_step8.js)

 const LOG_CHANNEL_;

And then we can write a logDonation function.

(GitHub code link: https://github.com/mistval/premium_bot/blob/master/src/bot_step8.js)

 function logDonation(member, donationAmount, paymentSource, paymentId, senderName, message, timestamp) { const isKnownMember = !!member; const memberName = isKnownMember ? `${member.username}#${member.discriminator}` : 'Unknown'; const embedColor = isKnownMember ? 0x00ff00 : 0xff0000; const logMessage = { embed: { title: 'Donation received', color: embedColor, timestamp: timestamp, fields: [ { name: 'Payment Source', value: paymentSource, inline: true }, { name: 'Payment ID', value: paymentId, inline: true }, { name: 'Sender', value: senderName, inline: true }, { name: 'Donor Discord name', value: memberName, inline: true }, { name: 'Donation amount', value: donationAmount.toString(), inline: true }, { name: 'Message', value: message, inline: true }, ], } } bot.createMessage(LOG_CHANNEL_ID, logMessage); }

Now we can update onDonation to call the log function:

 async function onDonation( paymentSource, paymentId, timestamp, amount, senderName, message, ) { try { const user = findUserInString(message); const guild = user ? bot.guilds.find(guild => guild.members.has(user.id)) : null; const guildMember = guild ? guild.members.get(user.id) : null; return await Promise.all([ updateMemberRoleForDonation(guild, guildMember, amount), logDonation(guildMember, amount, paymentSource, paymentId, senderName, message, timestamp), ]); } catch (err) { console.warn('Error updating donor role and logging donation'); console.warn(err); } }

Now I can invoke the webhook again, first with a valid username, and then without one, and I get two nice log messages in the log channel.

Two nice messages

Previously, we were just sending strings to Discord to display as messages. The more complex JavaScript object that we create and send to Discord in the new logDonation function is a special type of message referred to as a rich embed. An embed gives you some scaffolding for making attractive messages like those shown. Only bots can create embeds, users cannot.

Now we are being notified of donations, logging them, and rewarding our supporters. We can also add donations manually with the addpayment command in case a user forgets to specify their username when they donate. Let's call it a day.

The completed code for this tutorial is available on GitHub here https://github.com/mistval/premium_bot

Sonraki adımlar

We've successfully created a bot that can help us track donations. Is this something we can actually use? Well, perhaps. It covers the basics, but not much more. Here are some shortcomings you might want to think about first:

  1. If a user leaves our guild (or if they weren't even in our guild in the first place), they will lose their Premium Member role, and if they rejoin, they won't get it back. We should store payments by user ID in a database, so if a premium member rejoins, we can give them their role back and maybe send them a nice welcome-back message if we were so inclined.
  2. Paying in installments won't work. If a user sends $5 and then later sends another $5, they won't get a premium role. Similar to the above issue, storing payments in a database and issuing the Premium Member role when the total payments from a user reaches $10 would help here.
  3. It's possible to receive the same webhook more than once, and this bot will record the payment multiple times. If Ko-fi doesn't receive or doesn't properly acknowledge a code 200 response from the webhook listener, it will try to send the webhook again later. Keeping track of payments in a database and ignoring webhooks with the same ID as previously received ones would help here.
  4. Our webhook listener isn't very secure. Anyone could forge a webhook and get a Premium Member role for free. Ko-fi doesn't seem to sign webhooks, so you'll have to rely on either no one knowing your webhook address (bad), or IP whitelisting (a bit better).
  5. The bot is designed to be used in one guild only.

Interview: When a Bot Gets Big

There are over a dozen websites for listing Discord bots and making them available to the public at large, including DiscordBots.org and Discord.Bots.gg. Although Discord bots are mostly the foray of small-time hobbyists, some bots experience tremendous popularity and maintaining them evolves into a complex and demanding job.

By guild-count, Rythm is currently the most widespread bot on Discord. Rythm is a music bot whose specialty is connecting to voice channels in Discord and playing music requested by users. Rythm is currently present in over 2,850,000 guilds containing a sum population of around 90 million users, and at its peak plays audio for around 100,000 simultaneous users in 20,000 separate guilds. Rythm's creator and main developer, ImBursting, kindly agreed to answer a few questions about what it's like to develop and maintain a large-scale bot like Rythm.

Interviewer: Can you tell us a bit about Rythm's high level architecture and how it's hosted?

ImBursting: Rythm is scaled across 9 physical servers, each have 32 cores, 96GB of RAM and a 10gbps connection. These servers are collocated at a data center with help from a small hosting company, GalaxyGate.

I imagine that when you started working on Rythm, you didn't design it to scale anywhere near as much as it has. Can you tell us about about how Rythm started, and its technical evolution over time?

Rythm's first evolution was written in Python, which isn't a very performant language, so around the time we hit 10,000 servers (after many scaling attempts) I realised this was the biggest roadblock and so I began recoding the bot to Java, the reason being Java's audio libraries were a lot more optimised and it was generally a better suited language for such a huge application. After re-coding, performance improved tenfold and kept the issues at bay for a while. And then we hit the 300,000 servers milestone when issues started surfacing again, at which point I realised that more scaling was required since one JVM just wasn't able to handle all that. So we slowly started implementing improvements and major changes like tuning the garbage collector and splitting voice connections onto separate microservices using an open source server called Lavalink. This improved performance quite a bit but the final round of infrastructure was when we split this into 9 seperate clusters to run on 9 physical servers, and made custom gateway and stats microservices to make sure everything ran smoothly like it would on one machine.

I noticed that Rythm has a canary version and you get some help from other developers and staff. I imagine you and your team must put a lot of effort into making sure things are done right. Can you tell us about what processes are involved in updating Rythm?

Rythm canary is the alpha bot we use to test freshly made features and performance improvements before usually deploying them to Rythm 2 to test on a wider scale and then production Rythm. The biggest issue we encounter is really long reboot times due to Discord rate limits, and is the reason I try my best to make sure an update is ready before deciding to push it.

I do get a lot of help from volunteer developers and people who genuinely want to help the community, I want to make sure everything is done correctly and that people will always get their questions answered and get the best support possible which means im constantly on the lookout for new opportunities.

Wrapping It Up

Discord's days of being a new kid on the block are past, and it is now one of the largest real-time communication platforms in the world. While Discord bots are largely the foray of small-time hobbyists, we may well see commercial opportunities increase as the population of the service continues to increase. Some companies, like the aforementioned Patreon, have already waded in.

In this article, we saw a high-level overview of Discord's user interface, a high-level overview of its APIs, a complete lesson in Discord bot programming, and we got to hear about what it's like to operate a bot at enterprise scale. I hope you come away interested in the technology and feeling like you understand the fundamentals of how it works.

Chatbots are generally fun, except when their responses to your intricate queries have the intellectual the depth of a cup of water. To ensure a great UX for your users see The Chat Crash - When a Chatbot Fails by the Toptal Design Blog for 5 design problems to avoid.

İlgili: JS En İyi Uygulamaları: TypeScript ve Dependency Injection ile bir Discord Botu Oluşturun