How To Use Python To Interact With Steemconnect's Access Tokens, Refresh Tokens And Perform Voting

in utopian-io •  4 months ago

banner.png

This tutorial will explain how Steemconnect and Python can be used to build a back-end application that is used to perform voting.


Repository

https://github.com/steemit/steemconnect
https://github.com/emre/steemconnect-python-client

What will I learn

  • Storing access tokens
  • Retrieving access token en refreshing
  • Voting and response
  • Error handling

Requirements

  • Python3.6
  • MySQL
  • Steemconnect

Difficulty

  • intermediate

Tutorial

Preface

Python is a great language to build back-end applications that work great in conjunction with a front-end that is for example build with a framework like WordPress. This tutorial will look into how voting can be done by fetching access_tokens from a MySQL database. There are 3 files that are not set up to do anything in particular but include all the functions and database table structure to do so. Github link.

Install Steemconnect for Python

A splendid Steemconnect written for Python by @emrebeyler.

pip3 install steemconnect

Setting up your Steemconnect app

This tutorial requires access to a Steemconnect app. It is somewhat a continuation on the Steemconnect & WordPress tutorials 1 and 2. However, they are not required for this tutorial.

All Steemconnect actions are performed via the Client object. It takes the client_id and client_secret from your Steemconnect app. In addition it requires the access_token for the account that actions will be performed for. A separate Client is needed for each account, or the access_token has to be changed.

c = Client(
        client_id=CLIENT_ID,
        client_secret=CLIENT_SECRET,
        access_token=access_token,
    )

The client_id and client_secret are declared outside of the function. This is also true for the database variables. They have to be configured for the code to work.

Storing access tokens

For the storing of the access tokens a MySQL database is used that has the following properties. This tutorial assumes that there is already a solution in place to obtain the initial access tokens and will look into how they can be used afterwards.

CREATE TABLE `steem_authorization` (
  `id` int(11) NOT NULL,
  `access_token` text NOT NULL,
  `user_login` varchar(20) NOT NULL,
  `expires_in` datetime NOT NULL,
  `refresh_token` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `steem_authorization`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `user_login` (`user_login`);

ALTER TABLE `steem_authorization`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

There are 4 main attributes. The access_token itself, the user_login, which STEEM account is belongs to, expires_in, this is calculated when the access_token is created. And the refresh_token, this is given when offline access is requested and is used to refresh the access token when it expires.

Retrieving access tokens and refreshing

The query is relatively simple as there should only be 1 access_token per user in the database. If this is not the case the updating and or registering for the access_token is not dealt with properly. get_data() fetches all the rows and return them.

def get_user_auth(self, voter):
    query = ("SELECT `access_token`,`refresh_token`,`expires_in` FROM " +
             f"`steem_authorization` WHERE `user_login` = '{voter}'")
    return self.get_data(query)



The access token is fetched from the database with its relevant data. The next step is to verify that the access_token is still valid, if not it must be renewed. Note: This is only possible if the user gave authorisation for offline access.

result = self.db.get_user_auth(voter)
access_token, refresh_token, expire_on = result[0]
dt = datetime.now()



The current date is referenced against the date when the token expires. The access_token is then refreshed by calling the refresh_access_token() function inside Client. The new variables are then stored in the database.

# Verify access_token
if dt > expire_on:
    result = c.refresh_access_token(
                refresh_token,
                "login,vote"  # scopes
    )

# Retrieve new variables and store in DB
access_token = result['access_token']
refresh_token = result['refresh_token']
expires_in = result['expires_in']
self.db.update_authentication_tokens(
    voter,
    access_token,
    refresh_token,
    expires_in,
    self.timestamp,
)



The current timestamp is used to calculate the new date when the new access_token expires.

def update_authentication_tokens(self, voter, access_token, refresh_token,
                                 expires_in, timestamp):
    dt = datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S')
    expires_in = dt + timedelta(seconds=expires_in)

    query = ("UPDATE `steem_authorization` SET `access_token` = " +
             f"'{access_token}', `expires_in` = '{expires_in}', " +
             f"`refresh_token` = '{refresh_token}' WHERE " +
             f"`user_login` = '{voter}';")
    self.post_data(query, 'steem_authorization')

Voting and response

Voting is relatively simple as it just requires the parameters that make up the vote.

# Perform vote
vote = Vote(voter, author, permlink, weight)



The vote will return a response, if it succeeds or fails.

result = c.broadcast([vote.to_operation_structure()])



A successful example:

{
    'result': {
        'id': 'b0e79cc31de3d248d7a90406edadf9dc3e979e14',
        'block_num': 24673233,
        'trx_num': 21,
        'expired': False,
        'ref_block_num': 31679,
        'ref_block_prefix': 3139963426,
        'expiration': '2018-08-01T01:57:57',
        'operations': [
            ['vote', {
                'voter': 'juliank',
                'author': 'axeman',
                'permlink': 'girls-n-cars-greta-vs-mb',
                'weight': 1000
            }]
        ],
        'extensions': [],
        'signatures': ['1f7b83bba9177a8f5c980e6185f7546134554ae6229363a1ef2986bdb29ba60d0b5b7e3cf6975e8d3d955a3274b2b8cbf526fd42b1b2f88ed0167c45296a50d15a']
    }
}

An example of an error:

{
    'error': 'server_error',
    'error_description': 'itr->vote_percent != o.weight: You have already voted in a similar way.'
}

Error handling

Error handling is done by just looking if there is an error key in the result.

if 'error' in result:
   message = result['error_description']
   print(message)
else:
   message = 'Succes'
   print(message)



It is recommend to capture the error message and store this inside the database for manual review. This way the application can continue.

self.db.add_to_error_log(
    voter, author, permlink, weight,
    message, self.timestamp,

def add_to_error_log(self, voter, author, permlink, weight, message,
                     timestamp):
    query = (
        'INSERT INTO `error_log` (`id`, `voter`, `author`, ' +
        '`permlink`, `weight`, `error`, `timestamp`) VALUES ' +
        f'(NULL, "{voter}", "{author}", "{permlink}", ' +
        f'"{weight}", "{message}", "{timestamp}");')
    self.post_data(query, 'error_log')

The files are available on Github.

This tutorial was written by @juliank.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thank you for your contribution.

  • As always an interesting tutorial. Thank you and good work.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Exactly what I needed to play with right now. Thanks a lot for this.

However, can we get steemconnect and js version of this (if there is)? That will be also interesting to read (maybe even more interesting).

·

You can look around, maybe somebody else hopped on that bandwagon. I barely know js myself so I cannot help you with that. However, the idea stays the same, just different syntax.

Hey @steempytutorials
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!