Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 1c6511c

Browse files
committed
Initial import
0 parents  commit 1c6511c

9 files changed

+202
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.pyc
2+
.vagrant/

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2013 Martin Polden
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

Makefile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
all: lint test
2+
3+
test:
4+
python pysnap/tests.py
5+
6+
lint:
7+
flake8 --max-complexity=10 pysnap/*.py

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pysnap
2+
======
3+
Implementation of Snapchat API in Python.
4+
5+
Dependencies
6+
------------
7+
8+
pip install --use-mirrors -r requirements.txt
9+

Vagrantfile

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- mode: ruby -*-
2+
# vi: set ft=ruby :
3+
4+
$script = <<SCRIPT
5+
set -x
6+
7+
# Ensure noninteractive apt-get
8+
export DEBIAN_FRONTEND=noninteractive
9+
10+
# Set time zone
11+
echo "Europe/Oslo" > /etc/timezone
12+
dpkg-reconfigure tzdata
13+
14+
# Install packages
15+
apt-get -y --quiet update
16+
apt-get -y --quiet install make python-pip
17+
18+
# Install pip packages
19+
pip install --quiet --upgrade -r /vagrant/requirements.txt \
20+
-r /vagrant/dev-requirements.txt
21+
SCRIPT
22+
23+
Vagrant.configure("2") do |config|
24+
config.vm.box = "raring64-current"
25+
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/raring/current/raring-server-cloudimg-amd64-vagrant-disk1.box"
26+
config.ssh.forward_agent = true
27+
config.vm.provider :virtualbox do |vb|
28+
vb.customize ["modifyvm", :id, "--memory", "1024"]
29+
end
30+
config.vm.provision :shell, :inline => $script
31+
end

dev-requirements.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bpython==0.12
2+
flake8==2.0
3+
mccabe==0.2.1
4+
pep8==1.4.6
5+
pip-tools==0.3.4
6+
pyflakes==0.7.3
7+
Pygments==1.6

pysnap/pysnap.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python
2+
3+
import requests
4+
from Crypto.Cipher import AES
5+
from hashlib import sha256
6+
from time import time
7+
from urlparse import urljoin
8+
9+
URL = 'https://feelinsonice-hrd.appspot.com/bq/'
10+
SECRET = 'iEk21fuwZApXlz93750dmW22pw389dPwOk'
11+
STATIC_TOKEN = 'm198sOkJEn37DjqZ32lpRu76xmw288xSQ9'
12+
BLOB_ENCRYPTION_KEY = 'M02cnQ51Ji97vwT4'
13+
HASH_PATTERN = ('00011101111011100011110101011110'
14+
'11010001001110011000110001000110')
15+
16+
17+
def make_request_token(a, b):
18+
hash_a = sha256(SECRET + a).hexdigest()
19+
hash_b = sha256(b + SECRET).hexdigest()
20+
result = [None] * len(HASH_PATTERN)
21+
for i, c in enumerate(HASH_PATTERN):
22+
result[i] = hash_b[i] if c == '1' else hash_a[i]
23+
return ''.join(result)
24+
25+
26+
def pkcs5_pad(data, blocksize=16):
27+
pad_count = blocksize - len(data) % blocksize
28+
return data + (chr(pad_count) * pad_count)
29+
30+
31+
def is_video(data):
32+
return len(data) > 1 and data[0] == chr(0x00) and data[1] == chr(0x00)
33+
34+
35+
def is_image(data):
36+
return len(data) > 1 and data[0] == chr(0xFF) and data[1] == chr(0xD8)
37+
38+
39+
def decrypt(data):
40+
cipher = AES.new(BLOB_ENCRYPTION_KEY, AES.MODE_ECB)
41+
return cipher.decrypt(pkcs5_pad(data))
42+
43+
44+
def encrypt(data):
45+
cipher = AES.new(BLOB_ENCRYPTION_KEY, AES.MODE_ECB)
46+
return cipher.encrypt(pkcs5_pad(data))
47+
48+
49+
def timestamp():
50+
return int(round(time() * 1000))
51+
52+
53+
class Snapchat(object):
54+
55+
def __init__(self, username, password):
56+
self.username = username
57+
self.password = password
58+
59+
def _request(self, endpoint, data=None):
60+
now = timestamp()
61+
if data is None:
62+
data = {}
63+
data.update({
64+
'username': self.username,
65+
'timestamp': now,
66+
'req_token': make_request_token(
67+
getattr(self, 'auth_token', STATIC_TOKEN), str(now))
68+
})
69+
return requests.post(urljoin(URL, endpoint), data=data)
70+
71+
def login(self):
72+
r = self._request('login', {'password': self.password})
73+
result = r.json()
74+
if 'auth_token' in result:
75+
self.auth_token = result['auth_token']
76+
return result
77+
78+
def logout(self):
79+
r = self._request('logout')
80+
return r.status_code == 200
81+
82+
def get_updates(self, update_timestamp=0):
83+
r = self._request('updates', {'update_timestamp': update_timestamp})
84+
result = r.json()
85+
if 'auth_token' in result:
86+
self.auth_token = result['auth_token']
87+
return result
88+
89+
def get_blob(self, snap_id):
90+
r = self._request('blob', {'id': snap_id})
91+
data = decrypt(r.content)
92+
return data if is_image(data) or is_video(data) else None

pysnap/tests.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python
2+
3+
import unittest
4+
from pysnap import make_request_token, is_image, is_video, pkcs5_pad
5+
6+
7+
class Snapchat(unittest.TestCase):
8+
9+
def test_make_request_token(self):
10+
self.assertEqual(('c6a633f0c2c9e72f7cdf49f888fc64ee7179096c57fdedaad1f'
11+
'4ea513dbb7b34'), make_request_token('foo', 'bar'))
12+
13+
def test_is_image(self):
14+
self.assertFalse(is_image([]))
15+
self.assertFalse(is_image([None]))
16+
self.assertFalse(is_image([None, None]))
17+
self.assertTrue(is_image([chr(0xFF), chr(0xD8)]))
18+
19+
def test_is_video(self):
20+
self.assertFalse(is_video([]))
21+
self.assertFalse(is_video([None]))
22+
self.assertFalse(is_video([None, None]))
23+
self.assertTrue(is_video([chr(0x00), chr(0x00)]))
24+
25+
def test_pkcs5_pad(self):
26+
self.assertEqual('\x10' * 16, pkcs5_pad(''))
27+
self.assertEqual('foo\r\r\r\r\r\r\r\r\r\r\r\r\r', pkcs5_pad('foo'))
28+
29+
30+
if __name__ == '__main__':
31+
unittest.main()

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pycrypto==2.6
2+
requests==1.2.3

0 commit comments

Comments
 (0)