Skip to content

Instantly share code, notes, and snippets.

@jashsu
Last active April 15, 2016 01:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jashsu/10239863f44261f9419acc1ba77d325c to your computer and use it in GitHub Desktop.
Save jashsu/10239863f44261f9419acc1ba77d325c to your computer and use it in GitHub Desktop.
Monitor availability of limited rewards tiers using only API calls (no scraping!)
Python 2.7.10 (default, Oct 23 2015, 18:05:06)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "copyright", "credits" or "license()" for more information.
>>> WARNING: The version of Tcl/Tk (8.5.9) in use may be unstable.
Visit http://www.python.org/download/mac/tcltk/ for current information.
>>> import requests, json
>>> from lxml import etree
>>> from StringIO import StringIO
>>> from pprint import pprint
>>>
>>> MAX_RWD_DESC = 20
>>>
>>> session = requests.Session()
>>> r = session.get('https://www.kickstarter.com/projects/248983394/ossic-x-the-first-3d-audio-headphones-calibrated-t/pledge/new')
>>> rt = etree.parse(StringIO(r.text), etree.HTMLParser(encoding = 'UTF-8'))
>>> reward_data = [json.loads(i.attrib['data-reward']) for i in rt.xpath('//*[contains(concat(" ", @class, " "), " pledge-selectable ")]')]
>>> reward_urls = [{i['reward'][0:MAX_RWD_DESC]: i.get('urls')} for i in reward_data]
>>> pprint(reward_urls)
[{u'No Reward': None},
{u'THANK YOU! Every bit': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815481?signature=1460763286.1cd3f1e0c00a939925918947d0503f58ae71b4c5'}}},
{u'OSSIC BRANDED GOOGLE': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815483?signature=1460763286.03254280d983f1f8ee85beeac57145fd70478a8b'}}},
{u'SUPER EARLY BIRD: SA': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815484?signature=1460763286.afb10489cf62ee612cdd9a9e09573155b8a92703'}}},
{u'EARLY BIRD: SAVE $18': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815485?signature=1460763286.8478f8fe803ae9376e9ea26efd6a7d1d2cfb19d3'}}},
{u'KICKSTARTER SPECIAL:': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815486?signature=1460763286.8aff8fcee9644c819a065ff9ccf6ae3b49bc64ec'}}},
{u'KICKSTARTER LAST CHA': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/5005057?signature=1460763286.dcfba2ae0aeac75891a5f1a005a7606131cf9d27'}}},
{u'EXCLUSIVE INNOVATOR ': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815488?signature=1460763286.5ef69b76905afa352bf7699861035e2ef4b25550'}}}]
>>>
>>>
>>> r2 = session.get('https://api.kickstarter.com/v1/projects/1241205786/rewards/4815486?signature=1460763286.8aff8fcee9644c819a065ff9ccf6ae3b49bc64ec')
>>> reward = json.loads(r2.text)
>>> reward['shipping_rules'] = []
>>> pprint(reward)
{u'backers_count': 4444,
u'description': u'KICKSTARTER SPECIAL: SAVE $150\r\nYou\u2019ll receive one OSSIC X headphone. You\u2019re one of the first onboard the audio revolution!',
u'estimated_delivery_on': 1483228800,
u'id': 4815486,
u'limit': 6000,
u'minimum': 249.0,
u'project_id': 1241205786,
u'remaining': 1556,
u'reward': u'KICKSTARTER SPECIAL: SAVE $150\r\nYou\u2019ll receive one OSSIC X headphone. You\u2019re one of the first onboard the audio revolution!',
u'shipping_enabled': True,
u'shipping_preference': u'unrestricted',
u'shipping_rules': [],
u'shipping_summary': u'Ships anywhere in the world',
u'updated_at': 1460677120,
u'urls': {u'api': {u'reward': u'https://api.kickstarter.com/v1/projects/1241205786/rewards/4815486?signature=1460763785.02f80a134adb8c26e81b6226dd158987ad1a71b7'}}}
>>>
import json, urllib2
from time import sleep, ctime
from random import random
def send_email():
print('Importing libraries...')
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
print('Composing and sending...')
#Edit this stuff...
user = 'user@example.com'
pswd = 'secretkey'
recipient = 'user@example.com'
smtp = smtplib.SMTP()
smtp.connect('smtp.gmail.com', 587)
smtp.ehlo()
smtp.starttls()
smtp.login(user, pswd)
message = MIMEMultipart()
message['From'] = 'noreply'
message['To'] = recipient
message['Subject'] = 'Kickstarter alert'
html = """<html><head></head><body><p>Kickstarter alert</p></body></html>"""
message.attach(MIMEText(html, 'html'))
smtp.sendmail('noreply', recipient, message.as_string())
smtp.close()
print('Done!')
def monitor(url):
done = False
while (not done):
r = urllib2.urlopen(url)
reward = json.loads(r.read())
remaining = reward['remaining']
if remaining > 0:
send_email()
done = True
else:
#update the url to fetch
url = reward['urls']['api']['reward']
signature = url.split("signature=",1)[1].split('.')
print(ctime(float(signature[0])) + " hash:" + signature[1])
sleep(8 + random() * 4)
@jashsu
Copy link
Author

jashsu commented Apr 14, 2016

Error message if the API is called without a valid signature:

{
   "error_messages":[
      "You are not authorized to access this resource."
   ],
   "http_code":401,
   "ksr_code":"unauthorized",
   "disclaimer":"This endpoint is solely for the use of applications owned by Kickstarter. Any other use is a violation of Kickstarter's terms of use."
}

@jashsu
Copy link
Author

jashsu commented Apr 14, 2016

API call signature parameter is a unix timestamp appended with a SHA1 of an unknown payload.

@jashsu
Copy link
Author

jashsu commented Apr 15, 2016

The hash is somehow tied to the reward object. My guess is the hash payload is something like sha1(userid + projectid + rewardid + timestamp + secret)

@jashsu
Copy link
Author

jashsu commented Apr 15, 2016

>>> monitor(url)
Fri Apr 15 18:22:09 2016  hash:21b901d948befd65bea2c63810540885105b82fc
Fri Apr 15 18:22:18 2016  hash:1c62bcf603f026a6c4779531a57c33b37e9a17ab
Fri Apr 15 18:22:27 2016  hash:b547b09233269d96a7ac2ba955192024f074efa8
Fri Apr 15 18:22:38 2016  hash:1b042d67bdd15fcb9b20997ac105fe01f431b586
Fri Apr 15 18:22:47 2016  hash:e6cc1f257a248158c0b52b8fc36fce0a51b02414
Fri Apr 15 18:22:57 2016  hash:06922a3507672c943d38ce7a9f4422f1b6c5a808
Fri Apr 15 18:23:08 2016  hash:4d4dcd28014f284a33a1820ed93dd3ba0c9091a9
Fri Apr 15 18:23:17 2016  hash:f721734fc264b460a65f58d05b9ac374190d090f
Fri Apr 15 18:23:26 2016  hash:332c0088a29bc4c5f82303beee3e11a4010e4160
Fri Apr 15 18:23:39 2016  hash:8c835aa98532ddec188e1c880ecf61d080428c5d
Fri Apr 15 18:23:47 2016  hash:2b4b2ef82506e0416eac02b12ede5928628a669b
Importing libraries...
Composing and sending...
Done!

Works :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment