用於 Pump.io 的 facebook sync套件 -> Pump.io facebook sync plugin
大家好我是Davis,這次要介紹一下 Pump.io 以及今年暑假實習幫 Pump.io 做了什麼事情。 Pump.io 是一個基於 node.js ,且開放原始碼的「分散式社群網路」。
「分散式社群網路」這概念不同於目前市面上常看到的社群網路諸如FB, twitter 等等,在以往的社群網路中,都是有一個集中的伺服器,所有的人都在該伺服器上面註冊帳號等等。而「分散式社群網路」是一種比較新穎的概念,使用者可以自己建立實體 (instance) ,每個實體都可以有複數的使用者,在發文、follow等概念上與其他社群網路類似,但是在實體與實體之間,則依然可以讓使用者作互相follow的動作,卻沒有註冊該實體。
舉例來說,A實體上有一個「使用者甲」,而實體B上面有「使用者子」,「使用者甲」可以在獲得「使用者子」的ID連結之後,到該頁面對「使用者子」進 follow 的動作,實體B只會要求「使用者甲」進行認證的動作,取得其 token,這邊的 token 是 OAuth 標準下的一個令牌的概念,在不需要經過帳號密碼的情況下,授權一個網站在一段時間內可以使用該用戶的資源,在這邊就是將 follow 的人的文章 po 到自己的牆上,詳細的 OAuth 的介紹可以在 wiki 上找到。
因此,在不同實體底下的用戶可以透過 token,將訊息貼到對方的牆上,而不需要讓「使用者甲」在實體B上面重新註冊一個帳號。
一言以蔽之,用一張圖作為解釋,左半邊為正常的facebook結構,而右邊為pump.io這類的分散式社群網路為結構
圖1:
圖片來源:diasproa
facebook bridge
pump.io 在 github 有許多的 issues,而其中幾個 issues 是作者希望可以提供與其他社群網路同步的服務。
同步的好處多多,原因是目前pump.io是沒有搜尋的功能,也就是說,即使在一個實體中註冊了非常多的帳號,仍然沒有辦法搜尋到別的帳號作追蹤,更不用說在不同的實體。反正,沒有別人的ID,就沒有辦法作追蹤的服務。
因此透過同步的方式,除了可以通知目前的狀態,更可以將自己帳號的連結透過各式各樣的社群網路發佈出去,因此讓別人可以輕易找尋到你,也可以加入pump.io的家庭。
這次製作的是 facebook bridge,可以在pump.io 上發文、打卡,並且同步到 facebook (issues 連結)。
目前同步服務中較有多人在使用的是噗浪 (plurk) 的同步服務,噗浪提供的同步服務可以透過連結的方式將社群網路的帳號作連線,並在噗浪發的文章會同時發佈到各個地方,介面上如圖2:
在發文的部份,參考的是 facebook 網頁的打卡服務,如圖3:
pump.io 結構分析
pump.io 的結構,前端是由 backbone.js 所構成,而後端是 Node.js + express + mongoDB。
backbone.js 是一個類似 MVC 的前端 framework,也可以說根本沒有 C (controller),透過 model 向後端取得資料之後,顯示到V (view) 上。總之,詳細的使用方式可以到backbone.js 官網,其中也有提供不少的範例,這邊就只討論 pump.io 的發文結構,看圖4:
發文 function 會將文章打包成 activity 的物件,pump.io 同時也是一個 JSON activity stream 的 server,所有的訊息都是透過這樣的格式在做傳輸。因此文章也是一個 activity,裡面除了發佈的內容之外,也取用資料庫的api以及文章連結等資訊。
經過一連串的callback,最終得到的 activity 會被 ajax 放到 pump.io 的牆上面,這邊的概念可以想像成是 facebook 的塗鴉牆。在貼到塗鴉牆上的 activity 將會是資訊最完整的部份,所以我決定在這邊將資料同步給 facebook。
activity 的結構分析
activity 的結構如圖5:
我們可以從中取出 content 以及 url ,此兩項分別儲存該篇發文的網址以及內文。利用這兩個值,我們可以把 po 到 FB 的文章中放入除了內文之外,更外嵌需要的 url,可以讓其他人透過 FB 連到 pump.io。
facebook javascript 使用設定
facebook 提供了相當多的SDK套件,其中我們使用 javascript api。 javascript 的 api 會先執行 FB.init 的動作,這是為了載入資料庫,以及填寫需要的 facebook app ID 。
此動作會在 window.fbAsyncInit 的時候執行,這可能會在頁面出來前或出來之後被執行到,而通常這時候 UI 其實已經建立完成,因此,稍後會使用 jQuery 的 trigger 的方式做介面上面的更新。 如程式碼1:
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
// init the FB JS SDK
FB.init({
appId : 'YOUR_APP_ID', // App ID from the app dashboard
channelUrl : '//WWW.YOUR_DOMAIN.COM/channel.html', // Channel file for x-domain comms
status : true, // Check Facebook Login status
xfbml : true // Look for social plugins on the page
});
// Additional initialization code such as adding Event Listeners goes here
};
// Load the SDK asynchronously
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
facebook graph api 使用方法
graph api 在使用上必須要先Login,如程式碼2:
FB.login(function (response) {
FB.getLoginStatus(function (response) {
if (response.status === 'connected') {
//Do something when you have allready log in, like update UI
callback(true);
} else {
//User cancelled login or did not fully authorize. You should show on
the UI
}
});
}, {
scope: 'email, user_likes, offline_access, publish_stream, read_stream'
});
而特別注意到在scope的部份,是詢問取用的權限,也就是說,必須要有這些權限,才可以做po文,讀取文章甚至是接下來要取得打卡地點。
FB login, po文設計
-
Login
-
FB.Event.subscribe()
在window.fbAsyncInit 的 function,確認目前登入的情況,若為登入且認證時,則做更新頁面的動作。
-
getStatusFB
在頁面重新整理或是登出再重新登入之後,因為fbAsyncInit不會再次運行,所以透過此function再次取得目前的狀態。
-
loginFB
在沒有做認證或是瀏覽器沒有登入FB的情況下,透過此方法做登入並取得權限。
-
-
postFB
將activity的內容取出,並且發佈到FB,並可以透過發佈的連結連結到pump.io頁面。
backbone.js 結構設計
backbone.js 是一個前端的類MVC架構的framework,可以很結構化的在modal地方與node.js存取物件,而Controller與View幾乎是合併的狀態下生成頁面,詳細請到官網或是這個基礎學習。 而在這邊只針對需要的頁面做增加程式碼。
如程式碼3:
events: {
"click #logout": "logout",
"click #post-note-button": "postNoteModal",
"click #post-picture-button": "postPictureModal",
"click #post-FB-button":"getFBLogin",
"click #post-place-button": "postPlaceModal"
},
其中events的array中表示監聽「事件」、「物件」、「function」,而頁面設計上,「#post-FB-button」是我的按鍵的id, 當「click」事件發生的時候,呼叫「getFBLogin」方法。而該方法是使用jQuery對頁面上的按鈕做隱藏或是顯示。
如程式碼4:
getFBLogin: function() {
facebookconnect.loginFB(function(res){
if (res) {
$('#post-FB-button .icon-thumbs-up').show();
$('#post-FB-button .icon-thumbs-down').hide();
$('#post-note #FBcheckbox').show();
} else {
$('#post-FB-button .icon-thumbs-up').hide();
$('#post-FB-button .icon-thumbs-down').show();
$('#post-note #FBcheckbox').hide();
}
});
return false;
},
facebook po文 function 實作
圖6:
程式碼5:
text = view.$('#post-note #note-content').val(),
to = view.$('#post-note #note-to').val(),
cc = view.$('#post-note #note-cc').val(),
checkbox = view.$('#post-note #FBcheckbox').attr("checked"),
act = new Pump.Activity({
verb: "post",
object: {
objectType: "note",
content: text
}
}),
Pump.newMajorActivity(act, function(err, act) {
if (err) {
view.showError(err);
view.stopSpin();
} else {
//FB post
if(checkbox){
facebookconnect.postFB(act);
}else{
console.log('you dont want to post to FB');
}
view.stopSpin();
view.$el.modal('hide');
Pump.resetWysihtml5(view.$('#note-content'));
// Reload the current page
Pump.addMajorActivity(act);
view.remove();
}
為實作在FB同步文章。在發佈文章的方法中,會先去抓取其中的物件,而程式碼5為判斷是否將選取框打溝,接著依照pump.io的設計,將文章到後端做儲存,儲存之後會回傳到前端用ajax的方式貼文到牆上。
在貼到牆上之前,判斷checkbox是否為勾起的狀態,若確定同步則呼叫 postFB,如程式碼6:
postFB: function (act) {
var link = "\n see the pump -> " + act.attributes.object.url;
var str = act.attributes.object.content + link;
var regex = /<br\s*[\/]?>/gi;
str = str.replace(regex, "\n");
var body = str;
FB.api('/me/feed', 'post', {
message: body
}, function(response) {
if (!response || response.error) {
console.log(response.error);
console.log('Error occured');
} else {
}
});
},
首先先取得本篇文章的url,之後可以透過此連結來到pump.io的頁面。 再使用FB.api的post方法,並將要發佈的文章帶入message中,並發佈到FB牆上。
成果
圖7,在pump.io上的po文
圖8,在FB上的同步po文
透過FB上面的連結,可以連結到pump.io上的該篇文章。
facebook 打卡需求及 function 設計
再來討論打卡的部份。
在打卡之前,可以透過 UI 看到這附近有什麼點可以打,必須有個 table 還有地圖 打卡的地點會是從FB api中取得,所以必須要有 read_stream 的權限打卡的地圖透過 google map api 取得為了取得目前的位置,使用 HTML5的navigator.geolocation.getCurrentPostion
function 設計:
- getLocation
- 使用 HTML 5 的 geo api 取得目前的經緯度,必須在wifi開著的情況下
- getPlace
- 針對目前的經緯度,取得這附近的打卡地點,回傳是25個
- mapImgUrl
- 針對取回的25個地點的經緯度,透過google map api 取得每個地點的地圖照片,並一起傳到UI 事件。
- getPlaceLink
- 打卡前,針對選取的地點取得其FB的網址
- postPlaceFB
- 組合連結並且打卡,api 的部份幾個月前已經與 post 整合了,因此不再使用 checkin api
HTML 5 geoLocation 使用方法
程式碼7:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
callback(position);
}, function(error) {
console.log(error);
alert('Error occurred. Error code: ' + error.code);
});
}else{
alert('no geolocation support');
}
圖9:
使用此 function 要特別注意必須開起 wifi,若有有線網路則無法取得目前位置。 而在取用之前,一定會有使用條款,因此應該等到確定使用者同意存取地點之後再進行打卡的動作,才不會發生錯誤。
回傳值為經緯度。
google map api 使用方法
程式碼8:
var mapImgUrl = function (latlon) {
return "http://maps.googleapis.com/maps/api/staticmap?center=" + latlon + "&zoom=16&size=500x200&sensor=false&markers=size:mid%7Ccolor:red%7C" + latlon;
},
圖10:
給予經緯度,就可以透過此方法可以取得以給予的經緯度為中心的點 google map 的靜態圖片。
facebook get location
圖11:
透過經緯度以及設定距離,可以得到這範圍內所有可以打卡的地點,而每個地點都會有其 Facebook 頁面的 ID 以及經緯度。
一次回傳的值為25個,透過其給予每一個經緯度,我們可以取得每個地點的靜態圖片。
backbone.js 設計
程式碼9:
data: {
user: Pump.principalUser,
lists: lists,
following: following,
places: places
}
events: {
"click #send-place": "postPlace",
"hover .place-select li": "hoverPlace",
"click .place-select li": "selectPlace"
},
hoverPlace: function (ev) {
$('.place-select img').attr('src', $(ev.currentTarget).attr('imgurl'));
},
selectPlace: function (ev) {
$('.place-select li').removeClass('active');
$(ev.currentTarget).addClass('active');
return false;
}
透過data的陣列可以將資料傳至前端的utml(pump.io作者由ejs改寫),其中places帶的值為前面得到的25個地理資訊、名稱以及由google map得到的地理圖片。
event中間聽hover與click事件,當hover發生的時候,就將imgurl顯示出來,而click事件發生時變先移除先前選好的地點,並把新的地點重新覆蓋上顏色。
place-selector.utml
程式碼10:
<div class="place-select">
<ul>
<% if (places && places.length > 0) { %>
<% _.each(places, function (place) { %>
<li fbid="<%- place.id %>" imgurl="<%- place.imgUrl %>">
<a href="javascript:void(0)"><%- place.name %></a>
</li>
<% }); %>
<% } %>
</ul>
<img width="100%" />
</div>
此段程式碼為前端的ejs,由前面的data傳入資料之後,變可以在utml中,利用較為簡單,類似javascript的code將需要的資料方式呈現出來。
facebook 打卡
圖12:
程式碼11:
selectPlace = $('.place-select .active').attr('fbid');
facebookconnect.getPlaceLink(selectPlace, function(respone){
var addText = '--@ <a href="'+respone.link + '">' + respone.name + '</a>';
if(checkbox && selectPlace){
facebookconnect.postPlaceFB(act, selectPlace, text);
}else{
console.log('you dont want to post to FB');
}
當確定選好地點要打卡之後,先將選取地點中的 FB ID 取出,並透過 facebook 的 search api 取得該地點的 Facebook 頁面(一串 url ),並放入貼文之中,傳入後端儲存。
儲存成功之後,傳到前端的同時也透過先前設計postPlaceFB api將貼文同步到facebook。
posPlaceFB 程式碼12:
postPlaceFB: function (act, id, text) {
var link = "\n see the pump -> " + act.attributes.object.url;
var str = text + link;
var regex = /<br\s*[\/]?>/gi;
str = str.replace(regex, "\n");
FB.api('/me/feed', 'post', {
message: str,
place: id
}, function(response) {
if (!response || response.error) {
console.log(response.error);
console.log('Error occured');
} else {
console.log(response);
}
});
},
facebook的post api中,若特別帶上place的id時,就從po文變成了打卡的形式,在文章上面就會出現該地點的圖案,以及在貼文的最後附上打卡的地點。
成果
圖13:
圖14:
總結
pump.io 提供了一個全新的社群網路模式,這個模式有點像是 email 的社群版,除了有更多的隱私之外,每個 pump.io instance 也可以有自己客製化的功能,若不希望自己的資料會外流到別人的伺服器,建立一個屬於自己的社群網路也會是一個好的選項。 目前 pump.io 在推廣上面是稍微困難一些,因為即使是在同一個 instace 上面,也沒有辦法簡單的找到其他的用戶。
facebook bridget 希望做到的是提供一個簡單的推廣方案,可以透過發文上的連結,讓親朋好友連 結到 pump.io 的系統,進而推廣 pump.io,因為這樣的方式可以讓朋友很方便的知道你的 pump.io ID,也才有辦法做follow 的動作。
謝謝大家看完這篇文章,也希望透過此文章,不管是在分散式社群網路、facebook graph api 以及 backbone.js 的使用上有更進一步的認知,謝謝!