C# / .NET Core, publish to Facebook
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. Потому что это, блядь, нечто.
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
.
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?
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:
Now click on Get User Access Token
:
A dialog window with lots of checkboxes will appear. Check these two: manage_pages
and publish_pages
.
Generated token will appear in the Access Token
field. It’s a short-term one. Here you can check when it expires:
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:
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:
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;
Page Access Token
- we got it on the previous step;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):
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:
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).
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:
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:
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:
…So, I’ll stick to the Graph API Explorer method.
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:
Okay, let’s try to submit our app for review then:
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:
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.
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):
And this one is fairly loosing his shit over the fact that he has to demonstrate his server-to-server use-case somehow:
Another example:
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.
А вот ещё:
Какая продвинутая обитель, общается с паствой прямо в бездуховных соцсетях.
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.
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.
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.
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.
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.
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.
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.
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks