Post from python to facebook timeline

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.

Verified environment

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

Source code

https://github.com/nmatsui/post_facebook_using_python

Create facebook app

First, create a facebook app.

1 Display "Add A New App" from the facebook developer portal https://developers.facebook.com/ and select "Website".

fb_python_01.png

2 Enter the name of the facebook app you want to create (this time ** python_publisher **) and press "Create New Facebook App ID"

fb_python_02.png

3 Select a category (this time ** Utility **) and press "Create App ID"

fb_python_03.png

4 Enter the URL for the authentication callback with OAuth (this time ** http: // localhost: 8000 / cgi-bin / get_token **) and press "Next".

fb_python_04.png

5 Completion of facebook application creation

fb_python_05.png

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

fb_python_06.png

7 Check App ID and App Secret

You may be prompted for a password when checking the App Secret

fb_python_07.png

Creating a configuration file

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"
  }
}

Environmental preparation

Now that we have a facebook app, we need to prepare a python environment.

Library installation

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

Creating a directory for CGI

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

Creating a CGI program

This time we will create the following two CGIs

Privileges given to access tokens

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.

OAuth authentication URL generation CGI

** 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 () `.

OAuth authentication URL display template

** 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.

Access token acquisition CGI

** 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.

Access token display template

** 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>

CGI program operation and access token acquisition

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

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 index

Access the following URL from the browser of the PC on which CGIHTTPServer is started.

http://localhost:8000/cgi-bin/index

fb_python_08.png

Authenticated with OAuth

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.

fb_python_09.png

2 Check the posting range Specify the disclosure range that the facebook application posts on your behalf.

fb_python_10.png

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.

fb_python_11.png

Obtaining and displaying access tokens

CGI gets the access token, saves it in token.json, and displays it on the screen as well.

fb_python_12.png

token.json


{
  "token": {
    "page_access": "Obtained Page Access Token", 
    "user_access": "Obtained User Acdess Token"
  }
}

Post to facebook

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.

python script

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)

Post to your timeline

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.

facebook_python_12.png

Posting to the timeline on facebook page

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.

fb_python_13.png

Finally

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

Post from python to facebook timeline
Post from Python to Slack
[Lambda] [Python] Post to Twitter from Lambda!
Post images from Python to Tumblr
Changes from Python 3.0 to Python 3.5
Changes from Python 2 to Python 3.0
Post to vim → Python → Slack
Cheating from PHP to Python
Post to slack with Python 3
Anaconda updated from 4.2.0 to 4.3.0 (python3.5 updated to python3.6)
Post to Twitter using Python
Switch from python2.7 to python3.6 (centos7)
Connect to sqlite from python
Post to Slack in Python
POST messages from python to Slack via incoming webhook
Create folders from '01' to '12' with python
Try to operate Facebook with Python
Connect to utf8mb4 database from python
Python (from first time to execution)
How to access wikipedia from python
Python to switch from another language
Easily post to twitter with Python 3
[Nanonets] How to post Memo [Python]
Did not change from Python 2 to 3
Update Python on Mac from 2 to 3
Post a message from IBM Cloud Functions to Slack in Python
[Python] Fluid simulation: From linear to non-linear
From Python to using MeCab (and CaboCha)
How to update Google Sheets from Python
Send a message from Python to Slack
Private Python handbook (updated from time to time)
I want to use jar from python
Convert from katakana to vowel kana [python]
Push notification from Python server to Android
Connecting from python to MySQL on CentOS 6.4
Porting and modifying doublet-solver from python2 to python3.
How to access RDS from Lambda (python)
Python> Output numbers from 1 to 100, 501 to 600> For csv
Convert from Markdown to HTML in Python
[Amazon Linux] Switching from Python 2 series to Python 3 series
API explanation to touch mastodon from python
Connect to coincheck's Websocket API from Python
Updated to Python 2.7.9
Sum from 1 to 10
sql from python
MeCab from Python
"Backport" to python 2
Send a message from Slack to a Python server
Edit Excel from Python to create a PivotTable
How to open a web browser from python
Study from Python Hour7: How to use classes
[Python] Convert from DICOM to PNG or CSV
Import Excel file from Python (register to DB)
I want to email from Gmail using Python.
[Python] I want to manage 7DaysToDie from Discord! 1/3
From file to graph drawing in Python. Elementary elementary
[First post] Question ・ How to sweep out python
[Python] How to read data from CIFAR-10 and CIFAR-100
[python] Create table from pandas DataFrame to postgres
[Bash] Use here-documents to get python power from bash
How to generate a Python object from JSON