├── .github └── FUNDING.yml ├── requirements.txt ├── .gitignore ├── credentials.json.example ├── LICENSE ├── README.md ├── request_model.py ├── tinder_token.py ├── main.py └── tinder_api.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [philipperemy] 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | requests 3 | robobrowser 4 | wget 5 | lxml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | *.pyc 3 | profile_pics/ 4 | profile_pics_resize/ 5 | .idea/ 6 | .DS_Store 7 | credentials.json -------------------------------------------------------------------------------- /credentials.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "FB_ID": "", 3 | "FB_EMAIL_ADDRESS": "", 4 | "FB_PASSWORD": "", 5 | "API_HOST": "", 6 | "MODEL_ID": "" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Philippe Rémy 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep Learning Tinder (Machine Learning) 2 | *With DIGITS, Caffe and Tinder token auto fetching* 3 | 4 | Welcome on the Tinder Bot app whose recommandations are based on Deep Learning for image recognition. Don't forget to star or fork this repo if you appreciate the bot! 5 | 6 | ## How does it work? 7 | 8 | Everything is explained here: http://philipperemy.github.io/tinder-deep-learning/ 9 | 10 | ## How to run the bot 11 | ``` 12 | git clone https://github.com/philipperemy/Deep-Learning-Tinder.git 13 | cd Deep-Learning-Tinder 14 | mv credentials.json.example credentials.json 15 | vim credentials.json # edit and fill the variables (explained in the next section: Configuration) 16 | python main.py 17 | ``` 18 | 19 | If the configuration file is correct, you will see in the logs: `Successfully connected to Tinder servers.` 20 | 21 | ## Configuration of credentials.json 22 | 23 | - `FB_ID` : The id of your facebook. Your profile is available at https://www.facebook.com/FB_ID where FB_ID is your id. 24 | - `FB_EMAIL_ADDRESS` : Your Facebook email address. 25 | - `FB_PASSWORD` : Your Facebook password. 26 | - `API_HOST`: URL of your NVIDIA Digits server. More information available here : https://github.com/NVIDIA/DIGITS 27 | - `MODEL_ID`: ID of your trained model. It appears in the URL when you click on your model in the DIGITS interface, like this: `API_HOST`/models/`MODEL_ID` 28 | 29 | `FB_EMAIL_ADDRESS` and `FB_PASSWORD` are used to retrieve your `FB_AUTH_TOKEN`, useful to request the Tinder token. 30 | 31 | Your configuration should look like this: 32 | 33 | ``` 34 | { 35 | "FB_ID": "tim.cook", 36 | "FB_EMAIL_ADDRESS": "tim.cook@apple.com", 37 | "FB_PASSWORD": "i_love_apple", 38 | "API_HOST": "http://localhost:5000/", 39 | "MODEL_ID": "20160619-000820-19f6" 40 | } 41 | ``` 42 | 43 | Hope you will have some fun ! 44 | -------------------------------------------------------------------------------- /request_model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import json 6 | import os 7 | import time 8 | import uuid 9 | 10 | import imageio as imageio 11 | import numpy as np 12 | from requests import post 13 | 14 | 15 | # from scipy.misc.pilutil import imread, imsave 16 | 17 | 18 | def like_or_nope(_filename_paths, model_api_host, model_id): 19 | scores = [] 20 | for _url in _filename_paths: 21 | scores.append(call_model(imageio.imread(_url), api_host=model_api_host, model_id=model_id)) 22 | print(scores) 23 | if np.any(np.array(scores) > 40.0): 24 | return 'like' 25 | return 'nope' 26 | 27 | 28 | def call_model(image, api_host='', model_id=''): 29 | tmp_filename = str(uuid.uuid4()) + '.png' 30 | imageio.imsave(tmp_filename, image) 31 | 32 | path = '/models/images/classification/classify_one.json' 33 | files = {'image_file': open(tmp_filename, 'rb')} 34 | 35 | try: 36 | r = post(api_host + path, files=files, params={'job_id': model_id}) 37 | finally: 38 | os.remove(tmp_filename) 39 | time.sleep(2) # wait 2 seconds. 40 | 41 | result = r.json() 42 | if result.get('error'): 43 | raise Exception(result.get('error').get('description')) 44 | 45 | for res_element in result['predictions']: 46 | if 'LIKE' in res_element[0]: 47 | print(result) 48 | return res_element[1] 49 | return 0.0 50 | 51 | 52 | if __name__ == '__main__': 53 | 54 | credentials = json.load(open('credentials.json', 'r')) 55 | target_dir = 'FINAL_NOPE/' 56 | for filename in os.listdir(target_dir): 57 | if filename.endswith(".jpg"): 58 | try: 59 | img = imread(target_dir + filename) 60 | call_model(img, credentials['API_HOST'], credentials['MODEL_ID']) 61 | except Exception as e: 62 | print(str(e)) 63 | -------------------------------------------------------------------------------- /tinder_token.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import requests 4 | import robobrowser 5 | 6 | # MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; U; en-gb; KFTHWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.16 Safari/535.19" 7 | # MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15' 8 | MOBILE_USER_AGENT = "Tinder/11.4.0 (iPhone; iOS 12.4.1; Scale/2.00)" 9 | 10 | FB_AUTH = 'https://www.facebook.com/v2.6/dialog/oauth?redirect_uri=fb464891386855067%3A%2F%2Fauthorize%2F&display=touch&state=%7B%22challenge%22%3A%22IUUkEUqIGud332lfu%252BMJhxL4Wlc%253D%22%2C%220_auth_logger_id%22%3A%2230F06532-A1B9-4B10-BB28-B29956C71AB1%22%2C%22com.facebook.sdk_client_state%22%3Atrue%2C%223_method%22%3A%22sfvc_auth%22%7D&scope=user_birthday%2Cuser_photos%2Cuser_education_history%2Cemail%2Cuser_relationship_details%2Cuser_friends%2Cuser_work_history%2Cuser_likes&response_type=token%2Csigned_request&default_audience=friends&return_scopes=true&auth_type=rerequest&client_id=464891386855067&ret=login&sdk=ios&logger_id=30F06532-A1B9-4B10-BB28-B29956C71AB1&ext=1470840777&hash=AeZqkIcf-NEW6vBd' 11 | 12 | # Recently fixed thanks to https://github.com/fbessez/Tinder/issues/75#issuecomment-497529023 13 | def get_access_token(email, password): 14 | s = robobrowser.RoboBrowser(user_agent=MOBILE_USER_AGENT, parser="lxml") 15 | s.open(FB_AUTH) 16 | f = s.get_form() 17 | f["pass"] = password 18 | f["email"] = email 19 | s.submit_form(f) 20 | f = s.get_form() 21 | try: 22 | s.submit_form(f, submit=f.submit_fields['__CONFIRM__']) 23 | # print(s.response.content.decode()) 24 | access_token = re.search(r"access_token=([\w\d]+)", s.response.content.decode()).groups()[0] 25 | except requests.exceptions.InvalidSchema as browserAddress: 26 | # print(type(browserAddress)) 27 | access_token = re.search(r"access_token=([\w\d]+)", str(browserAddress)).groups()[0] 28 | return access_token 29 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | 5 | import wget 6 | 7 | import request_model as model 8 | import tinder_api as ti 9 | from tinder_token import get_access_token 10 | 11 | 12 | def stats(likes, nopes): 13 | prop_likes = (float(likes) / (likes + nopes)) * 100.0 14 | prop_nopes = 100.0 - prop_likes 15 | print('likes = {} ({}%), nopes = {} ({}%)'.format(likes, prop_likes, 16 | nopes, prop_nopes)) 17 | 18 | 19 | def main(): 20 | credentials = json.load(open('credentials.json', 'r')) 21 | fb_id = credentials['FB_ID'] 22 | fb_auth_token = get_access_token(credentials['FB_EMAIL_ADDRESS'], credentials['FB_PASSWORD']) 23 | model_api_host = str(credentials['API_HOST']) 24 | model_id = str(credentials['MODEL_ID']) 25 | 26 | print('Deep Learning Tinder bot') 27 | print('----------') 28 | print('FB_ID = {}'.format(fb_id)) 29 | print('FB_AUTH_TOKEN = {}'.format(fb_auth_token)) 30 | print('MODEL_API_HOST = {}'.format(model_api_host)) 31 | print('MODEL_ID = {}'.format(model_id)) 32 | 33 | like_count = 0 34 | nope_count = 0 35 | 36 | while True: 37 | token = ti.auth_token(fb_auth_token, fb_id) 38 | 39 | print('TINDER_TOKEN = {}'.format(token)) 40 | 41 | if not token: 42 | print('could not get Tinder token. Program will exit.') 43 | sys.exit(0) 44 | 45 | print('Successfully connected to Tinder servers.') 46 | 47 | lat = 34.7 48 | lon = 135.5 49 | # http://words.alx.red/tinder-api-2-profile-and-geolocation/ 50 | print(ti.change_loc(lat, lon, token)) 51 | my_profile = ti.profile(token) 52 | print(json.dumps(my_profile, indent=4, sort_keys=True)) 53 | 54 | for user in ti.recommendations(token): 55 | if not user: 56 | break 57 | 58 | count_photos = 1 59 | filename_paths = [] 60 | for urls in user.d['photos']: 61 | directory = "data/" + str(user.age) + "/" + str(user.user_id) + "/" 62 | 63 | if 'tinder_rate_limited_id' in directory: 64 | print('Limit reached.') 65 | exit(0) 66 | 67 | if not os.path.exists(directory): 68 | os.makedirs(directory) 69 | 70 | url = urls['url'] 71 | filename_path = directory + str(count_photos) + ".png" 72 | count_photos += 1 73 | print(url, "=>", filename_path) 74 | wget.download(url, out=filename_path, bar=None) 75 | filename_paths.append(filename_path) 76 | try: 77 | action = model.like_or_nope(filename_paths, 78 | model_api_host=model_api_host, 79 | model_id=model_id) 80 | if action == 'like': 81 | like_count += 1 82 | print(' -> Like') 83 | stats(like_count, nope_count) 84 | match = ti.like(user.user_id) 85 | if match: 86 | print(' -> Match!') 87 | else: 88 | nope_count += 1 89 | print(' -> nope') 90 | stats(like_count, nope_count) 91 | ti.nope(user.user_id) 92 | 93 | except Exception as e: 94 | print(e) 95 | 96 | 97 | if __name__ == '__main__': 98 | main() 99 | -------------------------------------------------------------------------------- /tinder_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | import requests 5 | 6 | headers = { 7 | 'app_version': '3', 8 | 'platform': 'ios', 9 | } 10 | 11 | 12 | # super LIKE '/like/' + likedUserId + '/super' 13 | 14 | 15 | class User(object): 16 | def __init__(self, data_dict): 17 | self.d = data_dict 18 | 19 | @property 20 | def user_id(self): 21 | return self.d['_id'] 22 | 23 | @property 24 | def ago(self): 25 | raw = self.d.get('ping_time') 26 | if raw: 27 | d = datetime.strptime(raw, '%Y-%m-%dT%H:%M:%S.%fZ') 28 | secs_ago = int(datetime.now().strftime("%s")) - int(d.strftime("%s")) 29 | if secs_ago > 86400: 30 | return u'{days} days ago'.format(days=secs_ago / 86400) 31 | elif secs_ago < 3600: 32 | return u'{mins} mins ago'.format(mins=secs_ago / 60) 33 | else: 34 | return u'{hours} hours ago'.format(hours=secs_ago / 3600) 35 | 36 | return '[unknown]' 37 | 38 | @property 39 | def bio(self): 40 | try: 41 | x = self.d['bio'].encode('ascii', 'ignore').replace('\n', '')[:50].strip() 42 | except (UnicodeError, UnicodeEncodeError, UnicodeDecodeError): 43 | return '[garbled]' 44 | else: 45 | return x 46 | 47 | @property 48 | def age(self): 49 | raw = self.d.get('birth_date') 50 | if raw: 51 | d = datetime.strptime(raw, '%Y-%m-%dT%H:%M:%S.%fZ') 52 | return datetime.now().year - int(d.strftime('%Y')) 53 | 54 | return 0 55 | 56 | def __unicode__(self): 57 | return u'{name} ({age}), {distance}km, {ago}'.format( 58 | name=self.d['name'], 59 | age=self.age, 60 | distance=self.d['distance_mi'], 61 | ago=self.ago 62 | ) 63 | 64 | 65 | def auth_token(fb_auth_token, fb_user_id): 66 | h = headers 67 | h.update({'content-type': 'application/json'}) 68 | req = requests.post( 69 | # 'https://api.gotinder.com/auth', 70 | 'https://api.gotinder.com/v2/auth/login/facebook', 71 | headers=h, 72 | # data=json.dumps({'facebook_token': fb_auth_token, 'facebook_id': fb_user_id}) 73 | # data=json.dumps({'facebook_token': fb_auth_token} 74 | data=json.dumps({'token': fb_auth_token}) 75 | ) 76 | try: 77 | return req.json()['data']['api_token'] 78 | except: 79 | return None 80 | 81 | 82 | def recommendations(auth_token): 83 | h = headers 84 | h.update({'X-Auth-Token': auth_token}) 85 | r = requests.get('https://api.gotinder.com/user/recs', headers=h) 86 | if r.status_code == 401 or r.status_code == 504: 87 | raise Exception('Invalid code') 88 | 89 | if 'results' not in r.json(): 90 | print(r.json()) 91 | 92 | for result in r.json()['results']: 93 | print(result) 94 | yield User(result) 95 | 96 | 97 | def super_like(user_id): 98 | try: 99 | u = 'https://api.gotinder.com/like/%s/super' % user_id 100 | d = requests.get(u, headers=headers, timeout=0.7).json() 101 | except KeyError: 102 | raise 103 | else: 104 | return d['match'] 105 | 106 | 107 | def like(user_id): 108 | try: 109 | u = 'https://api.gotinder.com/like/%s' % user_id 110 | d = requests.get(u, headers=headers, timeout=0.7).json() 111 | except KeyError: 112 | raise 113 | else: 114 | return d['match'] 115 | 116 | 117 | def nope(user_id): 118 | try: 119 | u = 'https://api.gotinder.com/pass/%s' % user_id 120 | requests.get(u, headers=headers, timeout=0.7).json() 121 | except KeyError: 122 | raise 123 | 124 | 125 | def get_headers(tinder_token): 126 | return {'host': 'api.gotinder.com', 127 | 'Authorization': "Token token='#{'" + str(tinder_token) + "'}'", 128 | 'x-client-version': '47217', 129 | 'app-version': '467', 130 | 'Proxy-Connection': 'keep-alive', 131 | 'Accept-Encoding': 'gzip, deflate', 132 | 'Accept-Language': 'en-GB;q=1, fr-FR;q=0.9', 133 | 'platform': 'ios', 134 | 'Content-Type': 'application/json', 135 | 'User-Agent': 'Tinder/4.7.2 (iPhone; iOS 9.2.1; Scale/2.00)', 136 | 'Connection': 'keep-alive', 137 | 'X-Auth-Token': str(tinder_token), 138 | 'os_version': '90000200001'} 139 | 140 | 141 | def change_loc(lat, lon, tinder_token): 142 | req = requests.post( 143 | 'https://api.gotinder.com/user/ping', 144 | headers=get_headers(tinder_token), # NYC 40.7128 N, 74.0059 145 | # tokyo 35.6895 N, 139.6917 146 | data=json.dumps({'lat': lat, 'lon': lon}) 147 | ) 148 | try: 149 | return req.json() 150 | except: 151 | return None 152 | 153 | 154 | def profile(tinder_token): 155 | req = requests.post( 156 | 'https://api.gotinder.com/profile', 157 | headers=get_headers(tinder_token)) 158 | try: 159 | return req.json() 160 | except: 161 | return None 162 | --------------------------------------------------------------------------------