🎞️ Videos → A love story written in JavaScript
Description
Dating apps can feel tedious and like a waste of time. Is there a way to skip the grunt work? That’s what I asked myself six months ago when I built Swipr, a tool written in JavaScript that does the swiping for you. In this talk we’ll see how it works and how to built CLIs with Node along the way.
Chapters
- Introduction and Speaker Intro - Ramón Guijarro from Undefined Labs 0:00
- About Ramón: Web Developer, Startup Life, and Community Involvement 1:30
- A Call for More Inclusive and Safe Conferences 2:27
- Contact Info and Diving into Dating App Issues 3:02
- The “Like Everybody” Strategy and its Shortcomings 3:18
- Automating the Dating Grind: Preferences and Efficiency 4:05
- Building “Swipr”: From Browser Automation to API Interaction 7:26
- Swipr's Evolution: Reverse-Engineering the Tinder API 8:36
- Swipr in Action: A Demo of the CLI Tool 10:24
- Swipr's Architecture: Modular and Decoupled Design 11:39
- Key Modules: UI, Error Handling, and Logging 12:28
- The Config Module: Reading, Writing, and Validating User Preferences 14:07
- The Network Module: Handling Authentication, Tokens, and API Errors 16:18
- Semantic Error Handling for a Robust API Client 18:29
- The Node.js CLI Ecosystem: Useful Packages for Streamlined Development 20:14
- Future of Swipr: Expanding Criteria, Supporting More Apps, and UI Enhancements 22:22
- Final Thoughts: Fun with Automation, but Dating Still Requires Human Connection 24:22
Transcript
These community-maintained transcripts may contain inaccuracies. Please submit any corrections on GitHub.
Introduction and Speaker Intro - Ramón Guijarro from Undefined Labs0:00
So for this last session, ladies and gentlemen, I would like to introduce to all of you our last speaker for today, which is Mr. Ramón Guijarro. He's the software engineer from Undefined Labs from Spain. He's going to talk about a love story written in JavaScript. ขอเสียงปรบมือต้อนรับคุณ
Hi everybody. How's everyone doing? Okay. Still have some energy left in you for the last talk of the day. Okay, so I'm really happy to be here in Bangkok for the first time in my life. And it's especially exciting because I'm debuting a new talk. And I'm closing the event on top of that, so thanks a lot for having me. As you already know, my name is Ramón and for the next half an hour, I'm going to be talking to you about JavaScript, a love.
What we're basically going to talk about is some problems that current dating apps have. And we're going to see what people do about that and how those behaviors can be automated. And I'm going to show you a tool that I built just for that with JavaScript.
About Ramón: Web Developer, Startup Life, and Community Involvement1:30
But before we get into the topic, let me tell you a little bit about me. So I'm a web developer from Madrid, Spain. That was a long trip, by the way. I work at a small startup called Undefined Labs in which we build developer tools to help teams ship better code faster. And I work primarily with JavaScript and React. And I really like them, but above all, what I like is the web platform in general. I love its promise of an open medium that is accessible to all. And I like the developing community around it as well. I think it's a really nice one and that's partly why I go to conferences and I give talks like this one.
I also volunteer in a coding bootcamp for women in Madrid. So you probably can already tell that I care quite a bit about community, diversity, and inclusion in our industry.
A Call for More Inclusive and Safe Conferences2:27
So let me take a moment to say that one of the things that I would really like to see as part of that is conferences stepping up their game when it comes to these issues. Things like having an enforceable code of conduct so that everybody can feel safe or making an active
effort to feature more diverse speaker lineups. Some conferences are already doing this and that's great, but I would like it to become the norm rather than the exception. And you as attendees actually have the power to change it by demanding these things from the conferences you go to. So let's see if we can do better about that.
Contact Info and Diving into Dating App Issues3:02
If you want to reach out to me later, that's my Twitter account. My DMs are open, so feel free to drop me a line. And with all that being said, let's get started.
The “Like Everybody” Strategy and its Shortcomings3:18
Let's talk about dating apps. What about them? What are the problems? Well, there are a lot of them nowadays. Tinder, of course, is king, but there are many others: OkCupid, Grindr, Bumble, Happn, Hinge, Ship, Match.com.
The list goes on and on. However, most of them works in similar ways to Tinder.
And here's the facts about them. Most popular dating apps are driven by pictures. If you ever use any of them, you know this. And pictures always win over anything else in profiles. Co-founder of OkCupid says that photos drive 90% of the action in online dating.
Automating the Dating Grind: Preferences and Efficiency4:05
But even for apps that rely more on profiles, questions, interests than pictures, it turns out that we don't really know what we want. Match.com did some research on this and they found out that the people we send messages to don't actually match what we say we want. So that's interesting. And then we have some issues that are more specific to men, like the fact that women are much more selective. Tinder researched this and they found out that men are three times as likely to swipe right than women. So there's a 14% chance for a women to swipe right on some guy. And that's not so bad, right? 14% is not that low. Well, while good-looking men can probably do fine with that, the truth is that that 14% goes down a lot for less attractive people. And this is regardless of age. So the truth is that an average man is liked by only 1% of women in these dating apps.
So how does all this make people feel? Well, some people feel like the way apps work promotes superficiality because it's all based on pictures.
They feel that the liking process is arbitrary since we don't really know what we want even when pictures are not involved. And then on top of all this, you are pushed to come back every day to the app because most of them have a daily limit in the number of likes that you can give. So you are forced to come back to the app every day to keep using it in the hope that there will be a match.
So how can automation help with all this? Well, first we need to see what it is that we want to automate. Due to the facts we just saw, some men end up following what we could call the "like everybody" strategy. Since the whole process feels so superficial and so arbitrary, and the chances are so low, it all really feels like grunt work. So some people say might as well like everybody and hope for a match instead of spending time actually assessing every person for a very little return.
So the problem with this approach is that it still takes time, not as much as if you are carefully looking into its profile, but it still requires you to go back to the app every day to swipe. And it also leads to wasting some of your chances since you're swiping basically without looking. Some of your likes will go to profiles without pictures or with empty descriptions or with things that you are not looking for in general.
Building “Swipr”: From Browser Automation to API Interaction7:26
So how can we automate this strategy and make it better?
Well, we want something that does the swiping for us based on some preferences.
This way we have something that is automatic and unattended. We don't need to go back to the app every day, so that saves us time. And it's also more selective because due to those preferences, we can say some things about when we want to not match
with someone.
So, I joined Tinder like a year ago. All my friends were on it and they told me about this like everybody strategy. And I was thinking about it and at some point I thought, "Okay, let's build something, even if just for fun, that does this." So that's what I did and that's what we're going to see now. I built it for Tinder since it's the most popular dating app, but you could do similar things for other apps and we'll talk about that at the end of the talk.
Swipr's Evolution: Reverse-Engineering the Tinder API8:36
So the way the tool started is actually pretty different from what it is today. I learned that Tinder has a web app in addition to the mobile apps. So since I'm a web developer, that's great for me and I knew that Puppeteer was a thing. So I wrote a script with Puppeteer that runs an actual Google Chrome browser and imitates what a real user would do on the Tinder website. Because it is an actual browser, it was a little slow and heavy and that bothered me. So after doing that, which I only got to the login part, didn't do the whole thing, as I was doing it, I thought, "Wait, this is a website, so I could open the developer tools
and look at network requests, maybe, and try to figure out how the web app is working." Because it's a single page application, so it's like doing requests asynchronously and updating the DOM. So I can try to figure out how it works. It turns out that the API is fairly straightforward. It's not obfuscated in any way. I could see the endpoints, the payloads, everything. I should have probably thought about this first before doing the browser automation thing, but you know, I'm stupid, I don't know. And then I thought that I cannot be the first one to think about this because even before the web app existed,
you could still sniff traffic from the Android or the iOS apps. And sure enough, there is some unofficial online documentation about the Tinder API and that helped me finish figuring out some parts that were not clear to me, especially the authentication flow.
Swipr in Action: A Demo of the CLI Tool10:24
And so with all this knowledge, I ended up building a command line tool with Node.js that does requests directly to the Tinder API. So that way can be very fast, doesn't need to run a full headless browser in the background. And this tool is called Swipr. So let's just take a look at how it works. This is a terminal. You run Swipr. It will ask for your login information the first time you get in there, so you need to enter your phone number. You get a text message with a confirmation code. You will enter it as well and with this you're logged in.
And now it goes to the liking criteria part. It asks you for the minimum number of photos that you want in the profiles that you want to match. Let's say five. Then the length of the bio in words
and then if you want to like back or not. And with that we're all set. We're swiping. You can see the emoji means obviously left and right for the swipes and you get the name of the person, their age, and the number of photos. Not really sophisticated, it's a simple thing. It's not complicated, but it's a fun use of technology.
So there's that.
Swipr's Architecture: Modular and Decoupled Design11:39
So how is this built? Let's look a little bit at the internal architecture. So this is more or less what it is.
The way is built is intentionally done by modules
and all of them are decoupled from each other. They could even be their own packages if you wanted to do too. And the nice thing about this is that implementation details are hidden between them. So you can refactor and change things in each of them without affecting the rest, very independently. For example, the configuration module could change from reading from the disk to reading from the network, and the rest of the program wouldn't know as long as you keep the API. And that's a good strategy in general and a good
pattern to follow.
Key Modules: UI, Error Handling, and Logging12:28
So the core of the CLI tool is the UI module.
This is just a main loop with an async/await function. And all it does is call the other different modules, the user input and the logging modules, to get the input from the user and print things to the console. One interesting pattern that I'm using is this try/catch at top level.
What this allows you to do is that in any other module, whenever we have an error that is critical, then we just want to stop the app completely.
What I would do is just throw an error with a meaningful error message. And then I know that in this main loop it is caught and I can print the error message in a nice way, and always consistently with the same format. Because I'm always printing it from here. And I could do some additional things if I wanted, like save a log of the error or something.
Another nice thing that the code does that is interesting is handling API errors in a semantic way.
This is what I was talking about. We have the main module. We are doing stuff and then we are catching the API errors, and throwing errors with specific messages that then will be printed in the main module.
The Config Module: Reading, Writing, and Validating User Preferences14:07
So that helps with error handling. It's a nice way to handle all the error flow in a simple way. Then about the logging module. This is sometimes not something people think much about, but I think it's an important thing. It's just a module that abstracts details away, ensuring consistent message formatting. So it exports semantic functions like 𝚙𝚛𝚒𝚗𝚝𝚃𝚒𝚝𝚕𝚎 or 𝚙𝚛𝚒𝚗𝚝𝙻𝚒𝚔𝚎 or 𝚙𝚛𝚒𝚗𝚝𝚂𝚞𝚌𝚌𝚎𝚜𝚜. So in the case of 𝚙𝚛𝚒𝚗𝚝𝙻𝚒𝚔𝚎, for example, it would print the little emoji left hand with a hand left and right. Or in this case, success or error, it would print the message you pass to it prepended with a tick
or an x. So that way you're guaranteeing that your error
messages or your success messages will be always printed the same way.
Then the config module is interesting as well.
Separated from the rest and it does three things. It reads configuration, writes configuration, and validates. And the validation part is important. If you need to check that the phone number the user enters is an integer and maybe you want to check the length of it. And you could do that on the UI part as you are receiving the user input, but that can get messy because maybe you then have some other UI in which you do that as well. So it's better to have the validation of your config
values where the config is. So we have an 𝚒𝚜𝙲𝚘𝚗𝚏𝚒𝚐𝚅𝚊𝚕𝚒𝚍 function that just does that. So from the UI modules you can call this with the value the user is giving you and check that it is actually a value. And same thing when you're reading config from the disk. Maybe the user modified the config manually and it's not valid. Well, you can use this function to clean that up before returning the response to the rest of the program.
The Network Module: Handling Authentication, Tokens, and API Errors16:18
And then there's the network module, which is probably the most interesting part. It could actually be an independent API client if you just chopped it away from the rest of
the program. And there are a few things that you need to handle here.
Authentication and tokens is probably among the most interesting ones. So, once you are authenticated, you need to append a token with every API token with every request. But that token can expire and so you have your API token and then you have a refresh token. The refresh token allows you to ask for a new API token. So what happens is that at some random point in time, the API token can expire and then you need to use the refresh token to ask for a new one. But it would be a little bit shitty if the app just crashed when the API token expires.
So what you can do is build an API module that does that automatically. So how we could go about that? This is the code that I use in the tool, more or less, a little bit simplified. So this is just a 𝚌𝚛𝚎𝚊𝚝𝚎𝙰𝙿𝙸 function. What it does is it just gets all the API methods and modifies them in this way. You can see that 𝚌𝚊𝚕𝚕𝙼𝚎𝚝𝚑𝚘𝚍 function there. That calls the method with the API token. And then it actually tries to call it and if we get a status code for unauthorized, what we do is that we call a refresh API token function that will get
the new API token and then we call the method again. So this way we can forget about it in the rest of our app. When we call our methods that need authentication, we know that they're going to work and if the API token is expired, we know that we'll get a new one automatically without needing to worry about that.
Semantic Error Handling for a Robust API Client18:29
And then another interesting thing is like all the API methods have semantic errors.
So here, for example, is the method for liking a user. So it does a get request to that /like endpoint with
a user ID and API token. And then it parses the response. And if the remaining likes are falsy, which means zero, instead of just throwing whatever, it throws an 𝙾𝚄𝚃_𝙾𝙵_𝙻𝙸𝙺𝙴𝚂 constant that is exported from the module. So then the nice thing is that from the UI part, you can check these errors and print specific messages. I think we actually saw it before.
There you go. 𝚊𝚙𝚒𝙴𝚛𝚛𝚘𝚛𝚜.𝙾𝚄𝚃_𝙾𝙵_𝙻𝙸𝙺𝙴𝚂, that's the same constant. So from your main method, you can check that and say, "Oh, this is this specific error." And that's good practice in general because the alternative would be to be checking the status code and you would need to be doing weird things like, "Oh, I called the like user method and is returning a 503 and I know that for this method, it means this thing." So it's better to have all this logic inside your API methods. So then you don't worry about it. If the Tinder API changes tomorrow and instead of returning a likes remaining key with a zero, it returns an error with some status code, it doesn't matter. You just change this method and the rest of your program doesn't need to care about it.
So that's nice.
The Node.js CLI Ecosystem: Useful Packages for Streamlined Development20:14
And you know, the nice thing about using Node to build CLIs like this is that there's actually a pretty good ecosystem of packages for it. Some of the things that I did manually, there are actually nice libraries for it, free license ones, but again, I'm stupid, so don't be me and use them. Like, don't do all these things manually.
We have tools like Inquirer that I'm actually using, that is great for user input. It allows you to ask the user for things in a lot of different ways and it formats everything for you really nicely. So not only just strings, but you can print list of options for the user to choose with multiple selection, a lot of things. It's really good and the API is super nice. So that's definitely a must if you're building a CLI tool. Then there's modules like Chalk that allows you to
print output with coloring and bold and all those
nice things that you want to see. And then for API, there's a really nice module called apisauce that is based on Axios, which is a pretty popular HTTP client for JavaScript. But apisauce also handles errors in a similar way to what we've seen. It has constants based on status code. So instead of you having to go and check the status code, it will just return an object with an error field with a constant for the status code, like unauthorized or whatever. And it also has a way to do transformations on requests and responses, which is super useful because you can define, for example, a transformation on every request to inject your API token, so you don't need to handle that manually. Similar to what we've seen before in my example, but it's all taken care of by the library. So this is nice one. And then we have modules to help us with configuration,
like .env that helps you read environment variables
Future of Swipr: Expanding Criteria, Supporting More Apps, and UI Enhancements22:22
if your CLI tool needs to read them for config. And Conf, which basically handles configuration files for you. It will write your configuration to an appropriate file depending on the user operating system and everything is taken care of. And then you have other tools that are more comprehensive. If you want actual toolkits or frameworks, they include a lot of the functionalities that we've seen before under single packages. Gluegun and Oclif are two examples of that.
So, Let's finish with some ideas for the future of this tool.
First, of course, adding more liking criteria. Right now, it's very basic. So, you could add a blacklist or a whitelist of words that you want to see in bios. You could do image analysis in order to check that because users can have like two or three pictures, but maybe they are not their face, or they're not actual faces, they're just cartoons or something. That's fairly common. So, you could use image analysis to check if there are actual faces in the pictures. Or maybe set the percentage of people to like because
depending on the algorithm of the dating app, you might not want to like absolutely everybody. Support other dating apps either with,
by looking at network requests of web apps. Bumble, for example, recently released a web app, so you can look into that. Or you can also do traffic sniffing in mobile apps. And also, we could turn this into a website. We would need to store the credentials for the users, but you know, it's doable. Or even an Electron app with a background process that does this for you. There are a lot of things that you can do to improve this tool, or at least to keep expanding it.
Final Thoughts: Fun with Automation, but Dating Still Requires Human Connection24:22
So, some final thoughts about all this. Well, we had, or at least I had, a lot of fun finding out the different ways to do this, the pros and cons, figuring out the APIs of the dating app. We also learned that building CLIs with Node is not only possible, but actually nice and enjoyable. It's a very nice experience. However, did we really solve the dating app problem?
Definitely not. 53 matches, 83 people he begun talking, nine agreed to a date, three stood him up, two canceled, leaving him with four actual dates. This is just an example of a real user experience. So, the tool is just a way to skip some of the grunt work and focus on the actually difficult and interesting part, which is talking to other people and actually meeting them. Because at the end of the day, humans will be humans. Thank you.
Thank you for sharing your experience with us.