RUST - API for Influencers
Tags: rust
Published: 2020-03-20 08:47:09


## Table Of Contents
   * [English version](#english)
   * [French version](#french)



## English <a name="english"></a>


### Introduction

I had to make a Browser Extension that notifies the user when a new video is posted and when the [Twitch](https://twitch.tv) streamer launch a stream.

For this extension I needed to set up an API to centralize my requests to the APIs of [Google](https://developers.google.com/youtube/v3/) and [Twitch](https://dev.twitch.tv/).

I decided to innovate a bit, and to make this API in Rust rather than with NodeJS or PHP. With that, I decided to go further and separate my Twitch and Youtube requests in two different CLI applications. 

My main application communicates with my two sub-applications with   [`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html).

### Twitch  *&&* Youtube CLI application
My goal was to make my queries to the Twitch API and the Youtube API with its two applications, which, once the queries were made, returned the results. 
Results that I retrieve in my main application.

Here is an example to retrieve the last posted Youtube video: 
```rust
async fn get_last_video(&self, channel_id: String) -> Result<serde_json::Value, reqwest::Error> {
	let base_url: String = "https://www.googleapis.com/youtube/v3/search".to_owned();
	let client = reqwest::Client::new();
	let resp = client.get(format!("{}?part=snippet&channelId={}&maxResults=1&order=date&type=video&key={}", &base_url, &channel_id, &self.youtube_api_key).as_str()).send().await?;
	let text = resp.text().await?;
	let video: serde_json::Value = serde_json::from_str(&text).unwrap();
	Ok(video)
}
```

In this example, I use [serde_json](https://crates.io/crates/serde_json) and [reqwest](https://crates.io/crates/reqwest).

### Main application
To set up my API I used [actix-web](https://crates.io/crates/actix-web) where I could define my routes like this:
```rust
use crate::api_error::ApiError;

use actix_web::{get, web, HttpResponse};
use std::fs;

#[get("/")]
async fn get_home() -> Result<HttpResponse, ApiError> {
    let error = ApiError::new(404, "Error".to_owned());
    Ok(HttpResponse::Ok().json(error))
}
#[get("/twitch/stream")]
async fn get_twitch() -> Result<HttpResponse, ApiError> {
    let file_data = read_file("paranoi4k_twitch.json".to_owned()).await;
    let serialized: serde_json::Value = serde_json::from_str(&file_data.as_str()).unwrap();

    Ok(HttpResponse::Ok().json(serialized))
}
pub fn init_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(get_home);
    cfg.service(get_twitch);
}
```
We can see that for the `/twitch/stream` route, I retrieve data in a `paranoi4k_twitch.json` file at the root of my application.
Indeed, when I use my Twitch CLI, I save the data it returns to me in a json file, so that I can retrieve it in my route whenever I want.

This is how I save my json file: 
```rust
    pub fn check_stream(&self) -> std::io::Result<()> {
        let path = self.make_path();
        let app_command = format!("isonlive-user={}", &self.uid); 
    
        let output = Command::new(path)
            .arg(app_command)
            .output()
            .unwrap();
        let encoded = String::from_utf8_lossy(output.stdout.as_slice());
        let serialized: serde_json::Value = serde_json::from_str(&encoded).unwrap();

        let mut buffer = File::create(&self.filename).unwrap();
        buffer.write_fmt(format_args!("{}", serialized)).unwrap();

        Ok(())
    }
```
When I retrieve data from my CLI application, it is displayed as bytes, so I have to use the `from_utf8_lossy` method to convert to a string.

And Voilà, that's basically all I set up for this Api, it was more of a personal challenge in the end.

### Extension
For my web extension written in JavaScript, I have a function like this to call my API : 
```javascript
    /**
     * @param {string} socialname
     * @return {output} { status: false, data: false }
	 * @exemple let request = await rq("twitch");
     */
    async rq(socialname) {
        let output = { status: false, data: false };
        let url = socialname === "twitch" ? this.twitch_url : this.youtube_url;
        try {
            let response = await fetch(url, {
                headers: {
                    "Content-Type": "application/json"
                },
                method: "GET"
            });
            if (response.ok) {
                let responseTarget = await response.json();
				output.status = true;

                if (socialname === "youtube") {
                    output.data = responseTarget.items[0];
                } else if (socialname === "twitch") {
                    output.data = responseTarget.data;
                }
            } else {
                output.status = false;
                output.data = "Problème de serveur, vous pouvez me contacter ici twitter.com/zaekof";
            }
        } catch (error) {
            output.status = false;
            output.data = error;
        }
        return output;
    }
```

I think I've explained the main thing, don't hesitate to correct me and make constructive criticism :)


Useful links:
[RustYoutubeCli](https://github.com/loicngr/RustYoutubeCli)
[RustTwitchCli](https://github.com/loicngr/RustTwitchCli)
[InfluencerApi](https://github.com/loicngr/InfluencerApi)

----------------------------------------------------------
## French <a name="french"></a>

### Introduction

J'ai eu à réaliser une Extension pour navigateur qui avertit l'utilisateur quand une nouvelle vidéo est postée et quand le streamer [Twitch](https://twitch.tv) lance un live.

Pour cette extension j'ai eu besoin de mettre en place une API pour centraliser mes requêtes vers les API de [Google](https://developers.google.com/youtube/v3/) et de [Twitch](https://dev.twitch.tv/).

J'ai alors décidé d’innover un peu, et de faire cette API en Rust plutôt qu'avec du NodeJS ou du PHP. Et en plus de ça, j'ai décidé d'aller plus loin et de séparer mes requêtes Twitch et mes requêtes Youtube dans deux sou applications CLI différentes.
Mon application principal communique avec elles avec [`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html).

### Twitch  *&&* Youtube CLI application
Mon but était de faire mes requêtes vers l'API de Twitch et l'API de Youtube via ses deux applications, qui une fois les requêtes effectuées me retourne les résultats. 
Résultats que je récupère dans mon application principale.

Voici un exemple pour récupérer la dernière vidéo Youtube posté : 
```rust
async fn get_last_video(&self, channel_id: String) -> Result<serde_json::Value, reqwest::Error> {
	let base_url: String = "https://www.googleapis.com/youtube/v3/search".to_owned();
	let client = reqwest::Client::new();
	let resp = client.get(format!("{}?part=snippet&channelId={}&maxResults=1&order=date&type=video&key={}", &base_url, &channel_id, &self.youtube_api_key).as_str()).send().await?;
	let text = resp.text().await?;
	let video: serde_json::Value = serde_json::from_str(&text).unwrap();
	Ok(video)
}
```

Dans cette exemple, j'utilise [serde_json](https://crates.io/crates/serde_json) et [reqwest](https://crates.io/crates/reqwest).

### Application principal
Pour la mise en place de mon API j'ai donc utilisé [actix-web](https://crates.io/crates/actix-web) ou j'ai pu définir mes routes comme ceci :
```rust
use crate::api_error::ApiError;

use actix_web::{get, web, HttpResponse};
use std::fs;

#[get("/")]
async fn get_home() -> Result<HttpResponse, ApiError> {
    let error = ApiError::new(404, "Error".to_owned());
    Ok(HttpResponse::Ok().json(error))
}
#[get("/twitch/stream")]
async fn get_twitch() -> Result<HttpResponse, ApiError> {
    let file_data = read_file("paranoi4k_twitch.json".to_owned()).await;
    let serialized: serde_json::Value = serde_json::from_str(&file_data.as_str()).unwrap();

    Ok(HttpResponse::Ok().json(serialized))
}
pub fn init_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(get_home);
    cfg.service(get_twitch);
}
```
On peut voir que pour la route `/twitch/stream`, je récupère des données dans un fichier `paranoi4k_twitch.json` à la racine de mon application.
En effet, quand je fais appel à mon Twitch CLI, j'enregistre les données qu'il me retourne dans un fichier json, de manière à pouvoir les récupérer dans ma route quand je veux.

Voici comment j'enregistre mon fichier json : 
```rust
    pub fn check_stream(&self) -> std::io::Result<()> {
        let path = self.make_path();
        let app_command = format!("isonlive-user={}", &self.uid); 
    
        let output = Command::new(path)
            .arg(app_command)
            .output()
            .unwrap();
        let encoded = String::from_utf8_lossy(output.stdout.as_slice());
        let serialized: serde_json::Value = serde_json::from_str(&encoded).unwrap();

        let mut buffer = File::create(&self.filename).unwrap();
        buffer.write_fmt(format_args!("{}", serialized)).unwrap();

        Ok(())
    }
```
Quand je récupère les données de mon application CLI, celles-ci sont affichées sous forme de bytes, je dois donc utiliser la méthode `from_utf8_lossy` pour convertir en String.


Voilà en gros un peu tout ce que j'ai mis en place pour cette Api, c'était plus un défi personnel au final.

### Extension
Dans le cadre de mon extension web écrite en JavaScript, j'ai une fonction comme ceci pour faire appel à mon API : 
```javascript
    /**
     * @param {string} socialname
     * @return {output} { status: false, data: false }
	 * @exemple let request = await rq("twitch");
     */
    async rq(socialname) {
        let output = { status: false, data: false };
        let url = socialname === "twitch" ? this.twitch_url : this.youtube_url;
        try {
            let response = await fetch(url, {
                headers: {
                    "Content-Type": "application/json"
                },
                method: "GET"
            });
            if (response.ok) {
                let responseTarget = await response.json();
				output.status = true;

                if (socialname === "youtube") {
                    output.data = responseTarget.items[0];
                } else if (socialname === "twitch") {
                    output.data = responseTarget.data;
                }
            } else {
                output.status = false;
                output.data = "Problème de serveur, vous pouvez me contacter ici twitter.com/zaekof";
            }
        } catch (error) {
            output.status = false;
            output.data = error;
        }
        return output;
    }
```

Je pense vous avoir expliqué le principal, n'hésitez pas à me corriger et à faire des critiques constructives :)


Liens utiles :
[RustYoutubeCli](https://github.com/loicngr/RustYoutubeCli)
[RustTwitchCli](https://github.com/loicngr/RustTwitchCli)
[InfluencerApi](https://github.com/loicngr/InfluencerApi)



[Go to the TOP](#english)