├── .gitignore ├── README.md ├── dotcloud.yml ├── mitmproxy.png ├── phew.py ├── rest.py ├── settings-sample.py └── supervisord.conf /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | settings.py 37 | config -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Phewtick Hack 2 | ============= 3 | 4 | This is a python script to automatically call Phewtick RESTful APIs. 5 | 6 | It does 2 things very well: 7 | 8 | 1. Automatically 'scan QR code' to earn you money! 9 | 10 | 2. Send message to _any_ Phewtick user! 11 | 12 | 13 | What is Phewtick? 14 | ------------------ 15 | 16 | [Phewtick](http://www.techinasia.com/phewtick-mobile-app/) is an app that let you earn money when you meetup with friends! 17 | 18 | To earn, your friend must be nearby, then you scan his/her QR code, and both of you will get a random number of points (which is pegged to real money). On average, you earn a few cents per meetup.. 19 | 20 | You also need to wait 1 hour before you can 'meetup' with the same friend again. 21 | 22 | To cash out, you need to accumulate ~$30, which means you need ~1000 meetups before you can get the money. 23 | 24 | 25 | 26 | Why create this hack? 27 | ---------------------- 28 | 29 | The story goes like this.. 30 | 31 | My colleagues and I all use Phewtick, and is intrigued with this app 'idea' and 'business model'. We are active users of Phewtick, and the first thing we did as we arrive in office is to 'meetup'. 32 | 33 | And we meetup almost every hour.. 34 | 35 | As a [convert hacker](http://linked.in/junda), whose work is in [providing awesome API](http://developer.hoiio.com), I thought I should take a look at Phewtick internals. 36 | 37 | 38 | 39 | What is this hack? 40 | ---------------------- 41 | 42 | Using mitmproxy to sniff it's HTTP traffic, I learnt it's API endpoints and protocols. 43 | 44 | Then I wrote some python codes to help automate the scanning. 45 | 46 | Just the scanning. 47 | 48 | We are not really cheating. We are indeed meeting up everyday in the office :) 49 | 50 | 51 | Usage 1: Auto Scan 52 | ------------------ 53 | 54 | Clone the project and rename the `settings-sample.py` to `settings.py` 55 | 56 | git clone https://github.com/samwize/phewtick-hack.git 57 | mv phewtick-hack/settings-sample.py phewtick-hack/settings.py 58 | 59 | Open `settings.py` and enter your token, and your friends' tokens. To more tokens, the denser your network. 60 | 61 | Read the next section on how you can retrieve your token. 62 | 63 | Run the script: 64 | 65 | cd phewtick-hack 66 | python phew.py 67 | 68 | The script will automate refreshing of QR code and scanning it for everyone. Then it will sleep for around 1 hour before repeating again. 69 | 70 | Cheers (if you manage to cash out..) 71 | 72 | 73 | Usage 2: Messaging 74 | ------------------- 75 | 76 | Instead of `python phew.py` as described above, start python interactive shell. 77 | 78 | cd phewtick-hack 79 | python 80 | 81 | Assuming you want to send from the first token which is in the array `tokens[0]` to a Phewtick id `123456`, type 82 | 83 | ```python 84 | from phew import * 85 | message(0, 123456, "Hello!") 86 | ``` 87 | 88 | You can message to _any_ Phewtick id. Yes, that means you can spam anybody. 89 | 90 | To find your friends Phewtick id, you can generate with this 91 | 92 | ```python 93 | from phew import * 94 | generateUsers() 95 | ``` 96 | 97 | Retrieving tokens 98 | ----------------- 99 | 100 | It is a bit more tedious to get the token. It involves sniffing the HTPP traffic as you run your Phewtick app. 101 | 102 | You could use any software to sniff the http traffic. 103 | 104 | For me, I use [mitmproxy](http://mitmproxy.org/). Here is a guide on [how to use mitmproxy](http://blog.just2us.com/2012/05/sniff-iphone-http-traffic-using-mitmproxy/). 105 | 106 | This is how my mitmproxy looks: 107 | 108 | ![mitmproxy screenshot](https://raw.github.com/samwize/phewtick-hack/master/mitmproxy.png) 109 | 110 | What you need here is the `token` (which is purposefully blurred in the screenshot). Copy that and add to `settings.py` `tokens` array. 111 | 112 | Tip: You could ask all your friends to connect to your proxy and sniff their tokens. 113 | 114 | 115 | 116 | Deploying to dotcloud 117 | ---------------------- 118 | 119 | You can also deploy to [dotcloud](http://dotcloud.com) so that the python script is run day and night on a remote server. 120 | 121 | The 2 files added are `dotcloud.yml` and `supervisord.conf`, which you do not need to touch. 122 | 123 | Simply sign up with dotcloud, learn to create a python app, then push to it. 124 | -------------------------------------------------------------------------------- /dotcloud.yml: -------------------------------------------------------------------------------- 1 | www: 2 | type: python-worker 3 | config: 4 | python_version: v2.7 -------------------------------------------------------------------------------- /mitmproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samwize/phewtick-hack/b5b500be5de1cac277460cd5f18ba4f5f3edd3c1/mitmproxy.png -------------------------------------------------------------------------------- /phew.py: -------------------------------------------------------------------------------- 1 | from random import randrange 2 | from time import sleep 3 | from settings import * 4 | from rest import * 5 | 6 | 7 | 8 | def autoScan(): 9 | """ 10 | Automatically scan QR codes between tokens 11 | """ 12 | while (True): 13 | # Loop from 1st till second last token 14 | for i in range(0, len(tokens)-1): 15 | # Loop for from next till last 16 | for j in range(i+1, len(tokens)): 17 | new_qr_key = refreshToken(tokens[j]) 18 | sleep(randrange(10)) 19 | scanToken(tokens[i], new_qr_key) 20 | sleep(randrange(10)) 21 | sleep(randrange(20)) 22 | 23 | # Sleep for 1 hour 24 | t = 60*60 25 | print "Sleeping for " + str(t/60) + " minutes.." 26 | next = datetime.now() + timedelta(0,t) 27 | print "Next meetup at " + next.strftime('%H:%M') 28 | sleep(t) 29 | 30 | # Update everybody location 31 | for i in range(0, len(tokens)): 32 | updateLocation(tokens[i]) 33 | sleep(randrange(10)) 34 | 35 | 36 | def message(token_pos, phewtick_id, message): 37 | """ 38 | Send a message from user (token_pos in settings.tokens) to user (phewtick_id) 39 | """ 40 | sendMessage(tokens[token_pos], phewtick_id, message) 41 | 42 | 43 | def generateUsers(): 44 | writeAllUsersInTimelineForAllTokens() 45 | all = readAllUsersForAllTokens() 46 | for id, user in all.iteritems(): 47 | print id + "\t - " + user['name'] 48 | 49 | 50 | 51 | if __name__ == "__main__": 52 | autoScan() 53 | -------------------------------------------------------------------------------- /rest.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | from StringIO import StringIO 4 | import gzip 5 | import json 6 | from settings import * 7 | from datetime import datetime, timedelta 8 | from pprint import pprint 9 | 10 | 11 | # Set to true to print debug logs 12 | DEBUG = False 13 | 14 | # API Endpoints 15 | # Refresh scan 16 | refresh_url = 'http://v4api.phewtick.com/meets/refresh' 17 | # Scan on meet 18 | scan_url = 'http://v4api.phewtick.com/meets/meet' 19 | # Post location 20 | post_location_url = 'http://v4api.phewtick.com/users/position' 21 | 22 | # New 23 | # GET http://v4api.phewtick.com/alert/check?token=MY_TOKEN&version=3%2E2%2E0&type=i 24 | 25 | # POST http://v4api.phewtick.com/users/active 26 | 27 | # GET http://v4api.phewtick.com/users/read?user_id=MY_USER_ID&token=MY_TOKEN 28 | 29 | # POST http://v4api.phewtick.com/users/update 30 | # fb_token: FACEBOOK_TOKEN 31 | # user_id: MY_USER_ID 32 | # expire: 4001-01-01 00:00:00 +0000 33 | # iphone_version: 3.2.0 34 | # token: MY_TOKEN 35 | 36 | 37 | # GET timeline 38 | timeline_url = 'http://v4api.phewtick.com/reports/timeline' 39 | 40 | # POST http://v4api.phewtick.com/messages/opened 41 | # toId: 448325 42 | # token: MY_TOKEN 43 | 44 | 45 | # POST a new message 46 | message_create_url = 'http://v4api.phewtick.com/messages/create' 47 | 48 | 49 | # GET http://v4api.phewtick.com/messages/thread?offset=0&token=MY_TOKEN&limit=21&toId=448325 50 | 51 | 52 | 53 | 54 | headers = { 'User-Agent' : 'Phewtick/3.2.0 (iPhone; iOS 6.0.1; Scale/2.00)', 55 | 'Accept' : 'application/json', 56 | 'Accept-Language' : 'en, zh-Hant, ar, zh-Hans, fr, de, ja, nl, it, es, pt-PT, da, fi, nb, sv, ko, ru, pl, pt, tr, uk, hr, cs, el, he, ro, sk, th, id, ms, en-GB, ca, hu, vi, en-us;q=0.8', 57 | 'Proxy-Connection' : 'keep-alive', 58 | 'Accept-Encoding' : 'gzip, deflate'} 59 | 60 | headers_form = headers 61 | headers_form['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8' 62 | 63 | # Some parameters 64 | post_bg = 0 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | # Returns a new QR code 73 | def refreshToken(token): 74 | values = {'lng' : lng, 75 | 'lat' : lat, 76 | 'tz_offset' : tz_offset, 77 | 'tz_id' : tz_id, 78 | 'token' : token } 79 | # refresh_url = 'http://v4api.phewtick.com/meets/refresh?tz_offset=480&lng=103%2E876020&lat=1%2E329576&tz_id=Asia%2FSingapore&token=' 80 | 81 | req = urllib2.Request(refresh_url + '?' + urllib.urlencode(values), headers = headers) 82 | response = urllib2.urlopen(req) 83 | 84 | if response.info().get('Content-Encoding') == 'gzip': 85 | buf = StringIO( response.read()) 86 | f = gzip.GzipFile(fileobj=buf) 87 | data = f.read() 88 | else: 89 | data = response.read() 90 | 91 | if DEBUG: 92 | print data 93 | qr = json.loads(data)['qr_key'] 94 | # print 'Refresh QR: ' + qr 95 | 96 | return qr 97 | 98 | 99 | # Scan a friend QR Code to register meetup 100 | def scanToken(token, qr_key): 101 | values = {'lng' : lng, 102 | 'lat' : lat, 103 | 'qr_key' : qr_key, 104 | 'token' : token } 105 | 106 | data = urllib.urlencode(values) 107 | req = urllib2.Request(scan_url, data, headers) 108 | response = urllib2.urlopen(req) 109 | 110 | if response.info().get('Content-Encoding') == 'gzip': 111 | buf = StringIO( response.read()) 112 | f = gzip.GzipFile(fileobj=buf) 113 | data = f.read() 114 | else: 115 | data = response.read() 116 | 117 | if DEBUG: 118 | print data 119 | res = json.loads(data) 120 | 121 | if 'get_point' in res: 122 | print '>> Earned ' + str(res['get_point']) 123 | else: 124 | if 'err_text' in res: 125 | print 'Error: ' + str(res['err_text']) 126 | else: 127 | print '.' 128 | 129 | 130 | # Update current location 131 | def updateLocation(token): 132 | values = {'lng' : lng, 133 | 'lat' : lat, 134 | 'tz_offset' : tz_offset, 135 | 'tz_id' : tz_id, 136 | 'post_bg' : post_bg, 137 | 'token' : token } 138 | 139 | data = urllib.urlencode(values) 140 | req = urllib2.Request(post_location_url, data, headers_form) 141 | response = urllib2.urlopen(req) 142 | 143 | if response.info().get('Content-Encoding') == 'gzip': 144 | buf = StringIO( response.read()) 145 | f = gzip.GzipFile(fileobj=buf) 146 | data = f.read() 147 | else: 148 | data = response.read() 149 | 150 | print '^ ' + data 151 | 152 | 153 | # Send a message to a Phewtick user 154 | def sendMessage(token, to_id, message): 155 | values = {'toId' : to_id, 156 | 'body' : message, 157 | 'token' : token } 158 | 159 | data = urllib.urlencode(values) 160 | req = urllib2.Request(message_create_url, data, headers_form) 161 | response = urllib2.urlopen(req) 162 | 163 | if response.info().get('Content-Encoding') == 'gzip': 164 | buf = StringIO( response.read()) 165 | f = gzip.GzipFile(fileobj=buf) 166 | data = f.read() 167 | else: 168 | data = response.read() 169 | 170 | print '>> ' + data 171 | 172 | 173 | # Get timeline of meetups 174 | def getTimeline(token, offset): 175 | # timeline id is an item. leave empty to start from latest 176 | # http://v4api.phewtick.com/reports/timeline?token=MY_TOKEN&id=18106425&limit=21&offset=140 177 | 178 | values = {'limit' : 1000, 179 | 'offset' : offset, 180 | 'token' : token } 181 | 182 | data = urllib.urlencode(values) 183 | req = urllib2.Request(timeline_url + '?' + data, None, headers_form) 184 | response = urllib2.urlopen(req) 185 | 186 | if response.info().get('Content-Encoding') == 'gzip': 187 | buf = StringIO( response.read()) 188 | f = gzip.GzipFile(fileobj=buf) 189 | data = f.read() 190 | else: 191 | data = response.read() 192 | 193 | timeline = json.loads(data) 194 | # pprint(j) 195 | 196 | 197 | # print '>> ' + data 198 | return timeline 199 | 200 | 201 | 202 | 203 | 204 | ####################################### 205 | ######### Helpers ######### 206 | ####################################### 207 | 208 | 209 | def writeAllUsersInTimeline(token): 210 | """ 211 | Read the whole timeline, and write the details of users to file 212 | """ 213 | timeline = getTimeline(token, 0) 214 | 215 | # Loop thru timeline and print distinct id 216 | users = {} 217 | for item in timeline: 218 | if item['friend']['id'] not in users: 219 | users[item['friend']['id']] = item['friend'] 220 | # print item['friend']['id'] + ", # " + item['friend']['name'] 221 | if item['other']['id'] not in users: 222 | users[item['other']['id']] = item['other'] 223 | # print item['other']['id'] + ", # " + item['other']['name'] 224 | 225 | file = open("./data/users-" + token + ".json", "w+") 226 | json.dump(users, file) 227 | file.close() 228 | 229 | return users 230 | 231 | 232 | def writeAllUsersInTimelineForAllTokens(): 233 | for token in tokens: 234 | writeAllUsersInTimeline(token) 235 | return 236 | 237 | 238 | def readAllUsers(token): 239 | file = open("./data/users-" + token + ".json", "r") 240 | data = json.load(file) 241 | file.close() 242 | 243 | pprint(data) 244 | return data 245 | 246 | 247 | def readAllUsersForAllTokens(): 248 | merged = {} 249 | for token in tokens: 250 | try: 251 | users = readAllUsers(token) 252 | merged = dict(merged.items() + users.items()) 253 | except Exception, e: 254 | raise e 255 | return merged 256 | 257 | 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /settings-sample.py: -------------------------------------------------------------------------------- 1 | # Append tokens into this array 2 | tokens = ['YOUR TOKEN', 3 | 'FRIEND TOKEN 1', 4 | 'FRIEND TOKEN 2',] 5 | 6 | # Location info 7 | lat = 1.329576 8 | lng = 103.876020 9 | tz_offset = 480 10 | tz_id = 'Asia/Singapore' -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [program:daemonname] 2 | command = python /home/dotcloud/current/phew.py --------------------------------------------------------------------------------