Post from python to yourself or the timeline on your facebook page.
Unfortunately, facebook doesn't have an incoming webhook mechanism, so you have to create a facebook app for posting.
Also, an access token is required to operate facebook, but it is difficult to obtain an access token from CLI because facebook requires OAuth authentication (although it is not impossible if you analyze the response using a headless browser).
So, this time, I will start the CGI server with python on localhost and process the OAuth callback with CGI to get the access token.
I wanted to be able to post to the facebook group, but unfortunately I gave up this time because it seems that the facebook operator needs to approve the facebook application.
version | |
---|---|
OS | Ubuntu 15.04 (3.19.0-26-generic) |
Python | 2.7.10 |
pip | 7.1.2 |
requests | 2.8.1 |
jinja2 | 2.8 |
facebook-sdk | 0.4.0 |
facebook graph API | 2.5 |
https://github.com/nmatsui/post_facebook_using_python
First, create a facebook app.
1 Display "Add A New App" from the facebook developer portal https://developers.facebook.com/ and select "Website".
2 Enter the name of the facebook app you want to create (this time ** python_publisher **) and press "Create New Facebook App ID"
3 Select a category (this time ** Utility **) and press "Create App ID"
4 Enter the URL for the authentication callback with OAuth (this time ** http: // localhost: 8000 / cgi-bin / get_token **) and press "Next".
5 Completion of facebook application creation
6 Display the Top Page of the developer portal again and select the app created from "My Apps".
If you can't find the app you created, reload the developer portal
7 Check App ID and App Secret
You may be prompted for a password when checking the App Secret
Create a configuration file that describes the information of the created facebook application and the information of the facebook page to be posted. For the convenience of CGI programs, the configuration file name is fixed as ** conf.json **.
conf.json
{
"app": {
"app_id": "facebook app app ID",
"app_secret": "facebook app App Secret",
"redirect_uri": "Callback URL set in facebook app"
},
"page": {
"name": "The name of the facebook page to post"
}
}
Now that we have a facebook app, we need to prepare a python environment.
Create requirements.txt in the same location as conf.json, and install the following 3 libraries using pip.
requirements.txt
requests
jinja2
facebook-sdk
Install with pip
$ pip install -r requirements.txt
Place the CGI program in the directory where conf.json is placed ** Create the cgi-bin ** directory and the jinja2 template ** templates ** directory
Directory creation
$ mkdir cgi-bin
$ mkdir templates
This time we will create the following two CGIs
This time, specify the following three authority ranges. See Permissions Reference --Facebook Login for details on the permissions that can be specified.
Since the access token is given ** public_profile ** authority (authority to acquire public profile) by default, the access token acquired this time is allowed to operate within these four authority ranges.
index Generate an OAuth authentication URL for the facebook app and display the link in your browser.
** cgi-bin / index ** reads conf.json and generates OAuth authentication URL in the following format.
https://www.facebook.com/dialog/oauth?redirect_uri= <Callback URL set for facebook app & client_id = <App ID of facebook app> & scope = <Authority to grant approval>
cgi-bin/index
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import urllib
from jinja2 import Environment, FileSystemLoader
CONF_FILE = 'conf.json'
BASE_URL = 'https://www.facebook.com/dialog/oauth'
SCOPE = 'manage_pages,publish_actions,publish_pages'
TPL_DIR = './templates'
TEMPLATE = 'index.tpl.html'
def create_url():
with open(CONF_FILE, 'r') as f:
conf = json.load(f)
redirect_uri = urllib.quote_plus(conf['app']['redirect_uri'])
url = BASE_URL + '?'
url += 'redirect_uri=' + redirect_uri + '&'
url += 'client_id=' + conf['app']['app_id'] + '&'
url += 'scope=' + SCOPE
return url
def main():
params = {}
try:
url = create_url()
params['isOK'] = True
params['url'] = url
except Exception as e:
params['isOK'] = False
params['error_type'] = type(e).__name__
params['error_title'] = str(e)
env = Environment(loader=FileSystemLoader(TPL_DIR, encoding='utf-8'))
tpl = env.get_template(TEMPLATE)
html = tpl.render(params)
print('Content-type: text/html')
print('\n')
print(html.encode('utf-8'))
main()
Since the callback URL cannot be passed as a URL parameter as it is, it is escaped with ʻurllib.quote_plus () `.
** templates / index.tpl.html ** displays the generated URL as a link.
html+jinja:templates/index.tpl.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>index</title>
</head>
<body>
{% if isOK %}
<a href="{{ url }}">get token</a>
{% else %}
<b>The following error occurred</b><br/>
[{{ error_type }}] {{ error_title }}
{% endif %}
</body>
</html>
get_token Call back from the OAuth authentication function of facebook and get the authentication code. Obtain the following access token from facebook API using the authentication code and save it as a json file.
** cgi-bin / get_token ** uses the called back authorization code to get the Page Access Token of the facebook page specified as the User Access Token. The acquired access token is saved in token.json.
cgi-bin/get_token
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cgi
import json
import re
import requests
from jinja2 import Environment, FileSystemLoader
CONF_FILE = 'conf.json'
TOKEN_FILE = 'token.json'
TOKEN_URL = 'https://graph.facebook.com/oauth/access_token'
ACCOUNT_URL = 'https://graph.facebook.com/me/accounts'
USER_ACCESS_TOKEN_PATTERN = r'access_token=([^&=]+)(&expires=\d+)?'
TPL_DIR = './templates'
TEMPLATE = 'get_token.tpl.html'
class TokenRetriever(object):
def __init__(self, code):
self.code = code
with open(CONF_FILE, 'r') as f:
self.conf = json.load(f)
def get_token(self):
user_access_token = self.__get_user_access_token()
page_access_token = self.__get_page_access_token(user_access_token)
token = {}
token['user_access'] = user_access_token
token['page_access'] = page_access_token
token_json = json.dumps({'token': token}, indent=2, sort_keys=True)
return token_json
def __get_user_access_token(self):
payload = {}
payload['client_id'] = self.conf['app']['app_id']
payload['client_secret'] = self.conf['app']['app_secret']
payload['redirect_uri'] = self.conf['app']['redirect_uri']
payload['code'] = self.code
response = requests.get(TOKEN_URL, params=payload)
m = re.match(USER_ACCESS_TOKEN_PATTERN, response.text)
if m:
return self.__exchange_token(m.group(1))
else:
raise LookupError('access_token does not exist')
def __get_page_access_token(self, user_access_token):
payload = {}
payload['access_token'] = user_access_token
response = requests.get(ACCOUNT_URL, params=payload)
pages = filter(lambda p: p['name'] == self.conf['page']['name'],
json.loads(response.text)['data'])
page_access_token = pages[0]['access_token']
return self.__exchange_token(page_access_token)
def __exchange_token(self, token):
payload = {}
payload['client_id'] = self.conf['app']['app_id']
payload['client_secret'] = self.conf['app']['app_secret']
payload['grant_type'] = 'fb_exchange_token'
payload['fb_exchange_token'] = token
response = requests.get(TOKEN_URL, params=payload)
m = re.match(USER_ACCESS_TOKEN_PATTERN, response.text)
if m:
return m.group(1)
else:
raise LookupError('access_token does not exist')
def main():
params = {}
try:
form = cgi.FieldStorage()
if not form.has_key('code'):
raise LookupError('QueryString "code" does not exist')
token_retriever = TokenRetriever(form['code'].value)
token_json = token_retriever.get_token()
with open(TOKEN_FILE, 'w') as f:
f.write(token_json)
params['isOK'] = True
params['token_file'] = TOKEN_FILE
params['token_json'] = token_json
except Exception as e:
params['isOK'] = False
params['error_type'] = type(e).__name__
params['error_title'] = str(e)
env = Environment(loader=FileSystemLoader(TPL_DIR, encoding='utf-8'))
tpl = env.get_template(TEMPLATE)
html = tpl.render(params)
print('Content-type: text/html; charset=utf-8')
print('\n')
print(html.encode('utf-8'))
main()
The requests
library is used to use the REST API of facebook. The Facebook User Access Token usually expires within 1 to 2 hours after it is acquired. That was a hassle to test, so I used the access token extension REST API to exchange for tokens that are valid for 60 days.
** templates / get_token.tpl.html ** displays the acquired access token.
html+jinja:templates/get_token.tpl.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>get_token</title>
</head>
<body>
{% if isOK %}
<b>User Access Token</b>When<b>Page Access Token</b>I got.</br>
Tokens are in the following JSON format{{ token_file }}I wrote it to.
<pre>{{ token_json }}</pre>
{% else %}
<b>The following error has occurred:</b><br/>
[{{ error_type }}] {{ error_title }}
{% endif %}
</body>
</html>
Now, let's run CGI and get an access token. Since this is a verification, httpd such as nginx and apache is not used, and python2.7 CGIHTTPServer
is used instead.
Start CGI HTTP Server with the following command from the directory where the conf.json, cgi-bin directory, and templates directory are located.
Start CGI HTTP Server
$ python -m CGIHTTPServer
Access the following URL from the browser of the PC on which CGIHTTPServer is started.
http://localhost:8000/cgi-bin/index
Clicking get_token
will authenticate the facebook app and ask you to approve the permissions.
1 Checking the authority given to the user Check the authority to approve. Since this facebook app is not approved, a warning is displayed that some of the permissions such as writing to the group have been invalidated.
2 Check the posting range Specify the disclosure range that the facebook application posts on your behalf.
3 Permission check on facebook page Check the management authority and posting authority of facebook page. There is no warning about posting to facebook pages even for unapproved apps.
CGI gets the access token, saves it in token.json
, and displays it on the screen as well.
token.json
{
"token": {
"page_access": "Obtained Page Access Token",
"user_access": "Obtained User Acdess Token"
}
}
Now that I have an access token, I will post it from python to facebook. You can directly operate the REST API of facebook, but this time we will use Facebook SDK for Python.
Get yourself and the endpoint of the facebook page using the SDK and access token and write a message. Note that the write method is different between your timeline and the facebook page timeline.
post.py
#!/usr/bin/env python
# -*- encode: utf-8 -*-
import sys
import json
import facebook
class Timeline:
def __init__(self, token_file):
with open(token_file, 'r') as f:
token = json.load(f)['token']
self.user_endpoint = facebook.GraphAPI(token['user_access'])
self.page_endpoint = facebook.GraphAPI(token['page_access'])
def post_me(self, msg):
self.user_endpoint.put_object('me', 'feed', message=msg)
print('posted to my timeline: %s' % msg)
def post_page(self, msg):
self.page_endpoint.put_wall_post(message=msg)
print('posted to page timeline: %s' % msg)
if __name__ == '__main__':
if len(sys.argv) != 4:
print('usage: %s token_file [me|page] message' % sys.argv[0])
exit(1)
try:
timeline = Timeline(sys.argv[1])
if sys.argv[2] == 'me':
timeline.post_me(sys.argv[3])
elif sys.argv[2] == 'page':
timeline.post_page(sys.argv[3])
else:
print '%s is invalid' % sys.argv[2]
except (IOError, facebook.GraphAPIError) as e:
print e
exit(9)
Try posting to your timeline.
Post to your timeline
$ ./post.py token.json me "Test post. This is a test of a post from a python script."
posted to my timeline:Test post. This is a test of a post from a python script.
I will post it to the timeline of the facebook page created for this experiment.
$ ./post.py token.json page "Test post. This is a test of a post from a python script."
posted to page timeline:Test post. This is a test of a post from a python script.
I just wanted to post from python to facebook, but it was a very long way, but I was able to post safely to my timeline and the timeline on the facebook page. Will facebook also prepare an incoming webhook?
Recommended Posts