24 Ocak 2018 Çarşamba

node.js ile de workplace botu yazayım


Hey ho,, mono in da haus

Son birkaç aydır botlarla haşır neşirlik sonucu kendime bir bot özeti, how-to'su çıkarmak ve bu özeti paylaşmak niyetiyle sayfama uğramış bulunmaktayım :)

(Burada bir karikatür vardı ama telif yerim diye kaldırdım. Herkese dava açıyorlarmış karikatür paylaşılıyor diye. Karikatürün doğasına hiç uymuyor bence bu tavır :-/ Bunun muhabbeti de hiç çekilmiyor. )

:D Konuya geçeyim.. 

Daha önce .net core'la yapmıştım ama şimdi node.js'le yaptım. Çünkü node.js de canmış. :)

Yine bir workplace botunu örnekleyeceğim.

Önce botumuzu barındıracak bir klasör oluşturup terminal'de bu klasöre giriyoruz.

npm init
 komutunu çalıştırıp istenilen bilgileri mümkün olduğunca dolduruyoruz. Bu bilgiler project.json dosyamızı oluşturuyor.

Sonrasında klasörümüzü Visual Studio Code gibi bir editörle açıp içine app.js dosyamızı ekliyoruz. Bu dosyada kodlamaya başlayabiliriz.

Ardından botbuilder paketimizi eklemek için yine terminal'de şu komutu çalıştırıyoruz;

npm install --save botbuilder

Bir dee şu paketi yükleyelim;

npm install --save restify

Şuan .net core'da yaptığımız papağan botundan yapmaya başlamak için hazırız :) Hadi yapalım.

Şöyle bir repo'muz var. Buradaki Node örnekleri gayet yeterli başlangıç için;
https://github.com/Microsoft/BotBuilder

Buradaki hello-ChatConnector örneğini alalım;

var restify = require('restify');
var builder = require('botbuilder');

// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
   console.log('%s listening to %s', server.name, server.url);
});
 
// Bot Storage: Here we register the state storage for your bot.
// Default store: volatile in-memory store - Only for prototyping!
// We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own!
// For samples and documentation, see: https://github.com/Microsoft/BotBuilder-Azure
var inMemoryStorage = new builder.MemoryBotStorage();

// Create chat connector for communicating with the Bot Framework Service
var connector = new builder.ChatConnector({
    appId: process.env.MICROSOFT_APP_ID,
    appPassword: process.env.MICROSOFT_APP_PASSWORD
});

// Listen for messages from users
server.post('/api/messages', connector.listen());

// Create your bot with a function to receive messages from the user
var bot = new builder.UniversalBot(connector, function (session) {
    session.send("🐦 %s", session.message.text);}).set('storage', inMemoryStorage); // Register in memory storage


Şuan aslında kod olarak hazırız. Ama bu kadarla bitmiyor. Şimdi bu botumuzu yayınlama vakti.

Öncelikle Workplace'e  botumuzu Custom Integrations'dan ekleyelim.



Create Custom Integration butonuna tıklayıp botumuzun adını ve açıklamasını girelim.



Bot oluştuktan sonra bize vermiş olduğu App Id, App Secret ve özellikle tek sefer oluşturduktan sonra kaybetmemeniz gereken Access Token bilgilerinizi güvenli bir yere kaydedelim.

Sonrasında webhook'u ayarlamak için Azure'a uğramamız gerekecek evlerden ıraak :S :D

Adresimiz portal.azure.com
Buraya azure hesabımızla giriş yapalım.

Şöyle bir Dashboard gelecek önümüze;



Öncelikle Azure'dan bir "bot channel registration" oluşturalım.



Şimdi Bot Service oluşturma ekranımız gelecek. Buradaki messaging endpoint' çalışan bir endpoint vermemiz gerekiyor. O halde çözüm neydii? Tabii ki ngrok. :)

Şu komutu çalıştıralım;

ngrok http -region=eu -subdomain=zengindomaini 3978

Artık botumuz https://zengindomaini.eu.ngrok.io adresinde geçici olarak da olsa yayında.
Şöyle görelim vs code ekranımızı. Buradaki terminalde çalışmak da pratik oluyor;

Yeniden Azure'a dönebiliriz.

Messaging endpoint'teki adres şu;
https://zengindomaini.eu.ngrok.io/api/messages

çünkü mesajları kodumuzdan da görebileceğimiz gibi şuradan dinliyoruz;

// Listen for messages from users
server.post('/api/messages', connector.listen());




 Create butonuna bastıktan sonra bot servisimiz oluşmuş olmalı.

Hayırlı olsuun artık Azure'da da bir appid'miz ve appsecret'ımız var. Peki nerede? :D Ben şahsen çok zorlanmıştım bulmak için.. Ama bu sırrı sizinle de paylaşacağım.

Azure sol menüdeki Resource Groups'dan Deployments'a, oradan da ilgili deployment'a tıklayıp bilgilerini görebiliyorsunuz.



Bulduk ama bunlarla ne yapacağız? Kodumuzdaki şu kısım vardı ya, oraya işte bunların yerleri orası. Eğer bunları yazmazsanız debugda mesaj gönderme yerine düşer ama hata bot cevap vermez. Eğer tecrübesizseniz bir saat nedenini sorgulayabilirsiniz. Tecrübe konuşuyor :P

var connector = new builder.ChatConnector({
    appId: process.env.MICROSOFT_APP_ID, //APPID
    appPassword: process.env.MICROSOFT_APP_PASSWORD //APPSECRET
});

Eğer bu bilgileri environment olarak ayrı bir dosyadan okumak istiyorsanız

npm install --save dotenv

paketini yükleyip projenize .env uzantılı bir dosya oluşturup bu bilgileri buraya yazıp buradan okutabilirsiniz. Hatta 2 adet .env oluşturup live ve test key'lerini ayrı ayrı tutabilirsiniz.

Ben bunu vs code'un launch.json dosyasına aşağıdaki env ayarını nakşederek yaptım. İşte şöyle;

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "program": "${workspaceRoot}/src/app.js",
            "cwd": "${workspaceRoot}/src",
            "env" : {
                "NODE_ENV": "dev"
            }
        }
    ]
}


Sonra da bunu şöyle kullandım;

if ("dev" === process.env.NODE_ENV) {
    console.log("You are using development environments.");
    require('dotenv').config({
        path: 'settings_test.env'
    });
} else {
    console.log("You are using live environments.");
    console.log("[If you want to use dev environments. Please set environment variable 'export NODE_ENV=dev']")
    require('dotenv').config({
        path: process.env.BOT_ENV_FILE
    });
}


Son işimiz ise webhook'u ayarlayıp workplace'deki bot integration'a bu webhook adresini eklemek. Eğer isterseniz bu işlemi de Azure üzerinden, Resource Groups -> MonoBot -> Channels üzerinden channel ekleyerek yapabilirsiniz. Bunun için Channel seçimi yaptıktan sonra sizden istenen bilgileri doldurup bu bilgilerin altındaki webhook ve key bilgilerini workplace'deki yerine yapıştırmak.


Şuandan itibaren botumuz bize papağanlık yapabilir.

Referans aldığım linkler;

Bot info;
https://docs.microsoft.com/tr-tr/bot-framework/nodejs/bot-builder-nodejs-quickstart

https://github.com/Microsoft/BotBuilder

Kullanılan komponentlerin platforma göre ön izlemesini yapabileceğimiz yer;
https://docs.botframework.com/en-us/channel-inspector/channels/Facebook?f=Carousel&e=example1

Yalnız ne uzun oldu bu yazı yaaa :S :S

(Burada bir karikatür vardı ama telif yerim diye kaldırdım. Herkese dava açıyorlarmış karikatür paylaşılıyor diye. Karikatürün doğasına hiç uymuyor bence bu tavır :-/ Pişman mısın? Pişmanım gideyim mi?)



12 Kasım 2017 Pazar

macOS'da boşluklarla dock düzenleme - Organizing dock with blank icons


Ayrılın ayrılınn.

Terminal'e sırayla aşağıdaki kodları yazınca dock'a bir boşluk ekleniyor.;

defaults write com.apple.dock persistent-apps -array-add '{"tile-type"="spacer-tile";}'
killall Dock
Boşluklu Dock
gibi.

Şuracıkta dursun lazım olur ^^




22 Temmuz 2017 Cumartesi

.net Core MVC ile basit bir Workplace Bot deneyimleme (macos - VS Code)


Bot dünyasına şöyle bir giriş yapmak için şöyle basit bir .net core MVC projesi oluşturdum. :)

vs code terminal'den dotnet build ve dotnet run dedim ve projemin localhost:5000 'de koştuğunu gördüm.

Workplace üzerinde çalışmaya başlamak için öncelikle localimi tünelledim. Bu da ne demek?
Yerel makinanızı tünellek;  size bir subdomain vererek - yerelinizi internet üzerinde yayınlamanıza yardımcı olan bir eylemdir. Bu sayede yerel makinanızda çalışan bir uygulamayı uzakta bulunan müşterinize veya ekip arkadaşınıza çalışır halde göstermek için bir yerlere deploy etmenize gerek kalmaz.(https://www.serhatdundar.com/tag:ngrok)
Bu işlemi yapmak için araçlar var. Bunlardan en yaygını ngrok diye bir araç. Diğeri de https://localname.io/ diye şirin bir araç. localname.io'nun macos uygulaması da var ve subdomain vermenize izin veriyor. İkisi de iş görüyor.

Daha yaygın diye ngrok üzerinden gideyim.

1. Öncelikle download edelim;
https://ngrok.com/download
2. Şimdi login olup auth_token'ınımızı öğrenelim ve terminalimizden ngrok'un olduğu dizinde ngrok komutlarını çalıştırmaya başlayalım.
./ngrok authtoken authtokenım
3. Şimdi de 5000 portumu tünelliyorum çünkü uygulamam orada koşuyor.
./ngrok http 5000
Bana tünellediği adresi verdi;
https://fakirdomaini.ngrok.io
artık bu adres üzerinden workplace ile bağlantı kurmaya hazırız.

Şimdi workplace'de sol menü'den Integrations kısmına girip en alttaki "Create Custom Integration" butonuna tıklayalım.





Botumuza isim verip oluşturuyoruz ve id'si, token'ı vs oluşuyor. İzinleri kafadan attım hangileri lazımsa onlar seçilmeli..




Buraya kadar kolay geldik. Şimdi ise yazdığımız uygulama ile workplace'i tanıştırmamız lazım. Bunu bize webhook sağlayacak.

Webhooks sekmesine gelip uygulamamızın adresini veriyoruz. Yazdığımız adresin workplace'e success dönüyor olması gerek..

Bunun için WorkplaceController'ımda bir methodum var;

[HttpGet]
public string Get()
{
var query = Request.Query;

if (query["hub.mode"] == "subscribe" &&
query["hub.verify_token"] == "bot_verify_token")
{
var retVal = query["hub.challenge"];
return retVal.ToString();
}
else{
return Request.Query["hub.challange"];
}
}


dotnet build , dotnet run .. çalışıyor. O halde webhook'u ayarlayabilirim;


Kabul etti.. :)

Şimdi workplace'le iletişelim.. Diğer metodları yazalım, hatta direkt tüm controller'ı paylaşayım.. Bizim uygulamamız papağan misali sadece bizim ona yazdığımız şeyi bize dönüyor. Aşağı yukarı şöyle birşey;

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace botapp.Controllers
{
[Route("[controller]")]
public class WorkplaceController : Controller
{
[HttpGet]
public string Get()
{
var query = Request.Query;

if (query["hub.mode"] == "subscribe" &&
query["hub.verify_token"] == "weather_bot_verify_token")
{
var retVal = query["hub.challenge"];
return retVal.ToString();
}
else{
return Request.Query["hub.challange"];
}
}

[ActionName("Receive")]
[HttpPost]
public async Task<ActionResult> ReceivePost([FromBody]BotRequest data)
{
await Task.Factory.StartNew(async () =>
{
foreach (var entry in data.entry)
{
foreach (var message in entry.messaging)
{
if (string.IsNullOrWhiteSpace(message?.message?.text))
continue;

var msg = "You said: " + message.message.text;
var json = $@" {{recipient: {{ id: {message.sender.id}}},message: {{text: ""{msg}"" }}}}";
await PostRaw($@"https://graph.facebook.com/v2.6/me/messages?access_token=accesstokenim", json);
}
}
});

return new StatusCodeResult((int)System.Net.HttpStatusCode.OK);
}

private async Task<string> PostRaw(string url, string data)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = "application/json";
request.Method = "POST";

var requestStream = await request.GetRequestStreamAsync();
using (var requestWriter = new StreamWriter(requestStream))
{
requestWriter.Write(data);
}

var responseStream = await request.GetResponseAsync();

var response = (HttpWebResponse)responseStream;
if (response == null)
throw new InvalidOperationException("GetResponse returns null");

using (var sr = new StreamReader(response.GetResponseStream()))
{
return sr.ReadToEnd();
}
}
}
}


Burada bir adet BotRequest modeli görüyoruz.. Bunu da aşağıda kaynağını verdiğim arkadaş yazmış. Onun örneğinden kopya çektim bayağı.

https://tutorials.botsfloor.com/facebook-chatbot-in-asp-net-2f9379a238b0


BotRequest ;

using System.Collections.Generic;

namespace botapp
{

public class BotRequest
{
public string @object { get; set; }
public List<BotEntry> entry { get; set; }
}

public class BotEntry
{
public string id { get; set; }
public long time { get; set; }
public List<BotMessageReceivedRequest> messaging { get; set; }
}

public class BotMessageReceivedRequest
{
public BotUser sender { get; set; }
public BotUser recipient { get; set; }
public string timestamp { get; set; }
public BotMessage message { get; set; }
public BotPostback postback { get; set; }
}

public class BotPostback
{
public string payload { get; set; }
}

public class BotMessageResponse
{
public BotUser recipient { get; set; }
public MessageResponse message { get; set; }
}

public class BotMessage
{
public string mid { get; set; }
public List<MessageAttachment> attachments { get; set; }
public long seq { get; set; }
public string text { get; set; }
public QuickReply quick_reply { get; set; }
}

public class BotUser
{
public string id { get; set; }
}

public class MessageResponse
{
public MessageAttachment attachment { get; set; }
public List<QuickReply> quick_replies { get; set; }
public string text { get; set; }
}

public class QuickReply
{
public string content_type { get; set; }
public string title { get; set; }
public string payload { get; set; }
}

public class ResponseButtons
{
public string type { get; set; }
public string title { get; set; }
public string payload { get; set; }
public string url { get; set; }
public string webview_height_ratio { get; set; }
}

public class MessageAttachment
{
public string type { get; set; }
public MessageAttachmentPayLoad payload { get; set; }
}

public class MessageAttachmentPayLoad
{
public string url { get; set; }
public string template_type { get; set; }
public string top_element_style { get; set; }
public List<PayloadElements> elements { get; set; }
public List<ResponseButtons> buttons { get; set; }
public string recipient_name { get; set; }
public string order_number { get; set; }
public string currency { get; set; }
public string payment_method { get; set; }
public string order_url { get; set; }
public string timestamp { get; set; }
public Address address { get; set; }
public Summary summary { get; set; }
}

public class PayloadElements
{
public string title { get; set; }
public string image_url { get; set; }
public string subtitle { get; set; }
public List<ResponseButtons> buttons { get; set; }
public string item_url { get; set; }
public int? quantity { get; set; }
public decimal? price { get; set; }
public string currency { get; set; }
}

public class Address
{
internal string street_2;

public string street_1 { get; set; }
public string city { get; set; }
public string postal_code { get; set; }
public string country { get; set; }
public string state { get; set; }
}
public class Summary
{
public decimal? subtotal { get; set; }
public decimal? shipping_cost { get; set; }
public decimal? total_tax { get; set; }
public decimal total_cost { get; set; }
}

}

dotnet build
dotnet run

hadi yazışalım :)


Biraz daha yaratıcı hale getirmek artık kodlamaya kalmış olduu :) Birşeyler deneyeceğim kendi çapımda bakalım.. Gerçi,, başka ne olabilir ki? :PPP



20 Temmuz 2017 Perşembe

npm ERR! missing:


Merhaba Cinlerim..

.net core mvc ile ufak çaplı bir projeye başlamışken bootstrap'i jquery'yi fln npm'den eklemeye kalktım pratik olur diye. :) Yine mono laneti vuku bulup sorun çıktığı için çok da pratik olduğu söylenemez ama birşey daha tecrübe etmiş oldum.

Sıkıntı şöyle başladı, vs'den "Add New Item" deyip "npm Configuration File" seçtim. Sonra oluşan package.json dosyama aşağıdaki gibi paketleri ekledim, kaydettim.

"dependencies": {
"bootstrap": "3.3.7",
"jquery": "3.2.1",
"jquery-validation": "1.16.0",
"jquery-validation-unobtrusive": "3.2.6"
}

bu işlemi yaptıktan sonra "node_modules" diye bir klasör oluşup paketlerimin içine yüklenmesi gerekiyordu normalde ama eklenmedi. Paketlerin yanında yüklenmediğini belirten işaretler uyarılar vardı. Ardından Visual Studio üzerinden node.js Interactive Window'u açıp .npm komutlarını çalıştırmayı denedim.

Birkaç sitede çözüm olarak ".npm init -y" komutunun durumu düzelteceği yazıyordu. Olmadı, bende şöyle bir hata verdi;
Please specify a valid Node.js project or project directory. If your solution contains multiple projects, specify a target project using .npm [ProjectName or ProjectDir] <npm arguments> For example: .npm [MyApp] list

Dediğini yaptım..
.npm [ProjeminAdı] list
Yine bana aynı çıktıyı verdi; :(
+-- UNMET DEPENDENCY bootstrap@3.3.7
+-- UNMET DEPENDENCY jquery@3.2.1
+-- UNMET DEPENDENCY jquery-validation@1.16.0
`-- UNMET DEPENDENCY jquery-validation-unobtrusive@3.2.6
npm ERR! missing: bootstrap@3.3.7, required by asp.net@1.0.0
npm ERR! missing: jquery@3.2.1, required by asp.net@1.0.0
npm ERR! missing: jquery-validation@1.16.0, required by asp.net@1.0.0
npm ERR! missing: jquery-validation-unobtrusive@3.2.6, required by asp.net@1.0.0
[ProjeminAdı] list completed with errors
Sonra birkaç şey daha denedim, biraz daha araştırdım derken şu paketi buldum;

https://www.npmjs.com/package/npm-install-missing

Hadi bunu da deneyeyim bari;

 .npm install -g npm-install-missing

Bu paketi kurduktan sonra kullanmam lazım tabi, işte şöyle;


.npm [ProjeminAdı] npm-install-missing

Sonunda bir succesful mesajı görebildim :) Sonrasında tekrar şu komutu çalıştırdım;
.npm [ProjeminAdı] install
Oldu ve hasretle yolunu gözlediğim klasörüm geldi. Ve paketlerim yüklendi. Aferim, bugün de iyi eğlendik. :)
 
(Burada bir karikatür vardı ama telif yerim diye kaldırdım. Herkese dava açıyorlarmış karikatür paylaşılıyor diye. Karikatürün doğasına hiç uymuyor bence bu tavır :-/ Güzel eğlendim bugün)