I started with Twitter, and today I’ll tell you how I was struggling with publishing to Facebook from C# / .NET Core project. “Struggling” part is totally on the Facebook side. Потому что это, блядь, нечто.

.NET Core, Facebook, logo

Now to the details.

Getting started

So, we want to post updates from our website to a Facebook page. For that you’ll need to have a Facebook account (obviously) and create a page.

And you’ll need to become a developer and create a Facebook app. Having done that, go to your app settings and copy the following values from there:

  • App ID;
  • App Secret.
Facebook app settings

So far so good.

Torments, agony and misery

But then I got heavily misled by the Facebook documentation. It says, that to publish as a page you need to obtain access tokens first. Okay, let’s try to do that:

var rez = Task.Run(async () =>
{
    string url = "https://graph.facebook.com/YOUR-PAGE-ID?fields=access_token";
    using (var http = new HttpClient())
    {
        var httpResponse = await http.GetAsync(url);
        var httpContent = await httpResponse.Content.ReadAsStringAsync();
        
        return httpContent;
    }
});
var rezJson = JObject.Parse(rez.Result);
Console.WriteLine(rezJson);

Here’s the response:

{
  "error": {
    "message": "An access token is required to request this resource.",
    "type": "OAuthException",
    "code": 104,
    "fbtrace_id": "some/id"
  }
}

Right, so we need some other access token first? No shit, that’s why this request looked so suspiciously simple. But okay, indeed, there is some Facebook Login mentioned there, so let’s go there.

Okay, we’re here, where to next?

Facebook Login documentation

You think, Websites or mobile websites? Sounds logical, right? No! Because fuck you, that’s why!

Access Tokens is the one you need. Yes, another, different access tokens page. There you need App Access Tokens section. Let’s try to get this one:

// ...
string url = "https://graph.facebook.com/oauth/access_token?client_id=YOUR-APP-ID&client_secret=YOUR-APP-SECRET&grant_type=client_credentials";
// ...

Okay, this time it actually returned some token:

{
  "access_token": "GENERATED-APP-ACCESS-TOKEN",
  "token_type": "bearer"
}

Now let’s try to repeat the previous request with this token in parameters:

// ...
string url = "https://graph.facebook.com/YOUR-PAGE-ID?fields=access_token&access_token=GENERATED-APP-ACCESS-TOKEN";
// ...

Response:

{
  "id": "YOUR-PAGE-ID"
}

Thanks a lot! That’s exactly what I… see no point in getting! Documentation promised me Page Access Token, but where is it? Here in the response there is only my Page ID, which I already knew.

How it actually works

Okay, I’ll spare you from hours of me being raped by the Facebook documentation (блядский кусок говна этот Фейсбук вместе с его пиздовыебанной документацией) and tell you how it actually works.

Those Stack Exchange answers were a great help:

After reading those, I understood the biggest confusion of the whole fucking Facebook documentation - the only way to get Page Access Token is to use Graph API Explorer! You cannot get it by performing silly HTTP requests from your HTTP client. So, it would be really helpful, if it was written in the very first sentence at the very first documentation page.

And the whole Facebook Login thing apparently is supposed to be used for users social logins on websites and applications, and not for requests from servers.

Okay, let’s get this bloody Page Access Token.

Getting Page Access Token

One more surprise for you - Facebook access tokens have expiration time, so they are valid for a particular amount of time: standard ones expire in 2 hours (or less), and extended ones expire in 2 months. Fortunately, the Page Access Token, that we will be getting, has no expiration time (is valid forever).

Open Graph API Explorer and choose your application here:

Graph API Explorer, choosing application

Now click on Get User Access Token:

Graph API Explorer, User Access Token

A dialog window with lots of checkboxes will appear. Check these two: manage_pages and publish_pages.

Graph API Explorer, permissions

Generated token will appear in the Access Token field. It’s a short-term one. Here you can check when it expires:

Facebook short-term access token

You can (probably) continue using Graph API Explorer, but I closed it and did the rest from code.

Here’s how you get an extended token:

// ...
string url = "https://graph.facebook.com/oauth/access_token?client_id=YOUR-APP-ID&client_secret=YOUR-APP-SECRET&grant_type=fb_exchange_token&fb_exchange_token=YOUR-SHORT-TERM-TOKEN";
// ...

Take it from the response:

{
  "access_token": "EXTENDED-TOKEN",
  "token_type": "bearer",
  "expires_in": 5183975
}

Check its expiration time:

Facebook extended access token

And finally send this request:

// ...
string url = "https://graph.facebook.com/me/accounts?access_token=YOUR-EXTENDED-TOKEN";
// ...

Grab your Page Access Token (and also Page ID) from the response:

{
  "data": [
    {
      "access_token": "PAGE-ACCESS-TOKEN",
      "category": "Website",
      "name": "YOUR-PAGE-NAME",
      "id": "YOUR-PAGE-ID",
      "perms": [
        "ADMINISTER",
        "EDIT_PROFILE",
        "CREATE_CONTENT",
        "MODERATE_CONTENT",
        "CREATE_ADS",
        "BASIC_ADMIN"
      ]
    }
  ],
  "paging": {
    "cursors": {
      "before": "some",
      "after": "some"
    }
  }
}

Check its expiration time:

Facebook Page Access Token

Expires: never. Hallelujah.

Publishing to Facebook Page

In order to be able to publish stuff to your Facebook page via Facebook API you need to have 2 things;

  1. Page Access Token - we got it on the previous step;
  2. Page ID - same here, but “just for fun” try to find this one on your Facebook page.

Publishing a simple text post is pretty easy, I will show this one as well, but I also needed to publish posts with pictures attached, and that’s… something.

I tried lots of things: uploading attachments, attaching links, some other stuff, but nothing worked the way I wanted. Either nothing was attached, or picture was attached in some clipped form, or it was transformed to a web-link with some sort of a preview. You know, what worked? Uploading a new photo to the page album, getting the ID of the created post (not the photo ID) and then updating its message field! Пиздец. That way photo, that was published to the page feed, miraculously becomes a post (kinda?..).

That’s an example of what I wanted to get (and finally did):

Facebook post with picture

So, here’s my class for publishing to a Facebook page:

class Facebook
{
    readonly string _accessToken;
    readonly string _pageID;
    readonly string _facebookAPI = "https://graph.facebook.com/";
    readonly string _pageEdgeFeed = "feed";
    readonly string _pageEdgePhotos = "photos";
    readonly string _postToPageURL;
    readonly string _postToPagePhotosURL;

    public Facebook(string accessToken, string pageID)
    {
        _accessToken = accessToken;
        _pageID = pageID;
        _postToPageURL = $"{_facebookAPI}{pageID}/{_pageEdgeFeed}";
        _postToPagePhotosURL = $"{_facebookAPI}{pageID}/{_pageEdgePhotos}";
    }

    /// <summary>
    /// Publish a simple text post
    /// </summary>
    /// <returns>StatusCode and JSON response</returns>
    /// <param name="postText">Text for posting</param>
    public async Task<Tuple<int, string>> PublishSimplePost(string postText)
    {
        using (var http = new HttpClient())
        {
            var postData = new Dictionary<string, string> {
                { "access_token", _accessToken },
                { "message", postText }//,
                // { "formatting", "MARKDOWN" } // doesn't work
            };

            var httpResponse = await http.PostAsync(
                _postToPageURL,
                new FormUrlEncodedContent(postData)
                );
            var httpContent = await httpResponse.Content.ReadAsStringAsync();
            
            return new Tuple<int, string>(
                (int)httpResponse.StatusCode,
                httpContent
                );
        }
    }

    /// <summary>
    /// Publish a post to Facebook page
    /// </summary>
    /// <returns>Result</returns>
    /// <param name="postText">Post to publish</param>
    /// <param name="pictureURL">Post to publish</param>
    public string PublishToFacebook(string postText, string pictureURL)
    {
        try
        {
            // upload picture first
            var rezImage = Task.Run(async () =>
            {
                using (var http = new HttpClient())
                {
                    return await UploadPhoto(pictureURL);
                }
            });
            var rezImageJson = JObject.Parse(rezImage.Result.Item2);
            
            if (rezImage.Result.Item1 != 200)
            {
                try // return error from JSON
                {
                    return $"Error uploading photo to Facebook. {rezImageJson["error"]["message"].Value<string>()}";
                }
                catch (Exception ex) // return unknown error
                {
                    // log exception somewhere
                    return $"Unknown error uploading photo to Facebook. {ex.Message}";
                }
            }
            // get post ID from the response
            string postID = rezImageJson["post_id"].Value<string>();
            
            // and update this post (which is actually a photo) with your text
            var rezText = Task.Run(async () =>
            {
                using (var http = new HttpClient())
                {
                    return await UpdatePhotoWithPost(postID, postText);
                }
            });
            var rezTextJson = JObject.Parse(rezText.Result.Item2);
            
            if (rezText.Result.Item1 != 200)
            {
                try // return error from JSON
                {
                    return $"Error posting to Facebook. {rezTextJson["error"]["message"].Value<string>()}";
                }
                catch (Exception ex) // return unknown error
                {
                    // log exception somewhere
                    return $"Unknown error posting to Facebook. {ex.Message}";
                }
            }

            return "OK";
        }
        catch (Exception ex)
        {
            // log exception somewhere
            return $"Unknown error publishing post to Facebook. {ex.Message}";
        }
    }

    /// <summary>
    /// Upload a picture (photo)
    /// </summary>
    /// <returns>StatusCode and JSON response</returns>
    /// <param name="photoURL">URL of the picture to upload</param>
    public async Task<Tuple<int, string>> UploadPhoto(string photoURL)
    {
        using (var http = new HttpClient())
        {
            var postData = new Dictionary<string, string> {
                { "access_token", _accessToken },
                { "url", photoURL }
            };

            var httpResponse = await http.PostAsync(
                _postToPagePhotosURL,
                new FormUrlEncodedContent(postData)
                );
            var httpContent = await httpResponse.Content.ReadAsStringAsync();
            
            return new Tuple<int, string>(
                (int)httpResponse.StatusCode,
                httpContent
                );
        }
    }

    /// <summary>
    /// Update the uploaded picture (photo) with the given text
    /// </summary>
    /// <returns>StatusCode and JSON response</returns>
    /// <param name="postID">Post ID</param>
    /// <param name="postText">Text to add tp the post</param>
    public async Task<Tuple<int, string>> UpdatePhotoWithPost(string postID, string postText)
    {
        using (var http = new HttpClient())
        {
            var postData = new Dictionary<string, string> {
                { "access_token", _accessToken },
                { "message", postText }//,
                // { "formatting", "MARKDOWN" } // doesn't work
            };

            var httpResponse = await http.PostAsync(
                $"{_facebookAPI}{postID}",
                new FormUrlEncodedContent(postData)
                );
            var httpContent = await httpResponse.Content.ReadAsStringAsync();
            
            return new Tuple<int, string>(
                (int)httpResponse.StatusCode,
                httpContent
                );
        }
    }
}

And how to use it:

Facebook facebook = new Facebook("YOUR-PAGE-ACCESS-TOKEN", "YOUR-PAGE-ID");

// 1) if you want to publish a post with attached photo (actually, the other way around)
string result = facebook.PublishToFacebook("some text", "http://your.website/images/some.png");
Console.WriteLine(result);

// 2) if you want just to publish a simple text post
// var rezText = Task.Run(async () =>
// {
//     using (var http = new HttpClient())
//     {
//         return await facebook.PublishSimplePost("some another text");
//     }
// });
// var rezTextJson = JObject.Parse(rezText.Result.Item2);
// if (rezText.Result.Item1 != 200)
// {
//     try // return error from JSON
//     {
//         Console.WriteLine($"Error posting to Facebook. {rezTextJson["error"]["message"].Value<string>()}");
//         return;
//     }
//     catch (Exception ex) // return unknown error
//     {
//         // log exception somewhere
//         Console.WriteLine($"Unknown error posting to Facebook. {ex.Message}");
//         return;
//     }
// }
// Console.WriteLine(rezTextJson);

One more thing: right now your Facebook app is in development mode and is not publicly available. That means, all the posts you publish with it won’t be visible to anyone, except those whom you added into your app Roles (https://developers.facebook.com/apps/YOUR-APP/roles/). At first I didn’t know that, so I was totally freaking out, because I clearly saw my posts on the page under my account, while other users didn’t, for them it was an empty wall/timeline.

So, go to App Review (https://developers.facebook.com/apps/YOUR-APP/review-status/) section and switch “make it public” button. You don’t need to submit your app for approval, just make it public:

Facebook app public

After that all your posts you made while your app was in development mode will become visible to everyone (and all the future posts too, of course).


Updates

2017-11-26 | More information about Facebook Login

Having started to work on integration with Instagram API, I suddenly understood Facebook Login mechanism better (thanks to the Instagram documentation).

Turns out, you can actually make Facebook Login work. But you anyway cannot achieve it by using only HTTP requests from your server - for the initial step you will anyway need a browser, where user (you) will click on buttons in the Facebook login dialog.

And for that you first need to register a redirect URI in your app settings. Don’t let this redirect URI scare you, it’s nothing biggie - you can specify any address there, even http://example.org, like I did:

Facebook app login

After that, according to the information from this page, you need to open the following URL in browser:

https://www.facebook.com/dialog/oauth?client_id=YOUR-ADD-ID&redirect_uri=http://example.org/

Pay attention to the trailing slash (/), because redirect URI should be exactly the same as you registered it in your app settings.

Okay, you opened the URL, it showed you Facebook login dialog, and after that redirected you to the specified http://example.org/ and put a code parameter in the URL - check the address bar in your browser:

Facebook login redirect

Now documentation says to send this request (this one you can send from code):

// ...
https://graph.facebook.com/oauth/access_token?client_id=YOUR-APP-ID&redirect_uri=http://example.org/&client_secret=YOUR-APP-SECRET&code=THE-CODE-YOU-JUST-GOT-FROM-BROWSER
// ...

Again, pay attention to the trailing slash (/)! If you’ll miss it, response will be the following:

{
  "error": {
    "message": "Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request",
    "type": "OAuthException",
    "code": 100,
    "fbtrace_id": "some"
  }
}

And if you formed request right, you’ll get this response:

{
  "access_token": "USER-ACCESS-TOKEN",
  "token_type": "bearer"
}

And that’s the User Access Token (already extended, I believe) you need for this step (if it’s not an extended token, then for this step).

BUT.

Documentation has fucked you up again Default login URL only gives you default permissions (such as viewing public profile). So, trying to proceed and get Page Access Token will give you this response:

{
  "data": []
}

Meaning, that your app has no permissions to do anything on any page.

So, if you need more permissions (publishing as page, for instance), you need to add scope parameter to the login URL, like this:

https://www.facebook.com/v2.11/dialog/oauth?client_id=YOUR-ADD-ID&redirect_uri=http://example.org/&scope=manage_pages,publish_pages

Remember, this is an URL you need to open in browser (to get a Facebook login dialog).

And here’s the final surprise - you cannot ask for permissions such as manage_pages and publish_pages for an un-reviewed app using login dialog. Of course, if you were anyway going to submit your app for reviewing, then it should not be a problem for you. Meanwhile, that’s what you get:

Facebook unreviewed app permissions

…So, I’ll stick to the Graph API Explorer method.

2018-08-01 | Mandatory App Reviews

So, from today Facebook requires all the apps to go through the App Review.

I knew it was coming, but I wasn’t worrying about that, because all 3 warnings I received were saying the following:

If your app is not submitted for review, you will lose access to any permissions or features requiring review, including these:
  • user_friends
  • user_link
  • user_gender
  • user_age_range

I am not using any of those (neither did I request any of those), so I thought I’m fine.

But today I got the following error in my requests:

The permissions manage_pages,publish_pages are not available. It could because either they are deprecated or need to be approved by App Review

Nice surprise, thank you, Facebook.

I went to the application page, and suddenly discovered that my app does have user_friends permission, because it was granted automatically:

Facebook default permissions

Okay, let’s try to submit our app for review then:

Facebook manage and publish permissions

Oh, so manage_pages and publish_pages are also the kind of permissions that require passing App Review.

Okay, let’s check what sort of “additional information” in needed there:

Facebook no approve for publishing

Ha-ha, 2nd and 3rd options (let’s not count other) say that “Apps won’t be approved for this use of publish_page”, so you can only go through the 1st one, which has some ridiculous requirements, considering the actual use-case - automatic content posting from a website to a public Facebook page. I mean, my app isn’t even supposed to be used by anyone but me, and I used it only once myself, when I was getting the API key.

Perhaps, such usage of the Facebook API was not allowed from the beginning, I don’t know (and I didn’t read the rules). But if Facebook doesn’t want me to deliver new content to their wretched network, so be it.

2018-08-03 | Sudden rollback

Today I suddenly discovered that my requests to Facebook API are working again, even though I haven’t done anything and most definitely did not submit my app for review.

I went to Facebook Developer Community to see if it’s not only me, and discovered that Facebook got a lot of angry posts from developers towards the whole revoking access and mandatory App Review thing.

These guys, for instance, almost got their business destroyed (and they still might):

Facebook has fucked some business

And this one is fairly loosing his shit over the fact that he has to demonstrate his server-to-server use-case somehow:

Facebook App Review for a server-to-server interaction

Another example:

Facebook App Review for a server-to-server interaction

Seriously, how does one suppose to show a reviewer the “improvement of user experience”? I am that user, it’s for me only! But as I said, looks like such usage of API was not really allowed in the first place.

But anyway, my guess is that Facebook realized how big it did fuck up (way too many unhappy developers) and rollbacked the access revocation. I saw others saying the same thing - that they just got the access back without doing anything.

Well, it’s good that requests are working again, but who knows what happens next. I won’t be surprised if they will revoke the access again later on.

А вот ещё:

Урюпинская епархия работает с API

Какая продвинутая обитель, общается с паствой прямо в бездуховных соцсетях.

2019-02-01 | Yet another brief permissions issues

Today I started getting this error again:

The permission(s) manage_pages,publish_pages are not available. It could because either they are deprecated or need to be approved by App Review.

And just like the last time is miraculously disappeared by itself in a day or so. Classic Failsbook.

2019-03-01 | Forced App Review

Today I got the following e-mail from the Facebook:

Your app was proactively submitted for App Review

As communicated on August 1, 2018, APP-NAME was proactively submitted for App Review, and is now in a queue awaiting review. While your app is pending review, you can make updates to ensure the accuracy of your request. For example, you can edit your app settings and permission details. However, please note that you will not be able to cancel or remove permissions from your request prior to review. Additionally, due to increased volumes, we are unable to provide you with an anticipated review date. As a result, please make any updates to your submission as soon as possible to ensure the App Review team reviews the most up-to-date information.

You can view this and other Developer Notifications related to your app, in the App Dashboard.

Yeah, whatever. I tried submitting my app for the review myself, but that didn’t work out that well, so.

2019-03-06 | Unexpected App Review results

So today I suddenly received the following results from the recent forced App Review:

Your app review has been completed.

The review of APP-NAME has been completed. For more detail on the review please visit the App Review tab.

I went there and discovered several failed permissions I never asked for: user_gender, user_friends, user_age_range and others, and every one of those had a question “tell us how you’ll use…”. But I never requested those in the first place, you dumb twats. And the ones I did request (manage_pages, publish_pages) have just disappeared. Fucking piece of shit of fucking pieces of shits!

But wait, my application still works fine, even though the App Review didn’t go that well as it seems. So what has changed, and/or what is the point of this stupid App Review then? Because I didn’t change a single thing about my app, it remained untouched for all this time since I created it more than a year ago. So all of the sudden it is okay now? Oh wow, thanks a bunch, what a useful process.

And I reckon, the same happened with all the data-harvesting apps you had a hearing about at the Senate, so they will just continue to function like before. Well done, Failsbook, that’s just grand.

2019-03-29 | Another notification

Today Facebook sent me the following:

Hi,

Access to certain permissions or features expires on Apr 28, 2019

You can view this and other Developer Notifications related to your app, in the App Dashboard.

Thanks, The Facebook Team

I could care less up to this point, to be honest. If Failsbook will disable my posting application, I’ll just ditch it for good.

2019-09-23 | App Review required

Oh, lookie who’s here! Long time no see, but today I got another e-mail from Failsbook:

App Review required to retain access to specific permissions or features

As a result of changes to our platform, APP-NAME will need to submit an App Review request by October 23, 2019 to retain access to platform data for the following permissions or features: publish_pages, manage_pages

If APP-NAME doesn't submit for App Review by the deadline, it may impact how it accesses data.

If you are currently in Dev Mode, you will also need to switch your app to Live Mode after successfully completing App Review to maintain your current functionality.

Yeah, how about fuck you. I ain’t gonna do nothing, because from my experience ain’t nothing is going to happen.

2019-10-22 | Deadline extended

Hello, retard police? Failsbook is at it again. Here’s a new e-mail from them:

Deadline extended for platform changes

You were recently notified about actions required by October 23, 2019 for APP-NAME to maintain the current level of access and functionality. We are reevaluating out timeline and will delay enforcement until early 2020.

While no specific date has been set, we still recommend that you submit for App Review if necessary, switch all apps to Live Mode for production use and complete business or individual verification.

Deadline is extended, my ass. What did I tell you? Nothing is going to happen, and nothing did. And nothing will. What a fucking nonsense. Looking forward to “early 2020” with a great excitement.

2020-02-03 | Error validating access token

Oh wow, Falsebook has actually done something! Now my requests fail with an error:

Error validating access token: The user has not authorized application

It only took them 1.5 years.

It actually might be something not related, but Foicebook can go fuck itself an anyway, because I have no desire to waste any more time on this piece of shit. Good thing we didn’t really need it in the first place.