我试图得到我的朋友的名字和id与图形API v2.0,但数据返回空:

{
  "data": [
  ]
}

当我使用v1.0时,以下请求一切正常:

FBRequest* friendsRequest = [FBRequest requestForMyFriends];
[friendsRequest startWithCompletionHandler: ^(FBRequestConnection *connection,
                                              NSDictionary* result,
                                              NSError *error) {
    NSArray* friends = [result objectForKey:@"data"];
    NSLog(@"Found: %i friends", friends.count);
    for (NSDictionary<FBGraphUser>* friend in friends) {
        NSLog(@"I have a friend named %@ with id %@", friend.name, friend.id);
    }
}];

但是现在我找不到朋友了!


当前回答

正如Simon提到的,这在新的Facebook API中是不可能的。从技术上讲,你可以通过浏览器自动化来实现。

this is against Facebook policy, so depending on the country where you live, this may not be legal you'll have to use your credentials / ask user for credentials and possibly store them (storing passwords even symmetrically encrypted is not a good idea) when Facebook changes their API, you'll have to update the browser automation code as well (if you can't force updates of your application, you should put browser automation piece out as a webservice) this is bypassing the OAuth concept on the other hand, my feeling is that I'm owning my data including the list of my friends and Facebook shouldn't restrict me from accessing those via the API

使用WatiN的示例实现:

class FacebookUser
{
  public string Name { get; set; }
  public long Id { get; set; }
}

public IList<FacebookUser> GetFacebookFriends(string email, string password, int? maxTimeoutInMilliseconds)
{
  var users = new List<FacebookUser>();
  Settings.Instance.MakeNewIeInstanceVisible = false;
  using (var browser = new IE("https://www.facebook.com"))
  {
    try
    {
      browser.TextField(Find.ByName("email")).Value = email;
      browser.TextField(Find.ByName("pass")).Value = password;
      browser.Form(Find.ById("login_form")).Submit();
      browser.WaitForComplete();
    }
    catch (ElementNotFoundException)
    {
      // We're already logged in
    }
    browser.GoTo("https://www.facebook.com/friends");
    var watch = new Stopwatch();
    watch.Start();

    Link previousLastLink = null;
    while (maxTimeoutInMilliseconds.HasValue && watch.Elapsed.TotalMilliseconds < maxTimeoutInMilliseconds.Value)
    {
      var lastLink = browser.Links.Where(l => l.GetAttributeValue("data-hovercard") != null
                       && l.GetAttributeValue("data-hovercard").Contains("user.php")
                       && l.Text != null
                     ).LastOrDefault();

      if (lastLink == null || previousLastLink == lastLink)
      {
        break;
      }

      var ieElement = lastLink.NativeElement as IEElement;
      if (ieElement != null)
      {
        var htmlElement = ieElement.AsHtmlElement;
        htmlElement.scrollIntoView();
        browser.WaitForComplete();
      }

      previousLastLink = lastLink;
    }

    var links = browser.Links.Where(l => l.GetAttributeValue("data-hovercard") != null
      && l.GetAttributeValue("data-hovercard").Contains("user.php")
      && l.Text != null
    ).ToList();

    var idRegex = new Regex("id=(?<id>([0-9]+))");
    foreach (var link in links)
    {
      string hovercard = link.GetAttributeValue("data-hovercard");
      var match = idRegex.Match(hovercard);
      long id = 0;
      if (match.Success)
      {
        id = long.Parse(match.Groups["id"].Value);
      }
      users.Add(new FacebookUser
      {
        Name = link.Text,
        Id = id
      });
    }
  }
  return users;
}

该方法的原型实现(使用c# /WatiN)参见https://github.com/svejdo1/ShadowApi。它还允许动态更新Facebook连接器,检索您的联系人列表。

其他回答

在Swift 4.2和Xcode 10.1中:

如果你想从Facebook获得好友列表,你需要在Facebook上提交应用进行审查。请参阅一些登录权限:

登录权限

以下是两个步骤:

1)首先你的应用状态必须在Live中

2)从Facebook获得所需的权限。

1)启用我们的应用状态

进入应用程序页面,选择你的应用程序 https://developers.facebook.com/apps/ 在“仪表板”的右上方选择状态。 提交隐私政策URL 选择类别 现在我们的应用程序处于Live状态。

完成了第一步。

2)提交应用进行审核:

首先发送所需的请求。 例如:user_friends, user_videos, user_posts等。 其次,转到当前请求页面 例如:user_events 提交所有详细资料 像这样提交所有请求(user_friends, user_events, user_videos, user_posts等)。 最后提交应用进行审查。 如果你的评论被Facebook接受,你现在就有资格阅读联系人等等。

在2.0版的Graph API中,调用/me/friends会返回此人同样使用该应用程序的朋友。

此外,在v2.0中,必须向每个用户请求user_friends权限。默认情况下,User_friends不再包含在每次登录中。每个用户必须授予user_friends权限,以便出现在对/me/friends的响应中。有关更详细的信息,请参阅Facebook升级指南,或查看下面的摘要。

如果你想访问未使用应用程序的好友列表,有两个选项:

If you want to let your people tag their friends in stories that they publish to Facebook using your App, you can use the /me/taggable_friends API. Use of this endpoint requires review by Facebook and should only be used for the case where you're rendering a list of friends in order to let the user tag them in a post. If your App is a Game AND your Game supports Facebook Canvas, you can use the /me/invitable_friends endpoint in order to render a custom invite dialog, then pass the tokens returned by this API to the standard Requests Dialog.

在其他情况下,应用程序不再能够检索用户的朋友的完整列表(只有那些特别授权你的应用程序使用user_friends权限的朋友)。Facebook已经证实这是“故意的”。

对于想要允许人们邀请朋友使用应用程序的应用程序,你仍然可以使用Web上的发送对话框或iOS和Android上的新消息对话框。

更新:Facebook发布了一个关于这些变化的常见问题解答:https://developers.facebook.com/docs/apps/faq,其中解释了所有可供开发者邀请好友等的选项。

正如Simon提到的,这在新的Facebook API中是不可能的。从技术上讲,你可以通过浏览器自动化来实现。

this is against Facebook policy, so depending on the country where you live, this may not be legal you'll have to use your credentials / ask user for credentials and possibly store them (storing passwords even symmetrically encrypted is not a good idea) when Facebook changes their API, you'll have to update the browser automation code as well (if you can't force updates of your application, you should put browser automation piece out as a webservice) this is bypassing the OAuth concept on the other hand, my feeling is that I'm owning my data including the list of my friends and Facebook shouldn't restrict me from accessing those via the API

使用WatiN的示例实现:

class FacebookUser
{
  public string Name { get; set; }
  public long Id { get; set; }
}

public IList<FacebookUser> GetFacebookFriends(string email, string password, int? maxTimeoutInMilliseconds)
{
  var users = new List<FacebookUser>();
  Settings.Instance.MakeNewIeInstanceVisible = false;
  using (var browser = new IE("https://www.facebook.com"))
  {
    try
    {
      browser.TextField(Find.ByName("email")).Value = email;
      browser.TextField(Find.ByName("pass")).Value = password;
      browser.Form(Find.ById("login_form")).Submit();
      browser.WaitForComplete();
    }
    catch (ElementNotFoundException)
    {
      // We're already logged in
    }
    browser.GoTo("https://www.facebook.com/friends");
    var watch = new Stopwatch();
    watch.Start();

    Link previousLastLink = null;
    while (maxTimeoutInMilliseconds.HasValue && watch.Elapsed.TotalMilliseconds < maxTimeoutInMilliseconds.Value)
    {
      var lastLink = browser.Links.Where(l => l.GetAttributeValue("data-hovercard") != null
                       && l.GetAttributeValue("data-hovercard").Contains("user.php")
                       && l.Text != null
                     ).LastOrDefault();

      if (lastLink == null || previousLastLink == lastLink)
      {
        break;
      }

      var ieElement = lastLink.NativeElement as IEElement;
      if (ieElement != null)
      {
        var htmlElement = ieElement.AsHtmlElement;
        htmlElement.scrollIntoView();
        browser.WaitForComplete();
      }

      previousLastLink = lastLink;
    }

    var links = browser.Links.Where(l => l.GetAttributeValue("data-hovercard") != null
      && l.GetAttributeValue("data-hovercard").Contains("user.php")
      && l.Text != null
    ).ToList();

    var idRegex = new Regex("id=(?<id>([0-9]+))");
    foreach (var link in links)
    {
      string hovercard = link.GetAttributeValue("data-hovercard");
      var match = idRegex.Match(hovercard);
      long id = 0;
      if (match.Success)
      {
        id = long.Parse(match.Groups["id"].Value);
      }
      users.Add(new FacebookUser
      {
        Name = link.Text,
        Id = id
      });
    }
  }
  return users;
}

该方法的原型实现(使用c# /WatiN)参见https://github.com/svejdo1/ShadowApi。它还允许动态更新Facebook连接器,检索您的联系人列表。

虽然Simon Cross的回答是正确的,但我还是想用一个例子(Android)来补充一下。我会尽量概括一下,只关注这个问题。就我个人而言,我把东西存储在数据库中,所以加载很顺利,但这需要一个CursorAdapter和ContentProvider,这有点超出了这里的范围。

我自己来到这里,然后想,现在该怎么办!

这个问题

就像user3594351一样,我注意到朋友数据是空白的。我发现这是通过使用FriendPickerFragment。三个月前有用的,现在没用了。就连Facebook的例子也失败了。所以我的问题是“我如何手工创建FriendPickerFragment ?”

什么不起作用

Simon Cross的选项1还不足以邀请好友加入应用。Simon Cross还推荐了请求对话框,但它一次只允许5个请求。请求对话框还会在任何特定的Facebook登录会话中显示相同的朋友。不是有用的。

什么有效(总结)

第二种选择,需要付出一些努力。你必须确保你遵守Facebook的新规则:1)你是第二场比赛。)你有一个Canvas应用(Web Presence)你的应用已经在Facebook上注册了。这一切都是在Facebook开发者网站的设置下完成的。

为了在我的应用程序中模拟好友选择器,我做了以下工作:

创建显示两个片段的选项卡活动。每个片段显示一个列表。一个片段用于可用的朋友(/me/friends),另一个片段用于可邀请的朋友(/me/invitable_friends)。使用相同的片段代码来呈现两个选项卡。 创建一个AsyncTask从Facebook获取好友数据。一旦数据被加载,将其扔给适配器,适配器将把值呈现到屏幕上。

细节

的AsynchTask

private class DownloadFacebookFriendsTask extends AsyncTask<FacebookFriend.Type, Boolean, Boolean> {

    private final String TAG = DownloadFacebookFriendsTask.class.getSimpleName();
    GraphObject graphObject;
    ArrayList<FacebookFriend> myList = new ArrayList<FacebookFriend>();

    @Override
    protected Boolean doInBackground(FacebookFriend.Type... pickType) {
        //
        // Determine Type
        //
        String facebookRequest;
        if (pickType[0] == FacebookFriend.Type.AVAILABLE) {
            facebookRequest = "/me/friends";
        } else {
            facebookRequest = "/me/invitable_friends";
        }

        //
        // Launch Facebook request and WAIT.
        //
        new Request(
            Session.getActiveSession(),
            facebookRequest,
            null,
            HttpMethod.GET,
            new Request.Callback() {
                public void onCompleted(Response response) {
                    FacebookRequestError error = response.getError();
                    if (error != null && response != null) {
                        Log.e(TAG, error.toString());
                    } else {
                        graphObject = response.getGraphObject();
                    }
                }
            }
        ).executeAndWait();

        //
        // Process Facebook response
        //
        //
        if (graphObject == null) {
            return false;
        }

        int numberOfRecords = 0;
        JSONArray dataArray = (JSONArray) graphObject.getProperty("data");
        if (dataArray.length() > 0) {

            // Ensure the user has at least one friend ...
            for (int i = 0; i < dataArray.length(); i++) {

                JSONObject jsonObject = dataArray.optJSONObject(i);
                FacebookFriend facebookFriend = new FacebookFriend(jsonObject, pickType[0]);

                if (facebookFriend.isValid()) {
                    numberOfRecords++;

                    myList.add(facebookFriend);
                }
            }
        }

        // Make sure there are records to process
        if (numberOfRecords > 0){
            return true;
        } else {
            return false;
        }
    }

    @Override
    protected void onProgressUpdate(Boolean... booleans) {
        // No need to update this, wait until the whole thread finishes.
    }

    @Override
    protected void onPostExecute(Boolean result) {
        if (result) {
            /*
            User the array "myList" to create the adapter which will control showing items in the list.
             */

        } else {
            Log.i(TAG, "Facebook Thread unable to Get/Parse friend data. Type = " + pickType);
        }
    }
}

我创建的FacebookFriend类

public class FacebookFriend {

    String facebookId;
    String name;
    String pictureUrl;
    boolean invitable;
    boolean available;
    boolean isValid;
    public enum Type {AVAILABLE, INVITABLE};

    public FacebookFriend(JSONObject jsonObject, Type type) {
        //
        //Parse the Facebook Data from the JSON object.
        //
        try {
            if (type == Type.INVITABLE) {
                //parse /me/invitable_friend
                this.facebookId =  jsonObject.getString("id");
                this.name = jsonObject.getString("name");

                // Handle the picture data.
                JSONObject pictureJsonObject = jsonObject.getJSONObject("picture").getJSONObject("data");
                boolean isSilhouette = pictureJsonObject.getBoolean("is_silhouette");
                if (!isSilhouette) {
                    this.pictureUrl = pictureJsonObject.getString("url");

                } else {
                    this.pictureUrl = "";
                }

                this.invitable = true;
            } else {
                // Parse /me/friends
                this.facebookId =  jsonObject.getString("id");
                this.name = jsonObject.getString("name");
                this.available = true;
                this.pictureUrl = "";
            }

            isValid = true;
        } catch (JSONException e) {
            Log.w("#", "Warnings - unable to process Facebook JSON: " + e.getLocalizedMessage());
        }
    }
}

在Facebook SDK Graph API v2.0或以上版本中,您必须在登录Facebook时向每个用户请求user_friends权限,因为默认情况下,user_friends不再包含在每次登录中;我们必须加上这个。

每个用户必须授予user_friends权限,以便出现在对/me/friends的响应中。

let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
fbLoginManager.loginBehavior = FBSDKLoginBehavior.web
fbLoginManager.logIn(withReadPermissions: ["email","user_friends","public_profile"], from: self) { (result, error) in
    if (error == nil) {

        let fbloginresult : FBSDKLoginManagerLoginResult = result!
        if fbloginresult.grantedPermissions != nil {
            if (fbloginresult.grantedPermissions.contains("email")) {
                // Do the stuff
            }
            else {
            }
        }
        else {
        }
    }
}

所以在Facebook登录时,它会提示一个包含所有权限的屏幕:

如果用户按下继续按钮,权限将被设置。当您使用Graph API访问好友列表时,您的好友将如上所述登录应用程序

if ((FBSDKAccessToken.current()) != nil) {
    FBSDKGraphRequest(graphPath: "/me/friends", parameters: ["fields" : "id,name"]).start(completionHandler: { (connection, result, error) -> Void in
        if (error == nil) {

            print(result!)
        }
    })
}

输出将包含在通过Facebook登录应用程序时授予user_friends权限的用户。

{
    data = (
             {
                 id = xxxxxxxxxx;
                 name = "xxxxxxxx";
             }
           );
    paging = {
        cursors = {
            after = xxxxxx;
            before = xxxxxxx;
        };
    };
    summary = {
        "total_count" = 8;
    };
}