TL;DR: Şimdi 45 dakikalık projeyi ballandıra ballandıra sanki çok da bişey yapmışım gibi anlatacağım. Kod felan da var.

İşin hikayesi

Her şey PoC (Proof of Concept) çalışması yaptığımız bir firmanın bize açmış olduğu tek kullanıcılı bir VPN (Virtual Private Network) hesabı ile başladı.

Tek VPN hesabı, proje ile ilgilenen 3-4 kişi ve 2FA (Two Factor Authentication).

tam-aydinlanicam-bi-gulme-geliyo-tovbe

Ne var ki şimdi bunda?

Şimdi bir VPN hesabını birden fazla kişinin paylaşması aslında çok da problem değil (yok yok, aslında problem, bilenler bilir, sıkça yaşanan bir şeydir 🤖) fakat 2FA kısmı için tanımlanmış olan cevap telefonuna gelen mesaj sıkıntı. Güvenliği artırmak için kullanılan, SMS'in gitmesi gereken o telefon sıkıntılı işte. Çünkü o benim şahsi telefonum. İçimizden birisi o VPN hesabını kullanmak istediğinde benim telefon numarama bir SMS geliyor ve o gelen doğrulama kodunu VPN yazılımına girmemiz gerekiyor. Yoksa içeri giremiyoruz. Bir de girmemiz gereken "süre" kısıtı var ki ona hiç girmiyorum.

Neyse...

Kısaca o telefona gelen SMS'in hızlı bir şekilde ilgili arkadaş grubuna iletilmesi gerekiyor ki girmek isteyen kişi o SMS kodunu hızlıca girebilsin.

Peki ne yapacağız?

Bu işi bir şekilde otomatize etmemiz gerekiyor. Aklın yolu bir. Ama nasıl?

Birden fazla yöntem var tabii ki. Olası birkaç yöntemi sıralayalım.

  1. Mobile Push Notification desteği bulunan bir yazılımı (ki bunlar ücretli) kullanalım ve gelen mesajı Push Notification ile gönderelim.
  2. Halihazırda kullandığımız mesajlaşma yazılımlarının bot servisini kullanarak mesajı bunlar üzerinden gönderelim.
  3. O mobile push şeysinin desteği olan bir mobil yazılım biz geliştirelim (ki akıl kârı değil şu durumda) back-end, front-end Allâh ne verdiyse bizim elimizden çıksın.

Şimdi bu aşamada sorgulama kısmı devreye giriyor.

  • İhtiyacımız ne?
  • Yapılmışı var mı? // Var sa kullan.
  • Ne kadar zaman harcamak istiyoruz? // Yok mu?
  • Peki ya mesajların handle edilip ilgili servise iletilmesi kısmı?
  • Biz mi geliştircez? Yapılmışı var mı?

Neyse sorular bol.

Soru-Cevap

Soru: İhtiyacımız?
Cevap: Cep telefonuna gelen mesajı belirli kriterlere göre yakalayıp hızlı bir şekilde başka bir cep telefonuna göndermek.

Soru: Cep telefonuna gelen mesajı başka servislere nasıl iletirim?
Cevap: IFTTT ve ya benzer bir servis kullanarak ilgili mesajı farklı kanallara aktarabilirim.

Soru: Aktarabileceğim kanallar arasında kullanabileceğim bir push servisi/IM var mı?
Cevap: Var, ücretli. Mesela IM (Instant Messaging: Whatsapp, Telegram, Facebook Messenger gibi) uygulamaları da var ama yalnızca kendine gönderebiliyorsun. Biz başkasına da göndermek istiyoruz. (Bunun aşmak için belki Zapier tarzı şeyler de kullanılabilir ama her seferinde konfigürasyon artıyor.)

Soru: Daha kolay bir şey mi olmalı yani?
Cevap: E yani!

Soru: Biz geliştircez yani zorluyosun?
Cevap: 😈

Hayal edelim

Bir url olsa ve biz o url'ye POST ile bir body göndersek ve back-end de onu alsa ve bir IM üzerinden url'nin sahibine gönderse.

Karmaşık mı oldu?

Hayali biraz daha açalım.

Senin bir web adresin var (örneğin; selcukermaya.com/mesaj) ve bu adres sadece sana özel. Sadece bunu bilenler, sana doğrudan bu adres üzerinden, basit bir http request ile doğrudan mesaj gönderebiliyor. Yani tıpkı telefon numarası gibi "05XX XXX XX XX" numarasına bir mesaj geldiğinde senin alabilmen gibi. Ve bu arada bu mesaj sana bir IM aracı üzerinden geliyor olsun.

Hmmm. Olabilir gibi.

Bunu basitleştirmemiz lazım ama. O kadar basit kullanımı olmalı ki, hem back-end'i kolay kodlansın, hem de front-end'i basit bir şekilde geliştirilebilip kullanılabilsin. Hatta front-end hiç olmasın. UI olarak başka bir şey kullanabilelim.

Nasıl yaparız?

Bu bahsettiğimiz şeyleri yapabilmek için;

Bir back-end'e, kullanıcının bu url'yi oluşturabilmesi için bir front-end'e, mesajları alabilmek için ise bir IM aracına ihtiyacımız var.

Birkaç ekstra bilgi edinelim. Mesela IM araçları üzerinden mesaj gönderip alabilmek, bir şeyleri programlayabilmek için o IM aracının "bot" geliştirmeye olanak vermesi gerekiyor.

Peki development stack (Geliştirme Araçları)'imiz ne olucak? Hızlı olmamız gerekiyor ya. Performans şu anda en son düşüneceğimiz şey.

Programlama dili için (aslında scripting dili de neyse) : Javascript / Nodejs (Express Framework)
IM için: Telegram (Bilenler bilir çok severim)
Bot Framework için: Telegraf - https://telegraf.js.org/ (Hızlı bir şekilde Telegram botları oluşturabiliyoruz)
Analitik veri ve data için : MonoSay - https://monosay.com

Peki kullanıcı arayüzü? Onunla alakalı bir şey demedim. 😊

Bunun için de yazmış olduğumuz "bot"'u kullanacağız. Yani bir kullanıcı kendisi için bir url oluşturmak istediğinde bot üzerinden "/new" yazması yeterli olacak. Böylelikle geliştirme süremizi de hızlandıracağız.

Ne kadar konuştum arkadaş. Hadi kodlamaya geçelim...

Kodlama

zihnini-bosalt-evladim

Botumuzu ve back-end'imizi aynı proje içerisinde kodlayacağız. Siz bunu ayırabilirsiniz. Fakat ben ayırmadan aynı repo üzerinde kodladım ve sonrasında bunları ayrı ayrı Dockerize ettim (Docker nedir? Dockerize etmek nedir gibi şeyler için şuraya alalım.) .

npm Adımları

# // node kullanacağımızı belirtmek ile başlayalım. 
# // Sonraki adımlarda projenizin ismini cismini isteyecek sizden.
npm init

# // Telegraf kütüphanesi
npm install telegraf

# // MonoSay kütüphanesi
npm install monosay

# // Web Framework
npm install express
# // JSON body'leri doğrudan javascript obje olarak alabilmemiz için var
npm install body-parser

Klasör ağacı

Ben tüm source kodlar, dökümantasyon vb şeyleri ayırmak için proje ağacımı oluşturur oluşturmaz "src" klasörünü oluşturarak başlıyorum. Aynı şekilde ilerleyeceğim.

Aşağıdaki klasör ağacını ve ihtiyacımız olan iki javascript dosyasını ekliyorum. Diğer dosyaların detaylarına girmeyeceğim. Belki kaynak kodları bir repoda yayınlarsam doğrudan orada bulabilirsiniz.

src/
├── bot.js
└── hook.js
bot.Dockerfile
hook.Dockerfile
.gitignore
.dockerignore
package.json

bot.js

İlk olarak botumuzu geliştireceğiz dedik. Bu arada ben bütün ayarları "Environment Variable" olarak alacağım. Haliyle biyere gömülü bir şekilde ayar, token bilgisi, şifre vs. felan göremeyeceksiniz. Aynı şekilde ilerlemeniz tavsiyemdir.

Ara bilgi;
Telegraf'ın kullanımı için: http://telegraf.js.org/
MonoSay'in kullanımı için (Telegraf ile birlikte): https://docs.monosay.com/docs/telegraf-nodejs-implementation.html

Telegram için bot oluşturma kısmını uzun uzun anlatmak istemiyorum. Kısa adımlar şu şekilde;

Telegram'ı kur
Yeni mesaj -> botfather'ı bul.
/newbot yaz ve gereken adımları uygula.
Sana bir token vericek. O token'ı burada kullanacağız. Biyere not al.

Tüm kodları açıklaya açıklaya yazıyorum.

Adım adım ilerleyelim;

// Telegrafı ve MonoSay'i kullanacağımızı belirtelim.
const Telegraf = require("telegraf");
// Burada usetelegraf metodumuza bir token geçmişiz.
// Bu token'ı platform.monosay.com adresine girerek 
// botunuza "channel" oluşturduktan sonra alıyorsunuz.
const monosay = require("monosay").usetelegraf("MONOSAY-TOKEN");

// Bot tanımımızı yapıyoruz.
const bot = new Telegraf("TELEGRAM-TOKEN");
// Sonrasında tanımını yaptığımız bot için monosay'i initialize ediyoruz.
// Yani bizim botumuz bu diyoruz monosay'e ki 
// o da analitik toplamaya ve bize datayı kullandırmaya başlasın.
monosay.init(bot);

Birisi bot'u kullanmaya başladığında ona bilgilendirici bir mesaj verelim.

// Öncelikle bir kaç yerde kullanabileceğimiz bir help mesajı tanımlayalım.
// Birisi ilk defa konuşmaya başlarsa ya da /help yardım isterse bu mesajı göndereceğiz.
const help = "Start typing `/new hook-name` and it will give you a webhook to send a message to directly to your Telegram via {mono}push.";

// Bir kişi Telegram'da botumuz ile konuşmaya başlarsa;
bot.start(ctx => {
    // Bu kısımda monosay'e diyoruz ki botu kullanan arkadaş bu kullanıcı
    // Bunun profil datasını bu bilgiler ile güncelle.
	monosay.user(
		{
			channelUserId: ctx.from.id,
			name: ctx.from.first_name,
			surname: ctx.from.last_name,
			userName: ctx.from.username
		},
		/*success callback*/ null,
		/*error callback*/ null
	);
    // Hoşgeldin kardeş diyoruz, ismiyle karşılıyoruz. Çok tatlıyız.
    // Ne yapabileceğini de söylemekten çekinmiyoruz.
	return ctx.reply(`Hi, and welcome ${ctx.from.first_name}! \n${help}`);
});

// Adam bizden /help şeklinde yardım isterse. 
// Yardım metnimizi gönderiyoruz.
bot.help(ctx => ctx.reply(help));

Düzenli gitmeyi ihmal etmeyelim. Kullanabileceğimiz birkaç değişken tanımımızı yapalım.

// Komut isimlerini tekrar tekrar kullanabilmek için
const commands = {
	SUBSCRIBE: "new"
};
// monosay üzerinden kullanacağımız data collection name'imizi 
// yine tekrar tekrar kullanabilmek için
// buraya daha sonra tekrar geleceğiz.
const collections = {
	SUBSCRIPTION: "subscription"
};

Şimdi de kullanalım.

// Telegram'da bir bot'a yazılan /komut ifadelerini yakalayabilmek için
// bot.command'ı kullanıyoruz. İlk parametremiz komutun ismi oluyor.
// İkinci parametremizde ise bize o mesajlaşmanın context'ini veriyor.
// Biz onu ctx içinde aldık.
// commands.SUBSCRIBE'ımızın "new"'e eşit olduğunu unutmayalım.
bot.command(commands.SUBSCRIBE, ctx => {
    // Kullanıcıların oluşturulan webhook url'lerine isim verebilmeleri için
    // /new hook-name yazmaları yeterli. Örneğin ben bir url oluşturacaksam
    // /new Benim Kanalım
    // yazıp geçiyorum.
    // Kullanıcının /new 'den sonra yazmış olduğu şeyi alıyoruz.
	var name = ctx.message.text.substring(commands.SUBSCRIBE.length + 1);
    // Eğer isim girmemişse kibar bir dil ile "ihtiyacımız var kardeşim" diyoruz.
	if (!name.length) {
        // Dikkat ettiyseniz ctx.reply bizim kullanıcıya mesaj gönderme metodumuz.
        // Bize mesaj yazan kullanıcıya doğrudan geri dönüyoruz.
		ctx.reply(`You have to send subscription name too. Like \`/${commands.SUBSCRIBE} name\``);
		return;
	}
    // Eğer her şey yolundaysa url oluşturma işlemimizi başlatıyoruz. 
    // Aslında bir veri kaynağına kayıt atıyoruz 😊
    // Sonradan bunu kullanacağız.
    // Burada subscribe isimli metodumuzu çağırdık. İlk parametre ctx'imizi verdik
    // İkincisi ise url'mizin ismi.
	subscribe(ctx, name);
    // Son olarak da işleme başladığımızı kullanıcıya söylüyoruz.
	return ctx.reply("Subscribing...");
});

// Bu da bizim subscribe metodumuz. Aslında bütün kayıt işlemi sadece burası.
// Hadi inceleyelim.

function subscribe(ctx, name) {
    // İlk olarak monosay'e kaydedeceğimiz datayı oluşturuyoruz.
    // userid (string) : Kullanıcının Telegram User Id'si
    // name (string) : url'ye verdiği isim - bu ismi sonradan mesaj gönderen olarak kullanacağız.
    // date (date) : oluşturduğu tarihi kaydediyoruz.
    // username (string) : son olarak kullanıcının adı'nı kaydediyoruz. 
	var data = {
		userid: `${ctx.from.id}`,
		name: name,
		date: new Date(),
		username: ctx.from.first_name
	};
    // Bu kısım ile alakalı detaylı bir açıklama yapmayacağım.
    // docs.monosay.com adresinde kullanım örneklerini bulabilirsiniz.
    // Doğrudan ilgili adres: https://docs.monosay.com/docs/telegraf-nodejs-data.html
    // collections.SUBSCRIPTION : "subscription"
    // monosay'de oluşturmuş olduğumuz data collection adını yazıyoruz buraya
    // yukarıda istemiş olduğumuz dataları da field olarak eklemeyi unutmuyoruz. Hepsinin tiplerini yanlarına yazdım.
    // monosay schema validation yaptığı için tipler önemli.
	monosay.data(collections.SUBSCRIPTION).save(
		data,
		result => {
			// İşlem başarılı ise monosay bize result.success'i true olarak dönüyor.
            // Eğer hatalı bir durum söz konusu ise bizi error kısmına düşürüyor ki onu aşağıda yazdık.
            if (result.success) {
                // Kullanıcıya göndereceğimiz mesajı oluşturuyoruz.
				var message = `Your webhook is ready for sending message. Use;`;
                // Burada process.env.HOOK_URL kısmına siz webhook projenizi 
                // (hook.js - biraz sonra yazacağız)
                // hangi adres üzerinden yayınladıysanız onu yazıyorsunuz.
                // Biz oluşturacağımız url'ye monosay'in bize oluşturduğumuz data için 
                // göndermiş olduğu unique id 'yi sonuna ekliyoruz.
                // Yani şöyle bir şey olmuş oluyor;
                // https://webhook.monopush.io/telegram/6b0db11a3d3b522151b241ca
                // Tahmin edeceğiniz üzere bizim yayınladığımız adres 
                // webhook.monopush.io uzantısı üzerinde.
				message += `\n${process.env.HOOK_URL}/${result.data.id}`;
				message += `\n\nBody (application/json);\n\n`;
				message += "```\n";
				message += JSON.stringify(
					{
						message: "Hi there!"
					},
					null,
					2
				);
				message += "\n```";
                // Kullanıcıya kullanım örneği ile birlikte mesajı gönderiyoruz.
                // replyWithMarkdown dememizin sebebi Telegram markdown mesaj göndermeyi destekliyor.
                // Haliyle markdown formating'i algılaması için bunu kullanıyoruz.
				ctx.replyWithMarkdown(message);
			} else {
                // Ne olur ne olmaz hata olursa consola yazdırsın.
				console.error(result);
			}
		},
		err => {
			console.error(err);
            // Ne olur ne olmaz hata olursa consola yazdırsın.
            // Adama da desin ki kardeş bir sıkıntı var.
			ctx.reply("Something is wrong with your request.");
		}
	);
}

Ve mutlu son botumuz kullanıcıdan mesajları dinlemeye başlasın. Bunun için;

bot.startPolling();

Son satıra bunu yazdıktan sonra botumuz hazır hale gelmiş oluyor. Test edebilmek için aşağıdaki ana klasördeyken aşağıdaki satırı çalıştırmanız yeterli;

node src/bot.js

Aşağıdaki şekilde bir mesaj alacaksınız ve botunuz çalışıyor olacak.

MonoSay Initialized for Telegraf.
MonoSay API Url is https://api.monosay.com/v1/

Şimdi webhook tarafına (kişiye özel url kısmımıza) geçelim.

hook.js

En kolay kısma geldik.

// Buralar biliyorsunuz artık.
const Telegraf = require("telegraf");
const monosay = require("monosay").usetelegraf(process.env.MONOSAY_TOKEN);

// Burada ekstra kullanacağımız web framework tanımını yapıyoruz.
const express = require("express");
const app = express();
// Gelen json body'i parse edip bize object olarak verecek.
const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// Buraları da biliyorsunuz 😊
const bot = new Telegraf(process.env.BOT_TOKEN);
monosay.init(bot);

const collections = {
	SUBSCRIPTION: "subscription"
};

Şimdi sıra geldi son kısma. Url'yi dinleyeceğimiz bölüme.

// Eğer birisi aşağıdaki tanıma uyan bir adrese
// POST isteği yaparsa
// Bu metod da onu işliyoruz.
// Burada kullanmış olduğumuz :id kısmı parametre olduğunu belirtmemiz için
// Haliyle biz /telegram/blabla yazdığımız yeri
// req.params.id olarak alabiliyoruz.
// Bu şablona uymayan her şeye 404 dönecek.
app.post("/telegram/:id", (req, res) => {
	var id = req.params.id;
    // Gerçekten id göndermiş mi? Ne olur ne olmaz kontrol edelim.
	if (!id) {
        // Eğer göndermediyse söyleyelim.
		return res.send("id is not found.");
	}

    // Evet en önemli kısım.
    // Ne demiştik. Adamın url'sinin sonuna
    // monosay'den almış olduğumuz data 'nın id sini yazmıştık aslında.
    // Şimdi bu aldığımız id'yi monosay de ki data collection'ımızda var mı
    // diye kontrol edelim. Yoksa hata gönderelim.
    // Varsa da işlemlerimizi yapalım.
    // monosay.data("collection").get("id") kısmı bize
    // bir id ye göre tekil data çekmemizi sağlıyor.
    // eğer data varsa result.success 'imiz true oluyor
    // ve data result.data içinde bize geliyor.
	monosay.data(collections.SUBSCRIPTION).get(
		id,
		result => {
			if (result.success) {
                // Şimdi burada bilmemiz gereken bir kısım daha var ki
                // Biz adamdan datayı
                // application/json içerik tipinde
                // { "message" : "Mesajın kendisi" }
                // Şeklinde bekliyoruz.
                // Ve göndermek istediği mesajı da req.body.message dan alıyoruz.
                // Eğer mesaj varsa kullanıcının id sine doğrudan o mesajı gönderiyoruz.
                // Farkındaysanız bunu;
                // bot.telegram.sendMessage ile yapıyoruz.
                // İlk parametre mesajın gönderileceği kullanıcının id si
                // İkinci parametre ise mesajın kendisi.
                // Eğer bir mesaj gönderilmezse kullanıcıyı yine bilgilendiriyoruz.
				if (!req.body || !req.body.message) {
					bot.telegram.sendMessage(result.data.userid, "You must send your body or message.");
				} else {
                    // Mesaj gelirse de ilgili mesajı
                    // Dikkat ederseniz url'sine verdiği isimi de başına ekleyerek
                    // gönderiyoruz 👏
                    // Bu kadar
					bot.telegram.sendMessage(result.data.userid, `${result.data.name}:\n${req.body.message}`);
				}
                // Success mesajımız (200)
				res.send();
			} else {
				res.send(result);
			}
		},
		err => res.send(err)
	);
});

// Son olarak uygulamamız http request lerini 3000 portundan dinlemeye başlıyor
// Portu kafanıza göre değiştirebilirsiniz.
app.listen(3000, () => {
	console.log(`App is running on port ${port}!`);
});

Çalıştırmak için;

node src/hook.js

Yeterli. Aşaığdakini gördüyseniz olay tamam demektir.

App is running on port 3000!

Yorum satırları olmasa toplasan 100-150 satır kod yok iki projede.

Şimdi gelelim Docker'ize kısmına. Buradaki kodları tek tek açılamayacağım. İşi uzmanına bıraktım zaten. Sadece komutları verip geçeceğim.

Şimdi buradan sonraki kısımlar dockerize etme ve dockerize edilmiş şeyi local docker'da çalıştırmak üzerine olan işler. Eğer ilgi ve alakanız yoksa kullanmak istemiyorsanız (ki şiddetle tavsiye edilir) atlayabilirsiniz. Zaten proje bitti 😊

hook.Dockerfile

FROM node:alpine
WORKDIR /src
EXPOSE 3978/tcp
ENTRYPOINT ["node", "src/hook.js"]
COPY . /src
RUN npm install

Nasıl build ederiz?

docker build -t monopush/telegram-webhook -f hook.Dockerfile .

Nasıl run ederiz?

docker run --name telegram-webhook -p 3000:3000 monopush/telegram-webhook

bot.Dockerfile

FROM node:alpine
WORKDIR /src
EXPOSE 3978/tcp
ENTRYPOINT ["node", "src/hook.js"]
COPY . /src
RUN npm install

Nasıl build ederiz?

docker build -t monopush/telegram-bot -f bot.Dockerfile .

Nasıl run ederiz?

docker run --name telegram-bot monopush/telegram-bot 

Docker işlemleri de bu kadar. Huh. Ne yazdık arkadaş.

IFTTT

Make-an-Applet-IFTTT-2018-05-30-02-28-31

Eveeet. Buraya kadar her şey güllük gülistanlık 😎 Ama asıl eğlence şimdi. Hemen testimizi yapalım. IFTTT'yi telefonuma kurdum. Sonrasında sitesine girdim. Yeni bir Applet oluşturdum.

If +this kısmına Android SMS'i seçtim, +that kısmına ise Webhook'u seçtim. Oluşturduğumuz Url'yi yazdım, Http Method olarak POST seçtim, Body olarak ise {"message":"{{Text}}"} yazdım. Burada {{Text}} yazan yeri otomatik olarak gelen SMS'in içeriği ile değiştiriyor IFTTT.

Bir mesaj göndererek test işlemimi yaptım ve gelen mesaj doğrudan Telegram Bot'u üzerinden bana geldi.

Görev tamam!

Bu arada illa da IFTTT yi kullanmak zorunda değilsiniz. Basit bir http çağrısı ile aşağıdaki body'i ekleyerek kendinize yazdığınız bot üzerinden (ve ya arkadaşınızdan aldığınız url üzerinden) mesaj gönderebilirsiniz.

Örnek sorgu;

curl -X POST \
  https://webhook.monopush.io/telegram/5b0de3d054fcef0001345edef \
  -H 'Content-Type: application/json' \
  -d '{
	"message" : "Hey Dostum! Başardık anlıyo musun?!"
}'

telegram-monopush-2018-05-30-02-41-56

Peki bu nasıl ürüne döndü?

Bu bizim için problemlerin yalnızca biriydi. Fakat biz bunu genel kullanıma uygun yaptık.

Ürünün adı {mono}push. Şu anda yaptığı işlem sadece bu olsa da ilerde kapsamı azıcık daha genişleyecek. Özellikleri artacak. Ayakları yere daha sağlam basan bir şey olacak.

Şu anda istediğiniz herhangi bir sürece tıpkı Slack channel'larınıza gönderdiğiniz mesajlarınız gibi Telegram kanallarına, Botlarına mesajlar göndererek (ya da kendinize) bu işlemi gerçekleştirebilirsiniz.

Doğrudan test işlemi yapmak istiyorsanız eğer;

telegram-monopushbot-2018-05-30-02-31-50

http://telegram.me/monopushbot adresinden kullanmaya başlayabilirsiniz. Bu servis herkesin kullanımına açık halde olacak. Sizlerin de kullanabileceği bir ürün yani 😊

/new Urlnizin adı komutu ile url'nizi alabilir, sonrasında süreçlerinize dahil edebilirsiniz.

Şu anda {mono}push sadece Telegram'a destek veriyor olsa da yakında Facebook Messenger, Skype ve benzeri bot platformlarına destek veren IM'ler üzerinden de kullanabiliyor halde olacaksınız.

felsefe-gercegin-pesinde-omur-boyu-suren-bir-yolculuktur

Şimdilik, Selametle...