Look What You Made Me Do, Chrome

How to use Chrome Developer Tools to get tickets to Taylor Swift’s next concert

For her upcoming concert, Taylor Swift partnered with Ticketmaster to ensure that only legitimate fans can buy tickets. I’d like to say that I’m a true fan who will do the honest work to get a ticket… but I am also a woman with a computer and I like a challenge.

I ended up having a lot of fun exploring Chrome Developer Tools and I wanted to share what I learned. Here’s what we’ll cover in this post:

  • How to send code through the Console tab
  • How to use the Network tab to find relevant activity
  • XHR breakpoints
  • Putting this all together to create fake user activity

…Ready For It? (Background)

In the middle of the lines, in your source
I know I’m gonna set a breakpoint
So I take my time
Are you ready for it?

Before we get started, I want to remind you that automating access to Taylor Swift’s site is against her Terms of Use. If you try this at home, you might lose your chance to attend her concert at all.

In Ticketmaster’s system, you have to log onto tickets.taylorswift.com and participate in activities in order to gain boosts. The more boosts you have, the more likely you are to get a chance to buy a ticket. Activities include buying the album and merchandise, posting on social media, and watching her videos.

Taylor Swift’s Ticketmaster Verified Fan portal

Taylor Swift’s Ticketmaster Verified Fan portal

Of course, we don’t want to spend a ton of money or give Taylor Swift access to our social media accounts, so we’re left with viewing the music videos. You can view each video up to 20 times. Our goal is to write a script that will convince the Taylor Swift / Ticketmaster servers that we “watched” her music video without having actually sat through the hot mess that is “Look What You Made Me Do.”

Because it totally makes sense that we’d put in all this effort to not listen to Taylor Swift’s music in order to attend her concert, right?

Everything Has Changed (Elements and Console)

’Cause all I know is we opened Chrome
And your code looks like coming home
All I wrote is some jQuery, everything has changed

One of my first thoughts when I was playing with the site was, “I wish I could speed up her video by 2x so I could get through this more quickly.” Modified speed is a feature in the settings of videos on youtube.com, but it isn’t shown in embedded videos.

video on youtube.com with speed options

video on youtube.com with speed options

Luckily, a search for “youtube embedded video change speed” yielded a StackOverflow post describing exactly what I wanted to do. The StackOverflow response’s code uses jQuery to select the video’s IFrame and then POSTs a message to it to set the speed using Youtube’s API:

var playbackRate = 2;
var data = {
  event: 'command',
  func: 'setPlaybackRate',
  args: [playbackRate, true]
var message = JSON.stringify(data);
$('#iframe1')[0].contentWindow.postMessage(message, '*');
JavaScript pasted inside tickets.taylorswift.com with error message

JavaScript pasted inside tickets.taylorswift.com with error message

As shown in the screenshot above, we get an error. This error is because she probably isn’t using #iframe1 as her IFrame’s ID. Now we can use the Elements tab to find the ID for her Youtube IFrame. Here are some ways you could find the ID, in case you haven’t used the Elements tab before:

  • Command/ctrl + f and search for “iframe”. Note that there are multiple IFrames in the site, so you have to do a bit of reading to figure out which one contains the video. It’s the one that links to youtube.com as its source.
the iframe for the embedded youtube video

the iframe for the embedded youtube video

  • You can also enter “Inspect Element Mode” by using command/ctrl + shift + c and then click on the Youtube video. I use this hotkey most frequently to click directly on what I’m interested in.
  • In general, you can right-click on elements and select “Inspect,” but this doesn’t work on Youtube videos. Try it out on other elements of the site, though!

Whatever you do, you should see that the ID for the Youtube IFrame is actually #frame and you can change the StackOverflow code to reflect that. Press play on the music video to listen to Taylor Swift in chipmunk form. You can now get boosts twice as fast but with a lot more clicking and typing than before.

This doesn’t sound like a whole lot of progress, but I wanted to include it as an example of how to run your own code inside another site and how to navigate elements. This is the main reason I open Developer Tools on a regular basis, and it’s worth learning about if you haven’t used the tools before.

How You Get the Girl (Examining Network Activity)

And then you say, I want your endpoints for worse or for better
I would wait forever and ever
Broke your site, I’ll put the AJAX back together
I would wait forever and ever

So we’ve figured out that we can manually watch the music video faster than the average visitor, but that’s not good enough. The next thing I wanted to do was figure out how the site records a view. If we know how the site communicates to the server that a view was completed, we can try to send the same messages back to the server.

When I open the Network tab and play the music video, I get a flood of network activity.

a flood of network activity

a flood of network activity

This makes sense because we are streaming a video from Youtube and of course Google want to keep track of our activity. The first time I looked here, I scrolled through each of the rows trying to find anything that looked relevant- definitely a needle in a haystack situation. I played her video so many times trying to see when new activity would show up!

Then I noticed all the filters at the top! At the top left, you can add in text to filter by. For example, “domain:tickets.taylorswift.com” will only show network activity going to Taylor’s site, rather than to Youtube or Google’s DoubleClick servers. You can also select “XHR” to ignore requests for resources like images and CSS from her site. “XHR” stands for XMLHttpRequest, which means something something this is what you use when you’re making an AJAX call. *hand waving because I don’t really know that much about internet stuff* The point is, we care about XHR activity and none of the other types of activity.

network activity filtered for domain:tickets.taylorswift.com

network activity filtered for domain:tickets.taylorswift.com

So when we filter by “domain:tickets.taylorswift.com” and XHR, we see some interesting activity. There’s an endpoint named count!

(If you don’t see it, make sure you’ve started the music video and let it play for about 2 minutes. For the sake of time, I won’t cover how to find this, but basically you can read through the code and find where it checks for how much of the video you’ve loaded.)

Click on the entry for “count” to see its headers and response.

HTTP headers for /watch/count endpoint

HTTP headers for /watch/count endpoint

In the “General” section, we can see that this was a successful POST to https://tickets.taylorswift.com/watch/count. In the “Response” tab at the top (you can see it next to Headers and Preview), you should see a JSON response saying {“success”: true}.

The funny thing is that I poked her site so much that I can’t share a screenshot of the response tab, because I only get {“success”: false} now. But that’s okay because we’re doing this for educational purposes.

Anyway, now that we know the endpoint and format that the site uses to record counts, why don’t we try sending this network request again and seeing what happens?

Right-click on the “count” row on the left side and go to Copy > Copy as cURL. Isn’t this amazing!? I didn’t know I could copy a network request into a cURL command before I started on this project! I was going to manually type out all the headers before someone stopped me and pointed out this awesome feature.

how to copy a record of network activity as a cURL command

how to copy a record of network activity as a cURL command

Now we can open a terminal and enter the cURL command with reckless abandon. Unfortunately, you will not get a {“success”:true} response!

the cURL command failed

the cURL command failed

The reason is that Ticketmaster has, of course, accounted for the fact that someone might just copy the same network request and enter it over and over. They added a unique ID to each request! You can see it in the Form Data a few screenshots above, or towards the end of the cURL command you pasted.

Jump Then Fall (XHR Breakpoints)

Whoa, oh, I need you breakpoints
Don’t be afraid, please
Run then break, run then break into me

Without the unique ID attached to each request, we can’t send valid views to the /watch/count endpoint. At this point, I was stumped. Where was this ID coming from?

I wanted to get more information about when the /watch/count call is made, so I poked around online to see if I could somehow get a stacktrace or any other information about an AJAX call. That’s how I found out that in addition to normal and conditional breakpoints, you can set breakpoints on many different interactions!

set a XHR breakpoint for /watch/count

set a XHR breakpoint for /watch/count

You can break on a lot of different events in the “Event Listener Breakpoints” menu, but that’s not what we’re interested in today. Instead, we want to add an “XHR Breakpoint” and break when the URL contains “/watch/count”.

When we play the video again, the debugger will start when the network request happens.

XHR breakpoint triggered

XHR breakpoint triggered

On the right side, you’ll see that we’re in JavaScript land, as shown by the VM11101:1 instead of a normal file name. This isn’t the most helpful place to be, since we want to be reading the code written by the Ticketmaster developers, not by Chrome. My usual strategy at this stage is to look through the call stack until I find a filename that looks familiar. If you reach this point and scroll down a little, you’ll start seeing more familiar method and file names! I saw postMessage from watch?video_id=music_video and clicked on it to see if I could find anything interesting.

stepped through the call stack for the breakpoint and found the unique ID

stepped through the call stack for the breakpoint and found the unique ID

Jackpot! Line 16 shows the raw ID we were looking for! It looks like every time the server serves this page, it includes the ID directly in the template. So if we make a request for this page, we should get a new ID each time. You can confirm this by refreshing the page and seeing line 16’s value change each time.

At this point, you might be thinking, if we can swap out the ID each time we make our /watch/count cURL request, the server might accept our count. Thanks to my breakpoint, this ID had not yet been used, so how would the server know any different? So if I make a modified cURL request now with the fresh ID, maybe it would work. And it did! I got a {“success”:true} response!

I thought I was done! But, as any Fearless fan knows, it’s never simple, never easy. Never a clean breakpoint, no one here to save state. Luckily, Chrome is the only thing I know like the back of my hands. (I’m sorry, I’ll stop. That’s a lie. I’ll never stop.)

At this point, I decided to try making GET requests for this data using the same Copy as cURL trick we used earlier. But what I noticed is that when I copied the ID from the cURL output and tried POSTing it to /watch/count, it wouldn’t work!

The Moment I Knew (Miscellany)

As I’m looking around the code,
But there was one thing missing,
And that was the moment I knew.

I don’t have super encouraging tips and tricks for how I eventually figured out what I was missing. I took a few days to stop thinking about this project, shared my findings with some friends, and eventually I landed on the right answer. I tried all the techniques I showed above, but with perseverance. I wrote breakpoints, walked through a bunch of code, and pored over the network logs. Sometimes, that’s just what it takes to find a solution.

Eventually, I noticed that each time the video is started, a POST is sent to /watch/start and it sends back a {“success”: true}.

filtered network activity shows /watch/start as an entry whenever a video is played

filtered network activity shows /watch/start as an entry whenever a video is played

I didn’t think this could possibly be relevant, but I tried requesting a fresh ID, POSTing to /watch/start with it, and then waiting a few minutes before POSTing to /watch/count. And finally I got my real successful response! That was how they were preventing random people from POSTing fake views!

<deep voice> I’m in. </deep voice>

Shake It Off (Final Tips)

Heartbreakers gonna break, break, break, break, break
And the fakers gonna fake, fake, fake, fake, fake
Baby, I’m just gonna shake, shake, shake, shake, shake

I’m not going so far as to share the script I wrote to automatically POST to the site. I have two reasons: first, I don’t want to piss off Taylor Swift more than I may already have (hi Taylor I love you), and second, I got rate-limited by her site and I have a suspicion that my account was shadow-banned for bad activity because I no longer get successful views even when watching the music video normally. I don’t want to do that to you! (To be fair, I got greedy and tried sending a request every minute. I don’t think I would have been flagged if I waited longer or in more varied increments. Or maybe refreshed my cookies or something. I made a new account, whatever.)

While it’s probably not a good idea to try this yourself, I do want to mention some great tools that helped me build my script that you might find useful for future work:

  • Python’s requests library: Don’t subject yourself to bash when you have a totally reasonable HTTP library at your hands.
  • Pythex: There are a ton of regular expression helpers out there and they all offer similar features. I like this one because it will show you the value of regex matching groups that Python would return. This was helpful for parsing out the raw unique ID.
  • curl.trillworks.com: You can paste a cURL command in here and it’ll spit it back out in Python code! So handy!

I hope I shared at least one Chrome tip that helps you in your future debugging adventures! Or, if you knew all of this already, I hope you at least enjoyed learning about Taylor Swift and this application of Chrome to a real-world problem.

Originally published on Medium.

Leave a Reply

Your email address will not be published. Required fields are marked *