Bitcoin Instagram

Posted by Balaji Srinivasan

Bitcoin Instagram

This tutorial follows up from the Introduction to Bitcoin and Introduction to Bitcoin Computing tutorials. You will learn how to create a simple "user-monetized content" site, in which you create a library of content that you own and then sell that content for bitcoin to your peers. We illustrate this concept by showing you how to automatically download your photos from instagram.com and set up a simple stock photography repository, payable in bitcoin.

Note: this tutorial requires a 21 Bitcoin Computer.

Motivation: the concept of user-monetized data

Billions of photos are now taken every day, and millions of them get uploaded to photo sharing sites like Instagram. Some of those photos are really good---the kind of photos businesses would pay to use in their marketing or their products.

Indeed, there's a fast-growing market for "stock photography"---images that can be searched and purchased (or licensed) by anyone needing a nice image for a particular task. But social sharing sites such as Instagram don't give you the ability to sell your photos to the thousands (possibly millions) of businesses and people who want to buy them.

Why not? One reason is that Instagram actually did try to do this in the past, during a 2012 update to its terms of service:

You agree that a business may pay Instagram to display your photos in connection with paid or sponsored content or promotions without any compensation to you.

2012 Instagram Terms of Service change (since reverted)

This wasn’t too popular, and Instagram reverted it. One hypothesis is that the key problem was that Instagram was going to sell photos without (a) requiring your explicit consent or (b) giving you a cut.

However, if Instagram had succeeded in monetizing their user's photography, they could well have destroyed every other company in the stock photography business. Just look at the resources they could draw upon: a recent September 2015 press release from Instagram contains the following impressive statistics, among others:

  • "400 million members"
  • "80 million photos per day shared"

Many of those 80 million photos are probably only interesting to the people sharing them, but even if only a fraction are saleable, that still leaves hundreds of thousands or millions of valuable photos sitting fallow today. And, interestingly, for many marketing purposes, “lower-quality” authentic-looking photosthe kind that get taken by everyday peopleare worth more than photos created by professionals.

Everyday people also have another advantage that professional photographers don't have---they're everywhere. Stock photography is just one potential market; another possible market is selling photographs of recent events to news organizations. If you think about it, almost anyone who pays for photographs from professionals today could probably benefit from buying photographs from Instagram users.

What Bitcoin brings to the table is a programmatic way of doing small, fast, international payouts to anyone on the internet. If you can take a photo on Instagram, you can get paid for it in Bitcoin.

So in this lab we’re going to explore the hypothesis: what would a social media site like Instagram look like if users could consent to and profit from the sale of their photos? What does user-monetized data look like? Let's take a look by exploring how to create a bitcoin-monetized version of Instagram.

Bitcoin Instagram: Walkthrough

In this lab, you'll do the following steps:

  • First, download your Instagram content.

  • Then run a Bitcoin-payable server so that customers can browse your photos

  • Next, get a customer to browse your gallery using their laptop (you will both need to be on the same Zerotier network)

  • The customer will see watermarked photos and will be able to buy full resolution images from their Bitcoin Computer

Here’s how that looks when organized as a timeline:

Bitcoin Instagram timeline

Set up Instagram

If you don't have an Instagram account, download the app (Apple / Android), create an account, and upload a few photos that you own the rights to (go ahead and take a picture of something with your phone if you want!).

Clone and run the Bitcoin Instagram example

We’ve made running the Bitcoin Instagram example very easy:

git clone https://github.com/21dotco/bitcoin-instagram.git
cd bitcoin-instagram
bash setup.sh <your_Instagram_username>

That will install all the necessary dependencies, prompt you for your Instagram password, download your photos, and start the server for selling for them. When it starts, it will print a message like this:

The files that you're selling: http://10.244.103.196:5000/instagram
The files that you've purchased: http://10.244.103.196:5000/instagram/purchased
Default username/password for purchased files: admin/default

Feel free to share the first URL in our Slack channel and visit each other's websites. Tell visitors to click on an image you want to buy; the instructions to purchase will be displayed in a modal pop-up. Here’s how that will look:

Gallery view

Order view

You can run the instructions from the command line of a 21 Bitcoin Computer or any device running 21

python3 ~/instagram_client.py 7 1161765487700275046.jpg http://10.244.204.2:5000

Here’s how that output will look:

Command line

After you've purchased an image, you can visit your purchased page (the exact URL was printed at the end of the setup.sh output) to see the image.

Awesome! You just downloaded some photos and bought/sold some for bitcoin with your classmates. Let’s understand how each bit of code works.

Code review: Instagram scraper

In the repository you checked out, the first program that gets run is the Instagram Scraper, instagram.js. Javascript (node.js) was chosen for this task since it has to interact with the Instagram website.

Like most scrapers, the code just walks through what would otherwise be a progression of screens on the Instagram website:

var make_IG_request = function(jar) {
  return function(method, url, form_data, callback) {
    var opts = {
      method: method,
      url: url,
      jar: jar,
      headers: {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
        "Referer": "https://www.instagram.com/",
      },
    };
    if (form_data) {
      if (method == "GET")
        opts.qs = form_data
      else {
        opts.headers["X-Instagram-AJAX"] = 1;
        opts.headers["X-Requested-With"] = "XMLHttpRequest";
        var csrftoken = findCookie(jar, url, "csrftoken");
        if (csrftoken) opts.headers["X-CSRFToken"] = csrftoken;
        opts.form = form_data;
      }
    }
    request(opts, function(error, response, body) {
      if (!error && response.statusCode == 200) {
        callback(null, body);
      } else {
        if (error) {
          console.log(error);
          callback(error);
        } else {
          console.log(response.statusCode);
          callback("Status " + response.statusCode);
        }
      }
    });
  };
};

Here we see the meat of the program, a function designed to make requests to Instagram. If you want, you can give it more authenticity by setting the user agent to the same one you usually use to login to Instagram from a computer.

console.log("Logging into Instagram..");
IG_request("POST", "https://www.instagram.com/accounts/login/ajax/", login, function(err, response)

Now we login to Instagram. Not shown is code dealing with a failed login.

      console.log("Logged in successfully.");
      IG_request("GET", "https://www.instagram.com/" + usernameValue + "/", null, function(err, response)

If we logged in successfully, we load the instagram home page.

        var getPictures = function(callback) {
          var media_query = isFirst ? "media.first(1)" : "media.after(" + start_cursor + ", 12)";
          var query = {
            q: "ig_user(" + userid + ") { " + media_query + " {\n  count,\n  nodes {\n    caption,\n    code,\n    comments {\n      count\n    },\n    date,\n    dimensions {\n      height,\n      width\n    },\n    display_src,\n    id,\n    is_video,\n    likes {\n      count\n    },\n    owner {\n      id\n    },\n    thumbnail_src\n  },\n  page_info\n}\n }",
            ref: "users::show"
          };
          IG_request("POST", "https://www.instagram.com/query/", query, function(err, response)

Now we build a complete list of all the user's images.

                  function(cb) {downloadFile(item.url, IMAGES_DIR + "/" +  item.id + ".jpg", cb);}

Then we download each of the images.

                  console.log("Successfully downloaded " + picture_urls.length + " images to " + outputDirValue + ".");
                  // watermark
                  var watermarkImg = function(item, callback) {
                    var thumbImg = THUMBS_DIR + "/" +  item.id + ".jpg";
                    var watermarkImg = WATERMARK_DIR + "/" +  item.id + ".jpg";
                    gm(thumbImg)
                    .resize(PREVIEW_THUMB_SIZE, PREVIEW_THUMB_SIZE)
                    .out("-draw", "font Arial font-size " + PREVIEW_FONT_SIZE + " gravity center fill Black rotate -45 text 0,0 \"PREVIEW\"")

After downloading all the images, we use a tool installed on the system, to create the preview thumbnails at the right resolution and with a watermark. That's it; that's all we need to do get all of our Instagram photos and prepare them for the next step.

Code review: server.py

Here is the simple Python-based server.py that we use to make the images we downloaded from Instagram:

"""Flask server for Bitcoin Instagram."""
#Import all the Python dependencies needed to run the server
import json
import sqlite3
import os
import sys

from flask import Flask
from flask import request
from flask import Response
from flask import render_template
from flask import g
from flask import send_from_directory
from flask.ext.basicauth import BasicAuth

#Import from the 21 Developer Library
from two1.wallet import Wallet
from two1.bitserv.flask import Payment

As is conventional in a Python program, we start by importing our dependencies, which are primarily the SQLite database, the Flask web microframework, and the 21 bitcoin developer library.

app = Flask(__name__, static_folder='static', template_folder='templates')
wallet = Wallet()
payment = Payment(app, wallet)
file_path = os.path.abspath(__file__)

Next, we create our Flask app. For more information about Flask, you may want to read our short guide, which also describes a bit about the Python function decorators used below.

#Define database config variables incase you want to password protect your DB
DATABASE = file_path[0:-10] + '/management/myphotos.db'
DEBUG = False
#SECRET_KEY = 'development key'
BASIC_AUTH_USERNAME = 'admin'
BASIC_AUTH_PASSWORD = 'default'

We define some configuration variables.

app.config.from_object(__name__)
basic_auth = BasicAuth(app)

We create our Flask app.

#This method connects to an existing database
def connect_db():
    rv = sqlite3.connect(app.config['DATABASE'])
    rv.row_factory = sqlite3.Row
    return rv

def get_db():
    if not hasattr(g, 'sqlite_db'):
        g.sqlite_db = connect_db()
    return g.sqlite_db

@app.before_request
def before_request():
    g.db = connect_db()

@app.teardown_request
def close_db(error):
    if hasattr(g, 'sqlite_db'):
        g.sqlite_db.close()

We create some stock functions for interacting with the SQLite database.

#This route will display a user's purchased images on a webpage
@app.route('/instagram/purchased')
@basic_auth.required
def show_purchased_photos():

    #Connect to the DB
    db = get_db()

    #Select all entries in the DB
    cur = db.execute('select id, photopath from purchased order by id desc')
    entries = cur.fetchall()

    #render the database entries on a webpage
    return render_template('index_purchased.html', purchased=entries)

The first Flask app we create is a password-protected page that lets customers view the images they purchased. If you haven't purchased any images, the page prints "no results found".

#This route will display a user's Instagram content on a webpage        
@app.route('/instagram')
def show_photos():

    """Show photos in grid."""
    #Connect to the DB
    db = get_db()

    #Select all entries in the DB
    cur = db.execute('select id, photopath, price from photos order by id asc')
    entries = cur.fetchall()

    #render the database entries on a webpage
    return render_template('index.html', photos=entries)

The second app we create displays a gallery of photos for sale. The preview images created by the scraper are shown, rather than the full-resolution images, so that potential customers can't get the full photo without paying.

#This method is used to obtain the price of an image once the users enters the image id
def get_price_from_id(request):

    #Obtain image id from user's response
    id = int(request.args.get('id'))

    #Connect to the DB
    db = get_db()

    #create query to select the image requested by the client and extract its price
    query = "select photopath, price from photos where id = ?"

    #execute query
    cur = db.execute(query, (id,))
    pic_path, pic_price = cur.fetchall()[0]

    #return price of image to the payment required decorator
    return pic_price

We create a function that gets the price of the image from the database. The default price is 500 satoshis, but this allows you to set a higher or lower price for particular images.

#This method is used to obtain the file name of the image selected by the user for purchase
def get_pic_name(id):

    #Connect to the DB
    db = get_db()

    #create query to select the image requested by the client and extract its name
    query = "select photopath, price from photos where id = ?"

    #execute query
    cur = db.execute(query, (id,))
    pic_path, pic_price = cur.fetchall()[0]
    pic_name = pic_path.split("/")

    #return image filename
    return pic_name[3]

Similar to retrieving the price from the database, this code lets you retrieve the filename. The scraper didn't give us interesting file names---just long numeric ids---but we could edit the database to give the images descriptive names.

#This method executes the buy command and transfers the file to the user
@app.route('/instagram/buy', methods=["GET"])
@payment.required(get_price_from_id)
def buy_photos():
    """Buy a photo."""

    photo_id = int(request.args.get('id'))
    pic_name = get_pic_name(photo_id)
    dir_path = 'static/' + sys.argv[1] + '/img'
    return send_from_directory(dir_path, pic_name)

As our last Flask app, we create a 402 payment protocol purchase endpoint where users can specify an image to buy, receive a price quote, pay it, and receive the full-resolution image.

if __name__ == '__main__':
    app.run(host='::')

Now that everything is set up, we start our apps.

Code review: client.py

Finally, the client.py code is used by a customer to buy the photos that they see from your server.py. Note that both client and server must be on the same Zerotier network (instructions) for this to work.

import json
import os
import sys
import subprocess
import sqlite3
from os.path import expanduser

#import from the 21 Developer Library
from two1.commands.config import Config
from two1.wallet import Wallet
from two1.bitrequests import BitTransferRequests

The usual imports.

#set up bitrequest client for BitTransfer requests
wallet = Wallet()
username = Config().username
requests = BitTransferRequests(wallet, username)

We load the user's wallet so that they can pay.

# obtain server address from command line argument
server_url = sys.argv[3]

One of the parameters passed to this program is the name of the server to purchase from.

#purchased directory path
if os.path.islink(__file__):
        file_path = os.path.realpath(__file__)
    else:
        file_path = os.path.abspath(__file__)
pur_dir_path = file_path[0:-10] + '/static/purchased'

def purchased_db(filename):
    db_filename = file_path[0:-10] + '/management/myphotos.db'
    schema_filename = file_path[0:-10] + '/management/schema.sql'
    db_is_new = not os.path.exists(db_filename)

    #Connect to a database containing purchased photos and insert the recently purchased photo
    with sqlite3.connect(db_filename) as conn:
        if db_is_new:
            print('Creating schema')
            with open(schema_filename, 'rt') as f:
                schema = f.read()
            conn.executescript(schema)

        print("Adding purchased image to database")
        pic_path = "/static/purchased/" + filename
        conn.execute('Insert into purchased (photopath) values (?)',[pic_path])
        conn.commit()
        print("Recent purchase successfully added to the database")

Some functions for managing the database.

def buy_image():

    #Obtain pictured id from command line arg
    pic_num = sys.argv[1]

    #create url and request to purchase image for bitcoin
    sel_url = server_url+'/instagram/buy?id={0}&payout_address={1}'
    answer = requests.get(url=sel_url.format(int(pic_num), wallet.get_payout_address()), stream=True)

    #check the response code. If it is 200 then the image was purchased successfully. Else give an error.
    if answer.status_code != 200:
        print("Could not make an offchain payment. Please check that you have sufficient funds.")

We attempt to buy the photo, print an error message if it fails, and if it succeeds…

    else:
        #obtain image filename from comand line arg
        filename = sys.argv[2]

        #open file handle to write contents of the file being streamed from server
        with open(filename, 'wb') as fd:
            for chunk in answer.iter_content(4096):
                fd.write(chunk)
        fd.close()

        #store the image to a folder called 'static/purchased'. create the folder if it is not present initially.
        if not os.path.isdir(pur_dir_path):
            subprocess.check_output([
                "mkdir",
                pur_dir_path])

        subprocess.check_output([
            "mv",
            filename,
            pur_dir_path + '/.'])

        print("Congratulations, you just purchased the image {0} for {1} satoshi!".format(filename, answer.amount_paid))
        print("The image is stored at the following location: {0}".format(pur_dir_path + '/' + filename))

        #Insert purchased image into a database of purchased pictures
        purchased_db(filename)

If we purchased the image successfully, we move it to the appropriate directory and add it to the database so server.py can display it on the purchased page.

if __name__ == '__main__':
    buy_image()

With all our logic implemented, we run the program's main function.

Your Assignment

Mandatory: get the code running

The minimum you need to do is to checkout and setup the Bitcoin Instagram server

Here are some ideas for extending the code -- all of which would be fine as projects -- but feel free to try something else:

  • Bitcoin YouTube: download your videos and sell them as MP4s
  • Bitcoin Soundcloud: download your audio files and sell them as MP3s
  • Bitcoin Behance: see your PSD and JPGs
  • Etc...

The goal is to build a user-monetized data service, to sell content that you created or own the right to redistribute. Please do not sell content that you do not have a license for!