Node-RED’de Function Düğümü ile CAPTCHA Nasıl Çözülür

node-red captcha - How to Solve CAPTCHAs in Node-RED With a Function Node

Ham API üzerinden giderseniz bir Node-RED captcha adımı iki düğümdür; beş satır JavaScript yazmayı tercih ederseniz tek bir function düğümüdür. Her ikisi de kendi donanımınızda çalışan bir çözücüyü çağırır, yani hiçbiri sizi çözüm başına saymaz. İnsanların takıldığı kısım çözme işi değil. Asıl mesele, bir function düğümünün settings.js içinde bu özelliği açana kadar bir npm paketini require edememesi ve Node-RED’in çok sık bir Raspberry Pi üzerinde çalışması, çözücünün ise orada çalışmamasıdır.

Neye ihtiyacınız var

  • Node-RED 3 ya da üzeri; istediğiniz yerde çalışıyor olabilir.
  • CapSkip’in çalışıyor ve dinliyor olması. Node-RED aynı Windows makinesindeyse Local mod, değilse Server mod. Her ikisi de şurada anlatılıyor: bağlantı ayarları.
  • Korumalı formun sayfa URL’si ve sitekey'i.
  • Ham API yolu yerine SDK yolunu istiyorsanız settings.js için düzenleme erişimi.

Buradaki hiçbir şey tarayıcı gerektirmiyor. Node-RED Chrome’u sürmüyor, HTTP çağrıları yapıyor; bu yüzden flow, sitekey’i sayfa HTML’inden okuyor ve token’ı sıradan bir form alanı olarak geri gönderiyor.

Bir flow’dan çözücüyü çağırmanın iki yolu

Bağlantıları kurmaya başlamadan önce birini seçin, çünkü çok farklı flow’lara götürüyorlar.

Hangi yolSize neye mal olurNe zaman doğru seçimdir
Ham API’ye karşı http request düğümleriBir gönderim düğümü, bir delay, bir yoklama düğümü ve döngü için bir switchsettings.js’yi düzenleyemiyorsanız ya da flow’un canvas üzerinde okunabilir olmasını istiyorsanız
function düğümü içinde Node SDKBir settings.js satırı ve Setup sekmesinde bir modülYoklamanın, backoff’un ve zaman aşımlarının sizin yerinize halledilmesini istiyorsanız

İkinci yol, bilmeye değer bir nedenle daha kısa. Ham API size on beş saniye bekleyip sonra her beş saniyede bir yoklama yapmanızı söyler ve elle kurulmuş bir flow bunu birebir uygular. SDK ise yoklamaya 250ms’de başlar ve bir tavana kadar aralığı açar; bu yüzden token’ı genellikle bir delay düğümünün döndürebileceğinden gözle görülür biçimde daha erken döndürür.

Birinci yol: http request düğümleri ve ham API

API 2captcha uyumlu, yani iki uç nokta var. in.php’ye gönderirsiniz ve bir id alırsınız; sonra o id ile res.php’yi, cevabın hazır olmadığını söylemeyi bırakana kadar yoklarsınız. Dört düğüm, bir döngü halinde bağlanmış.

Flow’daki düğümAyar
Gönderimi yapan http request düğümühttp://127.0.0.1:8080/in.php adresine POST, Return ayarı ayrıştırılmış JSON nesnesi
id’yi saklayan bir function düğümümsg.payload.request değerini msg.captchaId olarak saklayın
Bir delay düğümüSabit gecikme, reCAPTCHA v2 için 15 saniye
Yoklama yapan http request düğümühttp://127.0.0.1:8080/res.php adresine GET, Return ayarı ayrıştırılmış JSON nesnesi
Bir switch düğümüYanıt hâlâ CAPCHA_NOT_READY iken delay’e geri döngü yapın

Gönderim düğümü gövdesini msg.payload’dan alır, bu yüzden onu öncesindeki bir function düğümünde oluşturun. json değerini 1 yapmak, yanıtları eski boru ile ayrılmış metin yerine JSON hâline getirir; bu da sizi bir string bölme işleminden kurtarır.

// Feed this into the submit node. No npm module needed.
msg.url = "http://127.0.0.1:8080/in.php";
msg.method = "POST";
msg.payload = {
    key: env.get("CAPSKIP_KEY") || "capskip",
    method: "userrecaptcha",
    googlekey: "YOUR_SITEKEY",
    pageurl: "https://example.com/page-with-recaptcha",
    json: 1
};
return msg;

Yoklama düğümü, id’yi sorgu dizesinde geri ister. URL’yi bir function düğümünde oluşturun ki http request düğümünün şablonlayacak bir şeyi kalmasın.

// After the delay. Loop back here until the answer arrives.
const key = env.get("CAPSKIP_KEY") || "capskip";
msg.url = "http://127.0.0.1:8080/res.php?key=" + key +
          "&action=get&id=" + msg.captchaId + "&json=1";
msg.method = "GET";
return msg;

Yanıtla ilgili iki nokta. request değeri CAPCHA_NOT_READY olan 0 status’u bir hata değildir, cevabın hâlâ sürdüğü anlamına gelir ve switch düğümünüzün delay’in etrafından geri gönderdiği dal budur. 1 status’u ise token’ın msg.payload.request içinde olduğu anlamına gelir. Gerçekten çözülemeyen bir challenge sonsuza kadar dönmesin diye döngüyü makul sayıda geçişle sınırlayın. Yoklama durumu şu rehberde daha ayrıntılı anlatılıyor: CAPCHA_NOT_READY yanıtı, her parametre de şurada listeleniyor: CapSkip API dokümantasyonu.

İkinci yol: function düğümü içinde SDK

Bir function düğümü, varsayılan olarak npm paketlerine erişimi olmayan bir sandbox içinde çalışır. Bunu iki ayar kontrol eder ve ikisi farklı davranır.

Eskisi functionGlobalContext: modülü settings.js içinde require eder, düğümün içinde bir global.get çağrısıyla geri okursunuz. Çalışır, ama örnekteki her function düğümü onu görür ve bir modül eklemek Node-RED’i yeniden başlatmak demektir.

Daha iyisi functionExternalModules. settings.js içinde true yapın; function düğümü, bir modülü ve görüneceği değişken adını yazdığınız bir Setup sekmesi kazanır. Node-RED, deploy sırasında modülü kullanıcı dizininize kurar ve onu yalnızca o düğüm görür.

// settings.js, in your Node-RED user directory.
module.exports = {
    // Lets a function node declare its own npm modules
    // on the Setup tab, installed on deploy.
    functionExternalModules: true,

    // The older, instance-wide alternative.
    // functionGlobalContext: { capskip: require("capskip") },
}

Node-RED’i yeniden başlatın, bir function düğümü açın, Setup sekmesine gidin ve capskip modülünü capskip değişken adı altında ekleyin. Bir kez deploy edin, kurulur. Bundan sonra düğüm gövdesi onu doğrudan kullanabilir.

Adım 1: function düğümünün içinde çözün

Bir çözüm, saniyeler süren bir ağ çağrısıdır; bu yüzden düğümün asenkron olarak bitmesi gerekir. Yani mesajı düz bir şekilde return edemezsiniz. Node-RED’in buradaki kuralı nettir: işi bir async blok içinde yapın, mesajı bir node.send çağrısıyla dışarı verin ve hiçbir şey iki kez yayılmasın diye düğüm gövdesinden null döndürün.

// npm install capskip - or add it on the Setup tab
const solver = new capskip.CapSkip({
    host: "127.0.0.1",
    port: 8080,
    apiKey: env.get("CAPSKIP_KEY") || "capskip"
});

(async () => {
    try {
        const result = await solver.recaptcha(msg.sitekey, msg.pageUrl);
        msg.token = result.code;    // inject this into the form
        node.send(msg);
    } catch (err) {
        node.error(err, msg);       // routes to a catch node
    }
    node.done();
})();

return null;

msg’i node.error’a ikinci argüman olarak geçirmek, bir catch düğümünün hatayı yakalamasını sağlayan şeydir. Bunu atlarsanız hata debug kenar çubuğuna düşer ve flow öylece durur; bu da bir Node-RED captcha dalının hiçbir şey yapmamış gibi görünmesinin en yaygın nedenidir.

Tek bir metot reCAPTCHA v2, Invisible, Enterprise ve v3’ü kapsar. Varyantlar ayrı çağrılar değil, seçeneklerdir: invisible bir widget, invisible değeri 1 olan bir seçenek nesnesi taşıyan aynı satırdır; v3 ise version değerinin v3 olması artı bir action demektir. Turnstile ve GeeTest’in aynı biçimde kendi metotları vardır ve hepsi şurada listeleniyor: CAPTCHA çözme SDK sayfası.

Adım 2: tek bir düğümde flow’un tamamı

Sayfayı alın, sitekey’i HTML’den çıkarın, çözün, sonra token’ı formun geri kalanıyla birlikte geri gönderin. Flow’un bir inject düğümü, bu function düğümü ve bir debug düğümünden ibaret olmasını istiyorsanız yapıştıracağınız sürüm budur.

// Module on the Setup tab: capskip. fetch is built in
// from Node 18, which Node-RED 3 and 4 both require.
const PAGE = "https://example.com/page-with-recaptcha";
const solver = new capskip.CapSkip({ host: "127.0.0.1", port: 8080 });

(async () => {
    try {
        const html = await (await fetch(PAGE)).text();
        const found = html.match(/data-sitekey=["']([^"']+)/);
        if (!found) { throw new Error("No data-sitekey on the page."); }

        // Solve, then submit straight away. Tokens go stale.
        const result = await solver.recaptcha(found[1], PAGE);
        const reply = await fetch(PAGE, {
            method: "POST",
            body: new URLSearchParams({
                "g-recaptcha-response": result.code
            })
        });

        msg.payload = { status: reply.status, token: result.code };
        node.send(msg);
    } catch (err) {
        node.error(err, msg);
    }
    node.done();
})();

return null;

Çözümü, gönderimden hemen önceki adımda yapın; asla başka bir şeyi bekleyen daha erken bir dalda değil. Bir reCAPTCHA token’ı yaklaşık iki dakika geçerlidir ve bir kez kabul edilir; bu yüzden çözen, sonra bir delay düğümünde bekleyen, sonra gönderen bir flow, çözücü sorununa hiç benzemeyen bir ret alır. Bu hata biçimini şu rehberden okumakta fayda var: reCAPTCHA token süresinin dolması.

Node-RED ile çözücüyü farklı makinelerde çalıştırmak

Bu konu Node-RED’de çoğu araçtakinden daha önemli, çünkü kurulumların büyük bir kısmı bir Raspberry Pi, bir NAS ya da küçük bir Linux makinesinde ve CapSkip bir Windows uygulaması. Kurulumunuz buysa 127.0.0.1 Pi’dir, çözücü orada değildir ve çağrı, API’ye hiç ulaşamadan bağlantı reddedildi hatasıyla başarısız olur.

Cevap Server mod ve bu farklı bir ürün değil, bir ayar değişikliği. Local mod 127.0.0.1’e bağlanır ve yalnızca o cihaza yanıt verir. Server mod ağ adresinize ya da genel IP’nize bağlanır; böylece bir Pi’deki, bir konteyner sunucusundaki veya barındırılan bir Node-RED örneğindeki flow, Windows makinesini aynı API üzerinden çağırır. Statik bir genel IP o adresi sabit tutar. Her iki durumda da donanım hâlâ sizin ve hâlâ sayaçsız, yani yoğun bir flow sakin bir flow’dan daha pahalıya gelmez.

// Same call, same SDK. Only the host moves.
const solver = new capskip.CapSkip({
    host: "10.0.0.12",
    port: 8080,
    apiKey: env.get("CAPSKIP_KEY")
});

Çözücü bir ağ adresini dinlemeye başladığında anahtar doğrulamasını açın ve her Node-RED örneğine kendi anahtarını verin; böylece biri, diğerlerine dokunmadan iptal edilebilir. Anahtarı düğüm gövdesine değil bir ortam değişkenine koyun: flows dosyası diskte JSON’dur ve çoğu zaman bir git deposunda son bulur. Her iki mod da şurada adım adım anlatılıyor: CapSkip kurulum rehberi.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
capskip is not definedModül Setup sekmesinde hiç tanımlanmadıfunctionExternalModules değerini true yapın, sonra modülü ekleyip deploy edin
function düğümü hiçbir şey yaymıyorMesaj, async bloktan gönderilmek yerine return edildinode.send çağırın ve düğüm gövdesinden null döndürün
Flow görünür bir hata olmadan duruyornode.error, mesaj argümanı olmadan çağrıldımsg’i ikinci argüman olarak geçirin ve bir catch düğümü bağlayın
connect ECONNREFUSED 127.0.0.1:8080Node-RED, çözücüyü çalıştıran makinede değilÇözücüyü Server moda alın ve host değerini onun adresi yapın
Yoklama döngüsü hiç bitmiyorswitch düğümünde deneme sınırı yokGeçişleri bir context değişkeninde sayın ve bir sınırdan sonra vazgeçin
Form, sorunsuz görünen bir token'ı reddediyorÇözüm birkaç düğüm önce yapıldıÇözümü gönderimden hemen önce yapın, daha erken bir dalda değil
ERROR_GOOGLEKEYsitekey o sayfa URL’sine ait değildata-sitekey değerini gönderim yaptığınız sayfadan yeniden okuyun

FAQ

SDK’ya ihtiyacım var mı, yoksa http request düğümleri yeter mi?

İkisi de çalışır. http request yolu settings.js erişimi gerektirmez ve her adımı canvas üzerinde görünür tutar; bazı ekipler denetim için bunu tercih eder. SDK yolu yoklamayı, backoff’u ve zaman aşımlarını sizin yerinize halleder ve token’ı genellikle daha hızlı döndürür, çünkü kontrole on beş saniye sonra değil çeyrek saniye sonra başlar.

Barındırılan bir Node-RED örneği masamdaki bir çözücüye ulaşabilir mi?

Yalnızca Server modda. Barındırılan bir örnek başkasının altyapısında çalışır, dolayısıyla oradaki 127.0.0.1 sizin makineniz değil onların konteyneridir. Çözücüyü ulaşılabilir bir adrese bağlayın, yalnızca platformun giden adreslerine izin veren bir güvenlik duvarı kuralının arkasına koyun ve anahtar doğrulamasını açın. Bağlantı ayarları sayfası kurulumun tamamını anlatıyor.

Bir flow’un çözücüyü boğmasını nasıl engellerim?

Çözüm düğümünün önüne rate limit modunda bir delay düğümü koyun. Mesajları kuyruğa alır ve sabit bir hızda bırakır; üç zamanlama aynı makineyi gösterdiğinde tam da istediğiniz kısıtlama budur. Sınır koymadan bir döngü içinde çözüm yapmak, bir yığın işin zaman aşımı yığınına dönüşmesinin alışılmış yoludur.

Bu, n8n’de yapmakla aynı şey mi?

Çözücü tarafı birebir aynı, flow tarafı değil. n8n, Code düğümünü npm kurulumu olmayan kilitli bir sandbox içinde çalıştırır; orada ya HTTP düğümleri vardır ya da hiçbir şey. Node-RED ise tek bir function düğümü için bir paketi seve seve kurar; SDK yolunun burada var olmasının nedeni bu. n8n sürümü şurada anlatılıyor: n8n CAPTCHA workflow rehberi.

Kısa özet

functionExternalModules’ı açın, Setup sekmesinde capskip modülünü ekleyin ve çözümü, bir node.send çağrısıyla biten ve null döndüren bir async blokta yapın. Bir catch düğümü bağlayın ve hataların görünür olması için mesajı node.error’a geçirin. Node-RED bir Pi’de, çözücü Windows’ta duruyorsa bu Server mod ve değişen tek bir host dizesi demektir. Node.js yüzeyinin geri kalanı şurada: Node.js CAPTCHA çözücü sayfası, reCAPTCHA seçenekleri de şurada: reCAPTCHA v2 çözücü sayfası.

O flow’u beş dakikalık bir zamanlamaya bağlamadan önce bilinmeye değer son bir şey. CapSkip, zaten sahip olduğunuz donanımda çalışan bir yerel captcha çözücü olduğundan, sonsuza kadar her beş dakikada bir tetiklenen bir flow, elle tetiklediğiniz bir flow ile tam olarak aynı maliyete sahiptir.