顯示具有 facebook 標籤的文章。 顯示所有文章
顯示具有 facebook 標籤的文章。 顯示所有文章

2022年5月22日 星期日

Go 開發筆記 - 使用 golang.org/x/oauth2 與 Facebook 登入 / Google OAuth 串接

最近評估網站是否從 PHP 翻到 Golang ,研究了一下關於串接 OAuth2 相關部分。早年在串 FB 登入時,都是直接使用 Facebook PHP SDK ,雖然都知道底層還是 OAuth2 ,但不免還是擔心要串時很麻煩(主要是很懶再刻一份)。稍微研究了一下,原來有 golang.org/x/oauth2 套件可以用,裡頭有支援了各式各家的登入機制,非常方便。

接著反而開始複習起來 Facebook 登入 該怎樣處理,過程:
  • 建立一個 FB 應用程式 developers.facebook.com/apps/
  • 設定 FB 登入相關事宜,包括應用程式網域(添加 localhost)、FB 登入用戶端 OAuth 設定,如 有效的 OAuth 重新導向 URI
  • 處理相關雜事
結果處理相關雜事反而耗掉最多時間,包括:
  • FB應用程式要儲存時,還得弄個 隱私政策網址 跟 用戶資料刪除 網頁
  • FB登入相關,要求都走 https 溝通,變成要研究 golang gin 如何跑 https web server 出來、憑證該怎樣產生等
  • 寫完程式後,體驗流程後,想弄個 github 筆記一下且降低程式碼變動,開始規劃如何靠 YAML 檔案來抽換設定檔
大概就是如此,花了不少時間。最後的效果純粹驗證支援 FB 登入是可行的,收工 XD

2017年3月7日 星期二

使用 Facebook Graph API 取得使用者資料:性別、年紀、地區、教育來做問卷調查

幾年前開發 sign in via Facebook,然後就停擺了好一陣子,最近市場研究想做個問卷,才發現當年收集的資料不怎齊全 XD 再加上 Facebook Graph api 也改版幾次,並且越來越重視個資保護,有很多權限必須額外取得才行。

請參考 https://developers.facebook.com/docs/graph-api/reference/user 定義最準。

而一般問卷常用的年紀,在 Facebook 只有 13/18/21 這三種數值可用,要再更仔細去請用 FB ads 去發了;而教育資訊跟地區資訊也得額外要求額外的權限 user_education_history 跟 user_location 才能得到

最後,在用 graph api 來取得這些資訊吧:

/me?fields=id,name,email,education,gender,location,age_range
{
  "id": "##",
  "name": "Yuan-Yi Chang",
  "email": "@gmail.com",
  "education": [
    {
      ...
      "type": "High School",
      ...
    },
    {
      ...
      "type": "College",
      ...
    },
    {
      ...
      "type": "Graduate School",
      ...
    }
  ],
  "gender": "male",
  "location": {
    "id": "110765362279102",
    "name": "Taipei, Taiwan"
  },
  "age_range": {
    "min": 21
  }
}


預計先這般儲存吧:

age_range => 13/18/21
gender => None/Male/Female/Unisex
education => None/HighSchool/College/GraduateSchool

2017年3月2日 星期四

[PHP] Facebook Graph API v2.2 升級提醒 - CodeIgniter 2.x 與 Facebook PHP SDK v5

這幾天一堆 fb app 被這種信轟炸:YourFBApp 的新開發人員重要通知,簡言之就是 facebook graph 舊版 api 即將在 2017/03/25 失效,請立即更新。

Facebook 開放平台變更紀錄 - https://developers.facebook.com/docs/apps/changelog

追了一下,主因是很多手上的服務是在 2014 年底開發,當時就是用 v2.2 graph api 沒錯,關鍵之處可以用搜尋:

facebook-php-sdk-v4 $ grep -r "v2.2" *
src/Facebook/FacebookRequest.php:  const GRAPH_API_VERSION = 'v2.2';


非常精準的地發現自己就是這個族群,也感謝 Facebook 賜予工作機會(誤),這樣每兩年就有新工作可以做,非常開心!接下來就是痛苦的開始,還是一口氣從 v4 升級到 v5 吧!

前提:

由於使用 PHP CodeIgniter 2.x 架構,再加上 HA 架構,因此需要處理 SESSION 的儲存機制,在此就用 DB 處理,而對 v4 SDK 中,是直接在 FacebookRedirectLoginHelper 改寫一份對於認證所需資料儲存的東西,但在 v5 架構就更漂亮了,可以把負責資料儲存的東西傳入,不需再改成 FacebookRedirectLoginHelper。若服務仍在單一機器上運行,可以略過此設計。

v4:

<?php
// FacebookRedirectLoginCIHelper
namespace Facebook;
use Facebook\FacebookRedirectLoginHelper;
class FacebookRedirectLoginCIHelper extends \Facebook\FacebookRedirectLoginHelper {
private $sessionPrefix = 'FBRLH_';
public function __construct($redirectUrl, $appId = null, $appSecret = null) {
parent::__construct($redirectUrl, $appId, $appSecret);
$this->ci = & get_instance();
}
protected function storeState($state) {
if ($this->ci->session->set_userdata($this->sessionPrefix . 'state', $state)) {
$this->state = $this->ci->session->set_userdata($this->sessionPrefix . 'state', $state);
return $this->state;
}
return NULL;
}
protected function loadState() {
return $this->state = $this->ci->session->userdata($this->sessionPrefix . 'state');
}
}

v5:

<?php
// FacebookSessionPersistentDataCIHandler
namespace Facebook\PersistentData;
use Facebook\Exceptions\FacebookSDKException;
class FacebookSessionPersistentDataCIHandler implements PersistentDataInterface {
protected $sessionPrefix = 'FBRLH_';
public function __construct($enableSessionCheck = true) {
$this->ci = & get_instance();
}
public function get($key) {
if ($this->ci)
return $this->ci->session->userdata($this->sessionPrefix . $key);
return null;
}
public function set($key, $value) {
if ($this->ci)
$this->ci->session->set_userdata($this->sessionPrefix . $key, $value);
}
}


初始化:

v4:

require 'path/facebook-php-sdk-v4/autoload.php';
use Facebook\FacebookRedirectLoginCIHelper;
use Facebook\FacebookRequest;
use Facebook\FacebookSession;
use Facebook\GraphUser;

FacebookSession::setDefaultApplication($this->config->item("api_key"), $this->config->item("secret_key"));

v5:

require 'path/php-graph-sdk-5.4.4/src/Facebook/autoload.php';
use Facebook\Facebook;
use Facebook\Authentication\AccessToken;
use Facebook\PersistentData\FacebookSessionPersistentDataCIHandler;
use Facebook\FacebookRequest;

$fb = new Facebook([
'app_id' => $this->config->item("api_key"),
'app_secret' => $this->config->item("secret_key"),
'persistent_data_handler' => new FacebookSessionPersistentDataCIHandler(),
//'default_graph_version' => 'v2.8',
]);


取得登入網址:

v4:

$helper = new FacebookRedirectLoginCIHelper($callback_url);
$login_url = $helper->getLoginUrl($this->config->item('fb_scope'));

v5:

$helper = $fb->getRedirectLoginHelper();
$login_url = $helper->getLoginUrl($callback_url, $this->config->item('fb_scope'));


登入完成取得 access_token:

v4:

$fb_session = $helper->getSessionFromRedirect();
$access_token = $fb_session->getAccessToken();

v5:

$accessToken = $helper->getAccessToken();


取得 longlived access_token:

v4:

$long_lived_token = $fb_session->getLongLivedSession()->getToken();

v5:
$long_lived_token = (string) $fb->getOAuth2Client()->getLongLivedAccessToken($access_token);


查詢個人資料:

v4:

$user_profile = (new FacebookRequest($fb_session, 'GET', '/me'))->execute()->getGraphObject(GraphUser::className());
$uid = $user_profile->getProperty('id');
$name = $user_profile->getProperty('name');
$email = $user_profile->getProperty('email');
$profile_url = $user_profile->getProperty('link');
if (empty($profile_url))
$profile_url = https://www.facebook.com/$uid";
$profile_image_link = "https://graph.facebook.com/$uid/picture";

v5:

$user_profile = $fb->get('/me', $accessToken)->getGraphNode();
$uid = $user_profile->getField('id');
$name = $user_profile->getField('name');
$email = $user_profile->getField('email');
$profile_url = $user_profile->getField('link');
if (empty($profile_url))
$profile_url = https://www.facebook.com/$uid";
$profile_image_link = "https://graph.facebook.com/$uid/picture";


從字串初始化並檢查 token 是否過期:

v4:

use Facebook\FacebookSession;

$session = new FacebookSession( $token );
return $session->validate();

v5:

return $fb->getOAuth2Client()->debugToken( $token )->getIsValid() == true;


api 查詢架構:

v4:

function query($token, $method, $api, $array_mode = true) {
if (!empty($token)) {
$session = new FacebookSession($token);
                 if (!$session->validate())
return false;
$response = ( new FacebookRequest( $session, $method, $api ) )->execute();
if ($array_mode)
return $response->getGraphObject()->asArray();
return $response->getGraphObject();
}
return false;
}

v5:

function _node_query($token, $method, $api, $array_mode = true) {
if (!empty($token)) {
try {
if (!strcasecmp($method, 'POST'))
return $array_mode ? $this->fb->post($api, $token)->getGraphNode()->asArray() : $this->fb->post($api, $token)->getGraphNode();

return $array_mode ? $this->fb->get($api, $token)->getGraphNode()->asArray() : $this->fb->get($api, $token)->getGraphNode();
} catch (Exception $e) {
var_dump($e);
}
}
return false;
}

function _edge_query($token, $method, $api, $array_mode = false) {
if (!empty($token)) {
try {
if (!strcasecmp($method, 'POST'))
return $array_mode ? $this->fb->post($api, $token)->getGraphEdge()->asArray() : $this->fb->post($api, $token)->getGraphEdge();

return $array_mode ? $this->fb->get($api, $token)->getGraphEdge()->asArray() : $this->fb->get($api, $token)->getGraphEdge();
} catch (Exception $e) {
var_dump($e);
}
}
return false;
}


取得使用者權限清單:

v4:

query($token, 'GET', '/me/permissions');

v5:

_edge_query($token, 'GET', '/me/permissions');


查詢朋友清單:

v4:

function get_friend_installed_app_list($token, &$have_next_page, $page = 1, $item_per_page = 25) {
$have_next_page = false;
$data = $this->query($token, 'GET', '/me/friends?fields=installed&limit='.($item_per_page).'&offset='.(($page - 1) * $item_per_page));
if(isset($data['data']) && ($cnt = count($data['data'])) > 0) {
if (isset($data['paging']) && is_object($data['paging']) && property_exists($data['paging'], 'next')) {
if ($cnt >= $item_per_page)
$have_next_page = true;
}
$output = array();
foreach($data['data'] as $user)
if (is_object($user) && property_exists($user, 'id'))
array_push($output, $user->id);
return $output;
}
return false;

}

v5:

function get_friend_installed_app_list($token, &$have_next_page, $page = 1, $item_per_page = 25) {
$have_next_page = false;
$data = $this->_edge_query($token, 'GET', '/me/friends?fields=installed&limit='.($item_per_page).'&offset='.(($page - 1) * $item_per_page), false);
$items = $data->asArray();
if(is_array($items) && ($cnt = count($items)) > 0) {
$paging = $data->getMetaData();
if (isset($paging['paging']) && isset($paging['paging']['next']))
if ($cnt >= $item_per_page)
$have_next_page = true;
$output = array();
foreach($items as $user)
if (isset($user['id']))
array_push($output, $user['id']);
return $output;
}
return false;
}


收工!

2016年1月2日 星期六

[PHP] 架設 Phabricator - Open Source tools code for review, task management, and project communication @ Ubuntu 14.04

這原本是 Facebook 內部的工具,負責開發的工程師離開 FB 後成立公司來維護它,我對他的感覺就像 Redmine 這類工具。第一次接觸是在 2015 年秋天,但直到年底我才自己架設它,其實過程不會很複雜,但不是 apt-get 就能安裝完的,多少還是筆記一下吧。此外,想要快速把玩可以試試官方提供的 script - install_ubuntu.sh ,更多安裝簡介:Installation Guide

這邊只簡單紀錄自己安裝的過程,其中 MySQL DB server 採用 AWS RDS,而 web server 使用 nginx 跟 php5-fpm:

$ cat /etc/apt/sources.list.d/nginx_org_packages_ubuntu.list
deb http://nginx.org/packages/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/ubuntu/ trusty nginx
$ wget -O - http://nginx.org/keys/nginx_signing.key | sudo apt-key add -
$ sudo apt-get -qq update && sudo apt-get install git dpkg-dev php5 php5-mysql php5-gd php5-dev php5-curl php-apc php5-cli php5-json php5-fpm
$ sudo apt-get install nginx
$ lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:    14.04
Codename:    trusty
$ php -v
PHP 5.5.9-1ubuntu4.14 (cli) (built: Oct 28 2015 01:34:46)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies

$ mkdir /opt && cd /opt
$ git clone https://github.com/phacility/libphutil.git
$ git clone https://github.com/phacility/arcanist.git
$ git clone https://github.com/phacility/phabricator.git


設定 https web server & php 環境:

$ cat /etc/nginx/conf.d/phabricator.conf
server {
listen       443;
server_name  localhost;
client_max_body_size 8M;

location / {
root   /opt/phabricator/webroot;
index  index.php;
rewrite ^/(.*)$ /index.php?__path__=/$1 last;
}
location = /favicon.ico {
try_files $uri =204;
}
location /index.php {
root "/opt/phabricator/webroot";
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
ssl on;
ssl_certificate /etc/ssl/nginx/server.crt ;
ssl_certificate_key /etc/ssl/nginx/server.key ;
}

$ cat /etc/nginx/nginx.conf
user www-data;
...

$ cat /etc/php5/fpm/pool.d/www.conf
user = www-data
group = www-data
listen = /var/run/php5-fpm.sock
listen.owner = www-data
listen.group = www-data
...

$ cat /etc/php5/fpm/php.ini
upload_max_filesize = 60M
memory_limit = 512M
post_max_size = 60M
date.timezone = Asia/Taipei
opcache.validate_timestamps=false
...


設定 Phabricator 部分:

$ sudo /opt/phabricator/bin/config set mysql.host server-name.ap-northeast-1.rds.amazonaws.com
$ sudo /opt/phabricator/bin/config set mysql.user root
$ sudo /opt/phabricator/bin/config set mysql.pass password
$ sudo /opt/phabricator/bin/storage upgrade

Fix these schema issues? [y/N] Y
Fixing schema issues...
Done.                                                                      
Completed fixing all schema issues.

$ sudo /opt/phabricator/bin/config set phabricator.base-uri 'https://my-domain-name/'
$ sudo /opt/phabricator/bin/config set security.alternate-file-domain https://my-domain-name/
$ sudo /opt/phabricator/bin/config set phpmailer.smtp-user my-smtp-user
$ sudo /opt/phabricator/bin/config set phpmailer.smtp-password my-smtp-password
$ sudo /opt/phabricator/bin/config set phpmailer.smtp-port 25
$ sudo /opt/phabricator/bin/config set phpmailer.smtp-host my-domain-name
$ sudo /opt/phabricator/bin/phd start
$ cat /etc/rc.local
sudo /opt/phabricator/bin/phd start
$ sudo mkdir /var/repo && sudo chown www-data /var/repo
$ cat /opt/phabricator/conf/local/local.json
{
  "phpmailer.smtp-user": "my-smtp-user",
  "phpmailer.smtp-password": "my-smtp-password",
  "phpmailer.smtp-port": 25,
  "phpmailer.smtp-host": "my-domain-name",
  "security.alternate-file-domain": "https://my-domain-name/",
  "phabricator.base-uri": "https://my-domain-name/",
  "mysql.pass": "password",
  "mysql.user": "root",
  "mysql.host": "server-name.ap-northeast-1.rds.amazonaws.com"
}


剩下的可以從網頁上設定,網頁管理做得很不錯的,例如 metamta.default-address、metamta.domain 等,有些 issue 是 MySQL DB server 的,只是 AWS RDB 並不能修改。整體上架設還算輕鬆啦,唯一的缺點是在 DB server 內會開很多資料庫,建議可以專門用一個 DB server 來維護。

2015年4月21日 星期二

iOS 開發筆記 - 製作 iOS Simulator app 版本送交給 Facebook 進行 review (creating-ios-simulator-build-for-review)

似乎...Facebook SDK 文件沒找到?只好隨意 Google 一些資料

$ cd /path/project
$ xcodebuild -showsdks
OS X SDKs:
OS X 10.9                     -sdk macosx10.9
OS X 10.10                     -sdk macosx10.10

iOS SDKs:
iOS 8.3                       -sdk iphoneos8.3

iOS Simulator SDKs:
Simulator - iOS 8.3           -sdk iphonesimulator8.3

$ xcodebuild -arch i386 -sdk iphonesimulator8.3


若用 CocoaPods (碰到 ld: library not found for -lPods-XXX ) 或 xcworkspace 維護的,需要多一點指令:

$ xcodebuild -arch i386 -sdk iphonesimulator8.3 -workspace YourName.xcworkspace -scheme YourName

接著,想執行看看:

$ ios-sim launch /Users/user/Library/Developer/Xcode/DerivedData/YourName-xxxxxx/Build/Products/Debug-iphonesimulator/YourName.app

其中 ios-sim 可以逛一下這邊:https://github.com/phonegap/ios-sim

2015年4月13日 星期一

iOS 開發筆記 - 使用 Facebook SDK 4.0 分享至塗鴉牆與簡易的 publish_actions 權限判斷流程

大概都遵循 Facebook 文件,這邊指紀錄在一個 ViewController 中,啟動分享至使用者塗鴉牆的流程,包括判斷使用者是否安裝過 Facebook app、是否授權 publish_actions 等,以及選擇用 Facebook app 分享或是用 Facebook SDK 分享等。

#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <FBSDKShareKit/FBSDKShareLinkContent.h>
#import <FBSDKShareKit/FBSDKShareDialog.h>
#import <FBSDKShareKit/FBSDKShareAPI.h>
#import <FBSDKShareKit/FBSDKShareOpenGraphAction.h>

#import <FBSDKLoginKit/FBSDKLoginManager.h>
#import <FBSDKLoginKit/FBSDKLoginManagerLoginResult.h>

@interface ViewController () <FBSDKSharingDelegate>
@property (nonatomic, strong) NSString *url;
@end

@implementation ViewController

#pragma mark - FBSDKSharingDelegate

- (void)sharer:(id<FBSDKSharing>)sharer didCompleteWithResults:(NSDictionary *)results {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"INFO" message:@"done" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
    [alertView show];
}

- (void)sharer:(id<FBSDKSharing>)sharer didFailWithError:(NSError *)error {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"INFO" message:@"error" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
    [alertView show];
}

- (void)sharerDidCancel:(id<FBSDKSharing>)sharer {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"INFO" message:@"cancel" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
    [alertView show];
}

#pragma mark - share methods

- (void)useFacebookApp {
    FBSDKShareLinkContent *content = [[FBSDKShareLinkContent alloc] init];
    content.contentURL = [NSURL URLWithString:self.url];
    [FBSDKShareDialog showFromViewController:self
                                 withContent:content
                                    delegate:nil];
}

- (void)useFacebookSDK {
    FBSDKShareLinkContent *content = [[FBSDKShareLinkContent alloc] init];
    content.contentURL = [NSURL URLWithString:self.url];
    [FBSDKShareAPI shareWithContent:content delegate:self];
}

#pragma mark - init action

- (void)doShare
{
    if (![FBSDKAccessToken currentAccessToken]) {
        FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
        [login logInWithPublishPermissions:@[@"publish_actions"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
            if (error) {
                // Process error
                NSLog(@"Process error");
            } else if (result.isCancelled) {
                // Handle cancellations
            } else {
                if ([result.grantedPermissions containsObject:@"publish_actions"]) {
                    [self useFacebookSDK];
                } else {
                    [self useFacebookApp];
                }
            }
        }];
        return;
    } else if ([[FBSDKAccessToken currentAccessToken] hasGranted:@"publish_actions"]) {
        [self useFacebookSDK];
    } else {
        [self useFacebookApp];
    }
    return;
}

@end

2015年4月3日 星期五

iOS 開發筆記 - 使用 Facebook iOS SDK 4 之處理 FBSDKLoginManager result.isCancelled = true 問題

很久沒用 FB iOS SDK 了,一不小心發現已經進入到 4.0 版本,接著一堆 code 失效 Orz 接著,又來適應一下新板 SDK 的情況。

看著文件刻了一下:

#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <FBSDKLoginKit/FBSDKLoginManager.h>
#import <FBSDKLoginKit/FBSDKLoginManagerLoginResult.h>
- (void)testFB {
    if (![FBSDKAccessToken currentAccessToken]) {
        NSLog(@"do login");
     
        FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
        [login logInWithPublishPermissions:@[@"publish_actions"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
            if (error) {
                // Process error
                NSLog(@"Process error");
            } else if (result.isCancelled) {
                // Handle cancellations
                NSLog(@"Handle cancellations: %@, %@, %@", result, result.grantedPermissions, [FBSDKAccessToken currentAccessToken]);
            } else {
                if ([result.grantedPermissions containsObject:@"publish_actions"]) {
                    NSLog(@"with publish_actions");
                } else {
                    NSLog(@"without publish_actions");
                }
            }
        }];
        return;
    } else if ([[FBSDKAccessToken currentAccessToken] hasGranted:@"publish_actions"]) {
        NSLog(@"use publish_actions");
    }
 
    NSLog(@"token: %@", [FBSDKAccessToken currentAccessToken]);
}


很奇妙地,無論使用者如何授權此 Facebook app ,永遠都會落入 result.isCancelled == true 的事件中,然後 token 永遠都 null。

追了一下,原來還要設定 AppDelegate 太久沒用都忘記了,啊 FB iOS SDK 文件上也沒提醒 Orz

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [[FBSDKApplicationDelegate sharedInstance] application:application
                                                          openURL:url
                                                sourceApplication:sourceApplication
                                                       annotation:annotation];
}


如此一來,有幾種情境就都能抓到了:
  • 使用者不想授權此 app => Handle cancellations
  • 使用者授權此 app ,但不想給 publish_actions 權限 => without publish_actions
  • 使用者授權此 app ,並給予 publish_actions 權限 => with publish_actions

2015年3月11日 星期三

Facebook 開發筆記 - Facebook app 無法取得真實的 account id

最近在開發 Facebook app 時,發現 Facebook API 已經有更新不少東西,其中有一項還滿不錯的,第三方 Facebook app 無法取得 Facebook user 的真實 ID 資訊,每個 app 會各別拿到一個,例如在 A app 跟 B app 詢問 graph api: /me 時,拿到的 id 會不一樣。然而,不一樣的 ID 卻仍可以一樣定位到同一個使用者。

這個最大的缺點是各個 Facebook app 沒有互通的資訊,但對於各個 facebook app 取得到的資訊卻還是一樣夠用。此外,可以用 www.facebook.com/id 去測試,可以發現仍可以定位到同一個 user,算是滿貼心的隱私設計。

https://developers.facebook.com/docs/graph-api/reference/v2.2/user
The id of this person's user account. This ID is unique to each app and cannot be used across different apps. Our upgrade guide provides more info about this.

2015年3月10日 星期二

Facebook 開發筆記 - 使用單一 Facebook app 仿 OAuth 架構提供單多平台多 app 登入使用

由於 Facebook app 在任何平台上,只能允許一個 app,例如 iOS platform 就只能綁定一個 iOS app,如果想要多個 iOS app 都用同一款 Facebook app 時,就出現了這奇妙的需求 :P

因此,再次包裝的方式,那就是用 UIWebview/WebView 來解吧!對於任何一款 Mobile app 都是採用 Browser 來完成登入的。而 Backend service 是維持採用同一款 Facebook app。

原理:主體是以 Facebook Javascript SDK 來使用,當完成 Facebook app 登入認證後,將 Javascript 端取得的 Facebook app token 丟給 backend service ,驗證後交換成自家 service token,未來用自己的 service token 來進行服務互動。

以上狀態僅需依照 Facebook SDK Javascript 文件已經可以完成大半了:

FB.getLoginStatus(function(response) {
  if (response.status === 'connected') {
    console.log('get facebook app token');
  }
  else {
    FB.login();
  }
});


在 response.status === 'connected' 就能取得 facebook token,再轉呼叫自家 service 來交換 token 即可收工。

然而?事情不是那麼簡單的 XD 在 mobile platform 環境上,用 chrome browser 或是 safari 都可以正常工作,唯獨 PhoneGap 等類似架構會出錯,主因是 Facebook SDK Javascript 登入時,會彈跳視窗,結束後可以再導回來,但在 PhoneGap 架構上,可能受限於沒有分頁機制而出錯?總之,解法:自行用 direct_url 跟 Facebook 互動吧!這招印象中是在寫 server side 的用法:

當使用者點擊 login 時,不要用 Facebook Javascript SDK,而是將網址導向到:

https://www.facebook.com/dialog/oauth?client_id=YourFacebookAppID&redirect_uri=http://localhostOrServiceWebURL/&response_type=token&scope=publish_stream,email

當使用者完成登入後,將導回你指定的位置,而這時在 FB.getLoginStatus 時,又可以正常取得 response.status === 'connected' 等資訊了。

2015年2月14日 星期六

Call Facebook API without token at Linode: error: message: Application request limit reached

昨晚開了一台 Linode Server,想說來研究一下 Facebook api,結果:

$ curl "https://graph.facebook.com/fql?q=SELECT%20total_count%20FROM%20link_stat%20WHERE%20url%20='www.google.com'"
{"error":{"message":"(#4) Application request limit reached","type":"OAuthException","code":4}}


不死心,發 ticket 抱怨一下,想說會不會前一個用戶太操,得到了免費換了 IP 。結果仍是一樣的。接著,想起有美國的機器,測了一下仍是如此。

幾番測試後,我猜 Linode IP Range 大概被關注很久,預設都不給用,看來人紅也是種困擾 XD

以上 command 在任何一台的機器,預期得到的正確結果為:

$ curl "https://graph.facebook.com/fql?q=SELECT%20total_count%20FROM%20link_stat%20WHERE%20url%20='www.google.com'"
{"data":[{"total_count":10366203}]}

2014年11月25日 星期二

[PHP] CodeIgniter 與 Facebook PHP SDK v4 整合筆記 @ Ubuntu 14.04

整個流程並主要是把 Facebook PHP sdk v4 source code 下載到 ci_proj/application/libraries 中,接著撰寫一隻 ci library: facebook.php 來橋接一下,就差不多可以收工了。在此謹驗證收到的 facebook token 是否有效,其他部分並不實作:

$ cd /path/ci_proj/application/libraries
$ git clone https://github.com/facebook/facebook-php-sdk-v4.git
$ ln -s facebook-php-sdk-v4 facebook


建立 Facebook 相關 config:

$ vim /path/ci_proj/application/config/facebook.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
$config['facebook']['api_id'] = 'FACEBOOK APP ID';
$config['facebook']['app_secret'] = 'FACEBOOK APP SECRET KEY';


編輯橋接的 library 用法:

$ vim /path/ci_proj/application/libraries/facebook.php

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
require_once APPPATH . 'libraries/facebook/autoload.php';

use Facebook\FacebookRedirectLoginHelper;
use Facebook\FacebookSession;
use Facebook\FacebookRequest;
use Facebook\GraphUser;
use Facebook\FacebookRequestException;

class Facebook {
var $ci;
var $helper;
var $session;
var $permissions;

public function __construct() {
$this->ci =& get_instance();
$this->ci->load->config('facebook');
$this->permissions = $this->ci->config->item('permissions', 'facebook');
FacebookSession::setDefaultApplication( $this->ci->config->item('api_id', 'facebook'), $this->ci->config->item('app_secret', 'facebook') );
        }
public function token_validation($token) {
try {
$this->session = new FacebookSession( $token );
if ( $this->session->validate() )
return true;
} catch ( Exception $e ) {
}
$this->session = null;
return false;
}
public function get_user($token = NULL) {
if ( $this->session || (!empty($token) && $this->token_validation($token)) ) {
$request = ( new FacebookRequest( $this->session, 'GET', '/me' ) )->execute();
$user = $request->getGraphObject()->asArray();
return $user;
}
return false;
}
}


使用方式:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Test extends CI_Controller {
public function index()
{
$this->load->config('facebook');
$this->load->library('facebook');
$output = array(
'reqeusts' => $_REQUEST,
'app_id' => $this->config->item('api_id', 'facebook'),
'user_id' => $this->facebook->get_user($_REQUEST['token'])
);
$this->output->set_content_type('application/json')->set_output(json_encode($output));
        }
}

2014年4月27日 星期日

使用 Graph API 得知指定 URL 在 Facebook 散播的情況

很久沒用都會忘記 Orz

透過 Open Graph:

$ curl https://graph.facebook.com/?ids=http://blog.changyy.org/ | python -mjson.tool
{
    "http://blog.changyy.org/": {
        "id": "http://blog.changyy.org/",
        "shares": 2
    }
}


透過 FQL: SELECT url,id,site FROM object_url WHERE url = 'http://blog.changyy.org'

$ curl http://graph.facebook.com/fql?q=SELECT%20url,id,site%20FROM%20object_url%20WHERE%20url%20=%20%27http://blog.changyy.org%27 | python -mjson.tool
{
    "data": [
        {
            "id": 597172713656917,
            "site": "blog.changyy.org",
            "url": "http://blog.changyy.org"
        }
    ]
}

2014年4月26日 星期六

iOS 開發筆記 - Facebook SDK 與 App Reviewer 無法登入 FB 問題的處理心得



經過幾番測試,發現有些 iOS App Reviewer 無法正常登入 Facebook ,因此,假使你的 app 一開始就需要登入 FB 的話,很有可能會莫名其妙地收到 App Submission Feedback 的信件通知 Orz 然後百般跟 Reviewer 說會不會是他自己無法連到也沒法解決,千篇一律地收到制式回應,最後為了降低等待時間,就是重新上傳 binary 等待下一個 App Reviewer 了

故最佳解就是先限制 app 可以使用的地區,例如美國等,以此避免某些 App Reviewer 所在的地區不能連 Facebook !

2014年4月10日 星期四

iOS 開發筆記 - The Facebook server could not fulfill this access request: no stored remote_app_id for app


犯了一個小錯誤 XD 太久沒用 Facebook app 了,新版界面忘了新增 iOS Bundle ID 導致 Reviewer 無法登入 :P 但很妙的這個現象是在 iOS 使用內建系統 Facebook 帳號整合時,才會出現的。

至於新增 iOS app 的設定:
developers.facebook.com -> Your App -> 設定 -> 新增平台
預設沒有顯示出來,所以...不小心忽略了。

2014年1月18日 星期六

iOS 開發筆記 - The app references non-public selectors in Payload/.app/: id

這次碰到的問題是使用 Facebook SDK 造成的 Orz 但很妙的事,一樣的 code 再 10 天前並沒有這個現象。

追了一下可能的問題是在於 Facebook SDK 的設計:

id<FBGraphUser> friend
id<FBGraphUserExtraFields> user


然後範例都教人使用:

user.id 或 [user id]

解法:

[user objectForKey:@"id"]

只要透過存取方式的修改,即可避開。