I had great fun last month writing a Reddit bot that pretended to be DJ Khaled. It’s been running ever since, but I thought it was about time to extend Khaled’s internet empire into another social media service. The obvious candidate: Tinder.
{: .phone-shot}
I’m not going to spend much time explaining what I want the bot to be or to do - it’ll be pretty much the same as the Reddit bot. If you haven’t read that post, I recommend you do so. It’s great, honest. Someone told me I’d “won the internet”.
Up to speed? Cool. On to Tinder. Tinder is a weird little app. There’s no public-facing API to speak of, and creating anything that talks to the service is unofficially frowned-upon - which only makes it that much more fun to work with. There have been a few community efforts to get a library put together, and this Gist gives some really nice documentation on which different endpoints are available.
I like working with a library, so I settled on a Python one called Pynder. It’s a little buggy, but I like the way it’s designed, so thought I’d stick with it. I’m largely working with my fork, and hopefully I’ll get some pull requests accepted.
Using Pynder, it’s fairly trivial to connect to Tinder and get our swipe on, like so:
session = pynder.Session(FACEBOOK_ID, FACEBOOK_AUTH_TOKEN)
for user in session.nearby_users():
user.like() # Khaled's a little desperate.
The variable FACEBOOK_ID is simply the ID for the Facebook account we’ve created to connect to Tinder with - unfortunately, the only way to make a fake DJ Khaled Tinder account is to make a matching Facebook one. I’ll just have to hope it doesn’t get nerfed as a spam account.
The variable FACEBOOK_AUTH_TOKEN presents us with more of a problem. This is the token used to authenticate with the Facebook API, and is specific to the Tinder app. It’s possible to get a valid auth token using the web API, by following this link and then extracting the token from the location header of the response, once you’ve granted Tinder the permissions it needs.
Unfortunately, this token is only valid for, at most, a couple of hours. This isn’t really useful for a long-running bot we want to deploy to some server somewhere and forget about. Facebook offers longer-lasting auth tokens, which have an expiry time of around 60 days. This is what the Tinder app uses.
Sounds perfect, right? I thought the same. Facebook’s documentation describes how to turn a standard auth token into an extended one, by use of an API request. Unfortunately, this request requires us to send our app’s secret token - in the case of Tinder, this is something we don’t really have access to.
So, have we hit a dead end with regard to getting a token? Not quite. Let’s forget about talking to Facebook for now. What if we could somehow grab the token that the Android app is using to communicate with Facebook? Doing this is a bit fiddly, but actually pretty quick. Here’s how.
My mate Charles
My laptop is running OS X and my phone is running Android, so this process might be a little specific. However, the general principles should apply with whatever weird combinations of tech you’ve got.
On my computer, I installed a web debugging proxy called Charles. It seems to be pretty powerful, but I’m only really scratching the surface of its features for this. It’s got a 30-day free trial and some mildly irritating pop-up “buy me” messages, which I guess is fair enough.
First of all, we need to set up the proxy to run on a specific port on this machine, as well as enable SSL proxying. These settings live in the Proxy menu and should be easy enough to find. Here are the settings I used:
{: .center-image}
{: .center-image}
Once we’ve got our proxy server up and running, we need to get the Android phone to connect to it. I used Proxydroid, which seems to work pretty well. Just point it at Charles and flip the switch to ‘on’. I set it up like this:
{: .center-image .phone-shot}
We’re not quite done yet, though. We still need to get a certificate onto the phone so that Charles can decode the encrypted traffic. Thankfully, Charles provides us with a very simple method to get - with your proxy connected, point your phone’s browser at http://www.charlesproxy.com/getssl.
Once that’s installed, run Tinder on the phone, then have a look at what Charles spits out. Tinder seems to talk to a lot of different services, but the request we want is to the endpoint ‘auth’. It should look like this:
{: .center-image}
And there’s our auth token! Go ahead and copy that, we’re going to need it.
Getting chatty
The basic function of our bot is that, once an hour, it will:
- Check Tinder to see if it can swipe, and if so, swipe right on every single person until we hit the 12-hour limit. I guess I could buy the bot Tinder Pro or whatever it’s called, but the thought of being one of those people who pays for Tinder frightens me a little bit. Even if it’s for Khaled.
- Go through everyone the bot has matched with, and progress any conversations appropriately. Have a look at the Reddit bot post if you don’t know how a Khaled chat goes.
Basically, that’s just:
while True:
handle_likes()
handle_matches()
time.sleep(3600)
Let’s go through each of those methods. First up is doing what Tinder’s known for…
def handle_likes():
while True:
try:
users = session.nearby_users()
for u in users:
if u.name == 'Tinder Team':
log('Out of swipes, pausing one hour...')
return
u.like()
log('Liked ' + u.name)
except ValueError:
continue
Here, we want to continuously fetch nearby users and ’like’ all of them. Khaled ain’t shallow. Pynder sometimes fails to parse the JSON received, so catching that ValueError lets us just continue as normal. If we see a user called ‘Tinder Team’, that’s the Tinder service’s way of letting us know we’re out of swipes, so it’s only then that we stop.
Khaled got nine matches this morning when this ran. Six of them then unmatched him. So shallow.
Now that we’ve got a few people to chat to, we can do so. Here’s what that method looks like:
def handle_matches():
log(str(len(session._api.matches())) + ' matches')
matches = session._api.matches()
for m in matches:
message(m)
Simple enough - grab any and all matches, and message them appropriately. The main chunk of work is done in the message method, which looks like this:
def message(match):
ms = match['messages']
khaled = session.profile.id
if not ms:
send(match, 0)
return
said = False
count = 0
name = match['person']['name']
for m in ms:
if m['from'] == khaled:
count += 1
said = False
elif 'dj khaled' in m['message'].lower():
said = True
if count >= len(messages):
log('Finished conversation with ' + name)
return
if said:
send(match, count)
else:
log('No new messages from ' + name)
First up, if we’ve never spoken to the person, we should send Khaled’s traditional greeting. Otherwise, we want to go through the message log, and check if Khaled’s potential true love has mentioned ‘DJ Khaled’ since the last time he replied. If so, we send the next message(s) in the list. If there are no new messages from the person, or they haven’t said the right thing, we ignore ’em.
The send method is pretty simple. Here it is:
def send(match, message_no):
for m in messages[message_no]:
session._api._post('/user/matches/' + match['id'],
{"message": m})
time.sleep(3)
log('Sent message ' + str(message_no) + ' to ' + match['person']['name'])
Pynder has its own send method, but it’s a little broken and uses the wrong ID, hence this slight workaround. This method also stalls three seconds between sending consecutive messages - I reckon this seems a little more believable.
Tinder doesn’t really support formatting, and it definitely isn’t compatible with ChangeTip, so this bot can’t actually give people money. However, Tinder does support Unicode, so we can send Emojis. Yay! The messages.py file looks like this:
# coding=utf8
messages = [
[u'Say my name, baby.'],
[u'Say my NAME, baby.'],
[
u'You smart. You loyal. You grateful. I appreciate that.',
u'💷 Go buy your mom a house.',
u'💷 Go buy your whole family houses.',
u'💷 Put this money in your savings account.',
u'💷 Go spend some money for no reason.',
u'💷 Come back and ask for more.',
u'''Baby, let the music take control.
Let we the best sound take control, baby - hold on, say my name?'''
],
[u'''S'right. That's right, baby. You remember that.
https://youtu.be/fxPBu_vX9Q0''']
]
Yeah…having Emojis in the middle of code looks really weird.
We’re pretty much done with the coding side of things now. If you’d like to look at the full source, it’s on my GitHub. Fill yer boots.
Does it work? You bet. Here are some screenshots of its first ever conversation - it was lucky enough to meet someone who actually got the joke. These shots are from before the line breaks in messages were split up into individual ones, but it’s pretty much the same.
{: .phone-shot}
{: .phone-shot}
{: .phone-shot}
Big up this person for getting the joke and playing along.
Aaaand, that’s pretty much it! The bot’s been deployed into the cloud (I love that silly phrase), and I’ll probably tweet if it does anything totally hilarious. For now, here are some links you might find useful:
Until next time - remember to let the music take control.