Intro
While working I usually like to annoy my friends on WhatsApp with cheeky GIFs, but the web whatsapp version doesn’t support sending custom GIFs. What if we could extend the gif search feature and feed the app with our own GIFs?
Goal
The goal is to setup a proxy using Titanium which will redirect the giphy requests to our own backend. The backend will then return our own customized response which will contain giphy links to our own GIFs. We have to upload the GIFs on giphy as the app won’t allow loading GIFs from an untrusted source.
Analysis
To analyze the HTTP traffic we can use several tools such as Chrome Developer Tools, Fiddler or mitmproxy, we’ll stick to Chrome this time. Let’s see what happens when we search for a gif!
Seems simple enough. WhatsApp sends a request to the giphy api with the search term and other parameters(rating, language). Let’s take a look at the response.
The response itself is bigger at first, but the data structure seems simple enough; a collection with GIFs that contain images with certain sizes. Let’s take a look at the next requests to find out which image property is used for displaying the preview in the search.
That request URL tells us everything we need to know, it contains giphy-preview.webp so the images collection should have a similar named property?
Yay! Enough information collected about the preview, let’s do the same and click on a gif now.
Same story, the url contains giphy-downsized-small.mp4 and we look at the response again and find the property.
Easy.
So now that we know that those two properties are the ones we need. Let’s start building our backend!
Backend Implementation
We’ll need to persist our local GIFs somewhere; as we need to store the giphy uploaded link. We will also need an API that will read from the db and return the appropriate results to our proxy, which then returns the results to the web app. Since my cheeky collection consists of actual GIFs, we’ll need a way to convert those to webp which is required for the preview. The final requirement would be uploading the gif to giphy.
Response model
To generate our model classes from the json repsonse we will use quicktype. After few manual changes this is the result:
Persistence
We will need to be able to persist a gif entity which will contain the information for the preview_webp and downsized_small property. Best we find some lightweight embeded db which we can setup quickly. I decided on using LiteDB. After taking a quick glance over their github, the usage seemed simple enough.
The implementation is definitely production ready! :P
Giphy client
As the mobile/web app isn’t loading GIFs from untrusted sources, we’ll have to upload our GIFs to giphy and then persist the ID which we will use later on. Let’s take a look at the giphy developer documentation. No biggie, just the usual multipart/form-data request. If you ever need to do anything related to http requests, stick to the brilliant Flurl library which we also will be using.
Our simplistic implementation will look like this:
To obtain an auth key you will need to register on giphy.
Converting gif to webp
We need to convert our local GIFs to webp, as those are used in the preview property. For this task we’ll just use ffmpeg. After some quick google search, the required arguments are the following ones:
ffmpeg -i our.gif our.webp
Easy! Let’s write a service which that will deal with the converting process, we’ll be using IHostedService for our service implementation. With the ASP.NET Core 3.0 Release services implementing IHostedService are ran before the app actually starts.
A bit messy, but hey it gets the job done! So basically we get the required services from our container, find the gif files from our “gifs” folder. Once we have collected the files, we check if the webp file already exists, if it doesn’t then we start converting the gif to webp and upload the gif to giphy, once we get a result back from the giphy API we persist the changes.
Controller
Let’s finish this! All we need is two GET routes. One that will return our local gifs based on the search term, and one that will return the webp image itself for the preview. We just inject our GifStorageContext, find the gifs based on the search term and build the response model. The size property is required as WhatsApp doesn’t allow videos larger than 64 MB.
Titanium Implementation
Titanium has an example on their github page which we will just copy and adjust to our needs.
The OnRequest method is where all the magic happens. We have two checks built in because we only want to handle giphy search requests, we have to add the origin header as otherwise we would be getting CORS errors. Once that part is done, we send a request to our backend with the search term and return our own response by calling Ok() to the web app.
Result
Let’s try embeding the following gif!
Place the gif in the “gifs” folder, start the API project and then the Proxy project. If everything has worked as expected, we can see that the search will only display one gif, and that is our own! Yeah, we made it. We have successfully fooled WhatsApp into displaying and sharing our own GIFs. Hope you’ve learned something through this pointless exercise! :)