Facebook’ta bir uygulamayı veya oyunu geliştirmek ve dağıtmak, arkadaşlar, fotoğraflar ve diğer bilgileri hakkında ayrıntılı bilgi almak için Facebook API ile çalışmak gerekir. Facebook Graph API üzerinden bu görevleri gerçekleştirmek için kullanabileceğiniz birkaç resmi SDK bulunmaktadır. Bu yazımda actionscript 3.0 ile javascript SDK kullanarak detaylara değineceğim.

Başlarken öncelikle bu üç Facebook JavaScript API çağırmamız gerekli.

  • FB.init – Facebook API’yi init etmek
  • FB.login – Login with facebook (Facebook ile giriş yapmak)
  • FB.api – Facebook Graph API’yi çağırmak

ActionScript ile “flash.external.ExternalInterface” kullanarak  JavaScript fonksiyonlarını çağırabiliriz. Yani flash içerisinden bu komut ile istediğiniz javascrript API’ye erişebilir ve kullanabilirsiniz. Bizde Facebook Graph API için javascript SDK kullanırken dışarıdaki javascript komutlara bu şekilde erişebileceğiz.

facebookAPI

 

Test etmek için aşağıdaki adımları tamamlamanız gerekmektedir.

  • Flash ve JavaScript arasındaki iletişimi kurmak
  • Flash uygulaması için sunucu kurmak
  • Yeni bir Facebook uygulaması oluşturmak
  • Facebook JavaScript SDK
  • Bazı veriler için facebook ile giriş yapmak

1- Flash ve JavaScript arasındaki iletişimi kurmak

Flash runtime da Facebook JavaScript SDK  kullanmak  için, öncelikle ExternalInterface kullanarak iki katman arasındaki iletişimi kurmak gerekir.

Ana ActionScript 3 sınıfında  bu üç işlevi ekleyin ve init() fonksiyonunu çağırın.

private function init():void{
ExternalInterface.addCallback("myFlashcall",myFlashcall);
stage.addEventListener(MouseEvent.CLICK, onClick);
}
private function myFlashcall(str:String):void{
trace("myFlashcall: "+str);
}
protected function onClick(event:MouseEvent):void{
if(ExternalInterface.available){

trace("onClick");
ExternalInterface.call("myFBcall");
}
}

2- (Flash Builder kullanıyorsanız, index.template.html oluşturduktan sonra )  HTML dökümanıda  bulunan <head> tagi içine aşağıdaki kodları ekleyin.

  function myFBcall(){
      alert("test");
      document.getElementById("NAME_OF_YOUR_FLASH_ELEMENT").myFlashcall(“test string”);
   }
</script>

Ana sınıfı gerçek bir proje adıyla değiştirmek isterseniz  NAME_OF_YOUR_FLASH_ELEMENT isminin yerine değiştirebilirsiniz. Ben “FacebookDemo” ismini kullanacağım.

3- Uygulamayı çalıştırın. Flash içeriğinin içinde sahnede bir yere tıklarsanız, bir JavaScript çağrılır ve bir alert ekranda gözükecektir.

 

2- Flash uygulaması için geliştirme sunucusu kurmak

Online uygulama geliştirmek veya localhost sunucusu kurabilirsiniz. Ben geliştirme ortamı için localhost kullanmayı tercih ettim. Mac OS X üzerinde siz de MAMP sunucusu kullanabilirsiniz, veya Windows üzerinde WAMP sunucusu kullanabilirsiniz.

MAMP sunucu portum 8888 /Applications/MAMP/htdocs.

  1. Htdocs klasörü içiner fbdemo klasörü ekleyin.
  2. Gelişim sürecini kolaylaştırmak için, Flash Builder htdocs / fbdemo klasör ve çalışma (debug) sunucu üzerinde uygulamayı derlemek için yapılandırma ayarlamalarını düzenleyin.

Screenshot 2014-11-23 13.01.04

 

3- Sunucunuzda çalıştığından emin olun

3- Yeni bir Facebook App oluşturun

Bir sonraki adım, bir Facebook uygulaması oluşturmaktır.

  1. https://developers.facebook.com/apps/ gidin ve Yeni App Oluştur’u tıklatın. (Create New App)
  2. Uygulama (App) Domain için, localhost yazın.
  3. Site URL kısmına http://localhost:8888/fbdemo yazın
  4. Bir sonraki adımda unutmamanız gereken API Key ve API

Screenshot 2014-11-23 13.19.20

 

 

Facebook JavaScript SDK ile ilgili detaylar için,
https://developers.facebook.com/docs/reference/javascript/ adresinden bakabilirsiniz.

SDK başlatmak için, HTML <head> tagi içinde aşağıdaki kodu dahil edin  ve önceki adımda olan App ID ile YOUR_APP_ID bilgilerini değiştirin. (Flash Builder kullanıyorsanız, bu kod index.template.html ekleyin.)

window.fbAsyncInit = function() {

FB.init({
appId : 'YOUR_APP_ID',
channelUrl : 'www.YOUR_DOMAIN.COM/channel.html',
status : true,
cookie : true,
xfbml : true
});
};
(function(d, debug){

var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all" + (debug ? "/debug" : "") + ".js";
ref.parentNode.insertBefore(js, ref);
}(document, /*debug*/ false));
</script>

2- Sonraki adım ise <body> tagi içine fb-root divi ekleyin.

<div id="fb-root"></div>

5. Facebook ile giriş ve bazı veriler almak

Artık Facebook ile giriş yaparak ve API’yi kullanarak verileri alabilirsiniz.

MyFBcall fonksiyonunu düzenleyin ve aşağıdaki kod ile içeriğini değiştirin. Bu kod, facebook ile giriş yaparken size adınızı getirir ve bir alert görürsünüz ve daha sonra Flash runtime’a geri göndereceğiz.

function myFBcall(){
     FB.login(function(response) {
      if (response.authResponse) {
       console.log('Bilgilerinize hoşgeldiniz.... ');
       FB.api('/me', function(response) {
       alert('Your name is ' + response.name);
        document.getElementById("FacebookDemo").myFlashcall(response.name); }); 
     } else { 
      console.log('Kullanıcı girişi iptal.');
    }
     });
       }

Artık Flash facebook arasında temel bir bağlantı kurdunuz.

Yukarıdaki kod FB.login fonksiyonunu çağırır ve gerçek authResponse durumunu denetleyen bir anonim fonksiyon geri çağırır (callback). Daha sonra Facebook Graph API çağrıları ise FB.api fonksiyonunu çağırır. Geçerli kullanıcı hakkında bilgi almak için, sadece bir ‘/me’ parametresi yeterli olacaktır.

Eğer giriş yaptıktan sonra, sadece FB.login gerek kalmadan tek başına FB.api (“command”) çağırabilirsiniz; Örneğin:

function getProfilePicture(){
FB.api("/me/picture", function(response) {
// DO SOMETHING
});
}

Şu anda giriş yaptığınızdan emin olduğunuzu doğrulamak için, FB.getLoginStatus çağırabilirsiniz.

Daha fazla bilgi için Facebook geliştirici sitesinden yardım alabilirsiniz.

Bu Blog postumla beraber bir sonraki blog postum ActionScript ile trigonometri örnekleri anlatmayı düşünüyorum.

Öncelikle Trigonometri nedir, Açı nedir, Radyan ve Derece nedir gibi genel bir giriş yaptıktan sonra uygulama örneklerine geçmeyi düşünüyorum.

Trigonometri

Adından da anlaşılacağı gibi temel olarak üçgen ve üçgenlerin kenarları ile olan ilişkilerini inceleyen bir matematik dalıdır. Bildiğiniz gibi üçgenlerin üç kenarı ve üç açısı bulunmaktadır.

Açılar

Trigonometrinin temeli açılardır diyebiliriz. Açı iki düz çizginin birleşmesi sonucu çizgiler arasında meydana gelen boşluktur. Aşağıdaki şekillerde de görüldüğü gibi iki çizginin birleşmesi ile çizgiler arasında 4 tane farklı açı oluşur. Trigonometride genellikle açıya rotasyon gözüyle de bakılır.

Açı; başlangıç noktaları aynı olan iki ışının aralarında oluşan boşluktur. Bu boşluk büyüdükçe açının derecesi de büyüyecektir.

IMG_6453

Radyan ve Derece 

Radyan ve Dereceden önce dairenin ne olduğunu bilmemiz gereklidir. Daire için “düzlem üzerinde merkez noktadan çevresine eşit mesafede olan belirli bir takım noktalardır” diyebiliriz. Bizim öğrendiğimiz ve bildiğimiz şekli ise basit bir çemberdir. Radyan ve Dereceler ise bu daire üzerindeki açıları ölçmemiz için kullanılan araçlardır. Bir daire 360 derecedir ve bu 360 derecenin her bir parçası 1 dereceye eşittir. Radyan ise 57.2958 derecedir. Biraz daha genişletirsek bir daire yani 360 derece 6.2832 radyandır. PI sayısı ise değer olarak 3.1416’dır. Bu değer yarım daireye eşittir, yani 180 derece. 2PI ise tam bir daireye eşittir 360 derece yani 6.2832 radyandır. Aslında bu değerlerin tamamını bilmemize gerek yok ama ne olduklarını ne anlama geldiklerini bilirsek yazdığımız kodlarıda rahatlıkla yazabilir, çözebilir ve ne yazdığımızı rahatlıkla anlayabiliriz.

IMG_6454

Açılardaki son değineceğim konu ise ActionScript’in açı anlayışı. ActionScript açı ölçümlerinde dereceler ile çalışmaz; dereceler yerine radyanlar ile çalışır. Bu yüzden değerlerin derece değil de radyan olarak verilmesi gerekir. Peki radyan ve derecelerin birbirine nasıl dönüştürüldüğünü öğrenmemiz gerek. Dönüşümler için PI sayısını kullanmamız gerek.

radyan = derece * Math.PI / 180;

derece = radyan  * 180 / Math.PI;

Şimdi ilk olarak 100 dereceyi radyana dönüştürelim. Daha sonra da çıkan sonucu tekrar dereceye dönüştürelim

trace(100 * Math.PI / 180);    // Sonuç  1.7453292519943295

trace(1.7453292519943295 * 180 / Math.PI);  // Sonuç 100

Son konularımız ise Üçgen , Sinüs ve Kosinüs bu konular hakkında da özet bilgiler verdikten sonra örnekler yapmaya başlayacağız.

Üçgen

Üçgen geometride kullanılan temel şekillerden biridir. Bizim kullanacağımız genellikle dik üçgen olacakatır. Dik üçgende açı ölçüsü 90 derece olan köşenin karşısındaki kenar hipotenüstür. Hipotenüs her zaman diğer iki kenarda daha uzundur.

Dairesel Hareket Sinüs ve Kosinüs

Bir dik açının sinüsü karşı kenarın hipotenüse oranıdır. Kosinüs ise komşu kenarın hipotenüse oranıdır. Formüle etmek gerekirse

IMG_6455 2

Ufak bir girişten sonra uygulamalarla daha iyi anlaşılacağını düşünüyorum.

Socket bağlantıları belirlenen bir port numarası üzerinden sunucuya bilgi yollamak ve almak için kullanılır. Tabi bu sistem normal dosya gönderme ve alma sistemleri gibi çalışmaz. Aradaki en büyük normal bağlantıları istenilen bilgiyi aldıktan sonra bağlantıyı kapatırlar. Socket bağlantılarında ise veri alışverişinden sonra bağlantı kopartılmaz. Bu bağlantılar ya sunucu tarafındaki iletişimin kesilmesi ya da Flash Player tarafından bağlantının kesilmesi harici sürekli olarak açık kalır.

Socket bağlantılarının genel kullanımları ise online oyun sistemlerinde, chat programlama, real-time mesajlaşma , çoklu kullanıcı veri paylaşımına kadar birçok alanı kapsar.

ActionScript içerisinde iki çeşit socket bağlantısı vardır, XML Socket ve Binary Socket fakat ben bu yazıda XML Socket ile bağlantı kurup detaylarını anlatacağım.

Öncelikle XML Socket Nedir ve Nasıl Bağlantı Kurulur?

XML Socket belirlenen bir sunucuya bağlanarak sunucu ile alış verişinde bulunur. Bu veri alışverişini XML dökümanını kullanarak yapar. Kullanıcı herhangi bir şekilde veri isteğinde bulunmadığı  halde bile sunucu tarafında herhangi bir değişiklik olduğunda sunucu veriyi otomatik olarak yollar. Örneğin en basit anlık mesajlaşmada 2 kullanıcıdan biri mesajın gelmesi için hiç bir hamlede bulunmasına gerek yok. Sunucu bunu otomatik olarak açık olan bağlantı ile kullanıcıya mesajı yollar. XML Socket ile sunucuya bağlanmamız için ilk olarak sunucu adresine ve bağlanılacak port numarasına ihtiyacımız var. Sunucu adresi (domain adı.com) ya da ip adresi olabilir (127.0.0.1). Port numarası ise sunucu üzerinden bağlanacak olan port numarasıdır. Bağlanılacak olan port numarası güvenlik ve sınırlamalar nedeni ile 1024’den aşağı olmamalıdır. Güvenlik konularına şimdilik girmek istemiyorum :)

XMLSocket objesi çok karmaşık olmayıp ve bir kaç metot ve özelliği vardır. Aşağıda bulunan XMLSocket kod örneklerini inceleyebilir ve bilgisayarınızda ip yerine “localhost” yazarak test edebilirsiniz. Socket tarafını Nodejs ile yazmayı tercih ettim tabiki büyük projelerde Java gibi güçlü diller ile yazabilirsiniz.

Öncelikle Flash programını açarak SocketExample.fla olarak kayıt ettikten sonra

Eklemeniz gereken butonlar ve yazı alanları var.

Components panelinden ekleyerek

textarea – output_txt (instance name)
textinput – input_txt (instance name)
buton (send) – send_btn (instance name)
buton (disconnect) – disconnect_btn (instance name)

a

 

 

 

 

 

 

 

 

 

 


//XMLSocket objesi var xmlSocket: XMLSocket = new XMLSocket(); // Sunucu adı ve port numarası (ip adresi yerine localhost yazarak localinizde test edebilirsiniz) xmlSocket.connect(“localhost”, 7000); // Gelen veriyi almak için bir event tanımlıyoruz tetiklenmesi için fonksiyon ekliyoruz. xmlSocket.addEventListener(DataEvent.DATA, onIncomingData); //Veri yollamak için buton eventi ve fonksiyonu send_btn.addEventListener(MouseEvent.CLICK, clickHandler); //Bağlantıyı kopartmak için disconnect_btn.addEventListener(MouseEvent.CLICK, disconnectHandler); function clickHandler(event: MouseEvent): void { xmlSocket.send(input_txt.text); input_txt.text = “”; } function disconnectHandler(event: MouseEvent): void { xmlSocket.close(); send_btn.enabled = false; } function onIncomingData(event: DataEvent): void { trace(“[” + event.type + “] ” + event.data); output_txt.text += event.data + “\n”; output_txt.verticalScrollPosition = output_txt.maxVerticalScrollPosition; }

Şimdi ise NodeJs ile Socket için gereken kodlar.

Nodejs ile web server, modüller gibi temel bilgileri�
http://bit.ly/1s1ABWB ve
http://bit.ly/1shdftA adreslerinden inceleyebilirsiniz.


Dökümanınızın ismi tcp_server.js olarak kaydedin

var net = require(“net”), //net modülü entegre ediyoruz sys = require(‘sys’); //sys modülü

var server = net.createServer(function(stream) { stream.setEncoding(“utf8″); stream.on(“connect”, function() { stream.write(“hello\0″); sys.puts(sys.inspect(stream, false)); }); stream.on(“data”, function(data) { stream.write(data + “\0″); }); stream.on(“end”, function() { stream.end(); }); }); server.listen(7000, “localhost”);

Her şey hazır oldukran sonra terminalinize gidip node tcp_server.js diyerek socketi ayağa kaldırdıktan sonra flash’da cmd+enter diyerek swf’den text alanına yazı yazarak send butonuna basın ve Output panelinden gelen datayı görebilirsiniz.

gamuaStarling, Flash player 11 ile gelmiş olup oyun geliştirmek için tasarlanmıştır zengin partikül efektleri yapabileceğiniz ve Stage3D API’si üstüne geliştirilen ActionScript 3.0 2D Framework ve oyun motorudur. AIR ve Desktop içinde kullanılabilir. Görsel çizimini flash display üstlendiğinde 20fps’ye kadar düşen performans, Starling ile 60fps’ye yükselmektedir. Bunun en büyük nedeni hardware ve software render farkıdır.

Performanslı olması açısında CPU yerine GPU kullanmaktadır yeni nesil bilgisayarların GPU ( Graphichs Processing Unit ) performansı oldukça güçlüdür.

Starling 2D oyun geliştirmek, özellikle de ActionScript 3.0 geliştiricileri için tasarlanmıştır. Starling, hafif, esnek ve kullanımı kolay olduğundan, aynı zamanda UI programlama gibi diğer proje ihtiyaçları için de kullanılabilir. Temel ActionScript 3.0 bilgisi gerekmektedir. Starling mümkün olduğunca kolay olacak şekilde tasarlanmıştır, böylece herhangi bir Java veya .Net geliştiricisi hemen kullanmaya başlayabilir.

Starling bir çok yönden hafif bir framework olduğunu söylemiştim. Sınıfların boyutu (kod 80 KB) sınırlıdır.
Starling ücretsiz ve açık kayanaktır. BSD lisanslı, bu yüzden ticari uygulamalarda özgürce kullanabilirsiniz. Üzerinde her gün çalışılmakta ve geliştirilmeye devam edilmektedir.

Perde arkasında, Starling kullandığı Stage3D API’leri-mobil cihazlarda masaüstü ve OpenGL ES2 üzerinede OpenGL ve DirectX üstünde çalışan düşük seviyeli GPU API’leri vardır.

 

Yani Starling ActionScript 3.0 ile geliştirlen Flash Player ve AIR tabanlı 2D oyun geliştirme framework’üdür.

Teknik olarak
// Texture objesi oluşturuyoruz.
var texture: Texture = Texture.fromBitmap (new embeddedBitmap ());

// Görüntü nesnesi oluşturuyoruz.
var image: Image = new Image (texture);

// Özelliklerini ayarlıyoruz
image.pivotX = 50;
image.pivotY = 50;
image.x = 300;
image.y = 150;
image.rotation = Math.PI / 4;

// Ekrana addChild ile alıyoruz
addChild (image);

AS3 API

  • flash.events.EventDispatcher
  • flash.events.Events
  • flash.display.DisplayObject
  • flash.siplay.Sprite
  • flash.display.Stage

 Starling API

  • starling.events.EventDispatcher
  • starling.events.Event
  • starling.display.DisplayObject
  • starling.display.Sprite
  • starling.display.Stage
  • starling.events.Event.ADDED_TO_STAGE
  • starling.eventsEvent.ENTER_FRAME
  • starling.events.Event.RESIZE
  • starling.events.Event.REMOVE

Browser, Desktop, Android, İOS desteğinin yanında Stage3d altyapısına sahip 3d kütüphaneler ile ortak kullanım şansı da tanıyor .http://gamua.com/starling/ sitesine bakıldığında 200den fazla oyun görülmekte. Andorid ve İOS platformu üzerine yazılan oyunlara bakıldığında İOS bir hayli önde.

Kısa giriş niteliğinde olan bu yazımı önümüzdeki günlerde örneklendireceğim.

– http://gamua.com/starling/

 

 

2-6-2014 tarihinde Apple kendi programlama dili olan Swift’i duyurdu. ” This package contains the latest Stable release of Swift. Since Swift is written in Java, this package will run on all supported platforms with Java Runtime Environment 1.6 or greater.” —  Yani Apache ile dağıtılıyor, JRE üzerinde çalışıyor. Belki ciddi ciddi yayılabilir düşüncesiyle şimdilik önemli bir programlama dili olduğunu düşünüyorum. –

JavaScript geliştiricileri swift programlama dilinin Syntax’ının JS’ye birazda CoffeeScripte benzediğini hemen anlayacaklardır. Ayrıca Apple Swift’in Objective-C’den daha performansli oldugunu belirtti. Bu madde daha çok oyun programcılarının işine yarayacağı anlaşılabilir.

Benide heyecanlandıran Swift ile ilgili önümüzdeki günlerde blog yazıları ve videolar paylaşmayı düşünüyorum. Aşağıda da ilgili linkleri inceleyebilirsiniz.

 

Günümüzün en popüler sürüm kontrol sistemi olan Git ve Github ile proje yönetimi artık çok kolay. Git ve Github kullanarak geliştirdiğiniz bir projeye farklı konumlardan farklı kişiler ekleyebilir, onlara görevler atayabilir ve yaptıkları işleri kontrol edebilirsiniz.

Takım çalışması ve bireysel çalışmalarda etkin bir şekilde kullanılan yeni proje oluşturmak, projeye kişiler eklemek, kişilere görevler atamak, kişilerin analizlerini tutmak, ana projeyi değiştirmeden yeni kodları test etmek için dallar (Branch) oluşturmak, Versiyon kontrollerini yapmak için release işlemlerini anlamak ve bir versiyon çıktığımızda Örneğin 1.0.0 ve çok fazla versiyon çıkıyorsak bir süre sonra istediğimiz bir versiyona dönüp ordaki dosyada ve kodlarda değişiklikler yapabilir veya istediğiniz kod güncellemelerini yapabiliriz.
Tüm büyük projelerde kullanılan Github ile sizlerde projeleriniz açık kaynak veya kapalı kaynak olarak ekibinizin ve projenizin yönetimini çok rahatlıkla yapabilirsiniz. Sisteminize

Git indirerek dilerseniz masaüstü uygulamasından veya bilgisayarınızın konsolundan projenizi yönetebilir kimlerin ne kadar kodlama yaptığını, ne kadar süre çalıştığını, ne kadar commit mesajı yazdığını gibi tüm detayları

yönetebileceğiniz eşsiz bir web platformu. Github içinde bulunan açık kaynak çalışılan projelere (Pull request) yaparak projeye katılabilirsiniz. Peki katkıda bulunacağım projeler ve kodlar nerde derseniz eğer Github’ın arama kutucuğuna ilgilendiğiniz yazılım dilini bile aramanız yeterli ve sonrasında projenin kodlarına, dökümantasyonuna, açıklama satırlarına veya o projede eksik gördüğünüz bir yerde kendinize bir ISSUE yani görev atayarak ekleme yapabilir ve yardım ederek hem kendinizi geliştirebilir hem de bir açık kaynak projede yer almış olursunuz. Github kullanarak kendi kodlarınızında ne kadar kaliteli olup olmadığını ordaki eleştiriler ve tartışmalar doğrultusunda anlayabilir ve geliştirebilirsiniz. Kodlarınızı eleştirmelerine izin verin. Kodlarınız için yapilan yorumlar, yazılımı dünyasında ilerlemeniz ve tecrübe kazanmanız içindir. Ama göze alacağınız bir gerçek var oda kodlarınızı acımasızca eleştirirler :) En basit bir olay yazılım dilllerinde bulunan syntax’lar için sayfalarca tartışma grupları oluşturulur ve üstüne yorumlar yapılır sadece o yazıları okumak bile sizi geliştirebilir. Yazdığım kodları herkes görecek diye çekinmenize gerek yok. Yaptığınız en ufak dökümantasyon çalışması, en ufak kod parçasi, dökümantasyon çevirileri bile GitHub hesabınızda bulunsun ve buna hemen şimdi başlayın. Nasıl mı? Günde sadece yarım saatinizi ayırarak yukarıda anlattığım maddeleri yaparak başlayabilirsiniz. En güzel özelliklerden biri ise Git takvimi, oraya bir hatırlatma koyarak şu projeye kod ekle, test yaz, issue aç, bug bul, dökümantasyon düzenle, yararlı hissetiğiniz ve eksikliğini gördüğünüz dökümanları çevirmek de olabilir. Bu şekilde başlayarak güzel bir başlangıç yapabilirsiniz ve bir süre sonra projelerede isminiz geçmeye başlar ve bu sizin için CV oluşturmanızıda etkiler, İşte benim kodlarım burada diyebilirsiniz. Bu böyle uzar gider son olarak naçizane önerim ise teknik bir konuda düzenli olarak ve kaliteli bilgileri ekleyerek blog tutmanız. Hala yoksa hemen şimdi ya kendi blogunuzu yazın veya bir blog alıp yazmaya başlayın :) Hala Github ne işe yarar dostum diyenlere kısacası diyorum ki Github Yazılım geliştiricilerin, yazılım dünyasının Facebook’u Linkedin’i Twitter’ı  diyebilirim ve orda olman gerek :) Şimdi!

https://github.com/