1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
use anyhow::Result;
use serde::Deserialize;

const TWITCH_API_BASE_URL: &str = "https://addons-ecs.forgesvc.net/api/v2";

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Addon {
    pub id: f64,
    pub name: String,
    pub authors: Vec<Author>,
    pub attachments: Vec<Attachment>,
    pub website_url: String,
    pub game_id: f64,
    pub summary: String,
    pub default_file_id: f64,
    pub download_count: f64,
    pub latest_files: Vec<File>,
    pub categories: Vec<PartialCategory>,
    pub status: u32,
    pub primary_category_id: u32,
    pub category_section: CategorySection,
    pub slug: String,
    pub game_version_latest_files: Vec<GameVersionFile>,
    pub is_featured: bool,
    pub popularity_score: f64,
    pub game_popularity_rank: f64,
    pub primary_language: String,
    pub game_slug: String,
    pub game_name: String,
    pub portal_name: String,
    pub date_modified: String,
    pub date_created: String,
    pub date_released: String,
    pub is_available: bool,
    #[serde(rename = "isExperiemental")]
    pub is_experimental: bool,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GameVersionFile {
    pub game_version: String,
    pub project_file_id: f64,
    pub project_file_name: String,
    pub file_type: u16,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CategorySection {
    pub id: u32,
    pub game_id: u32,
    pub name: String,
    pub package_type: u32,
    pub path: String,
    pub initial_inclusion_pattern: String,
    pub extra_include_pattern: Option<String>,
    pub game_category_id: u16,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PartialCategory {
    pub category_id: u32,
    pub name: String,
    pub url: String,
    pub avatar_url: String,
    pub parent_id: u32,
    pub root_id: u32,
    pub project_id: f64,
    pub avatar_id: u16,
    pub game_id: u32,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Author {
    pub name: String,
    pub url: String,
    pub project_id: f64,
    pub id: f64,
    pub project_title_id: Option<f64>,
    pub project_title_title: Option<String>,
    pub user_id: f64,
    pub twitch_id: f64,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Attachment {
    pub id: f64,
    pub project_id: f64,
    pub description: String,
    pub is_default: bool,
    pub thumbnail_url: String,
    pub title: String,
    pub url: String,
    pub status: u16,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct File {
    pub id: f64,
    pub display_name: String,
    pub file_name: String,
    pub file_date: String,
    pub file_length: u32,
    pub release_type: u16,
    pub file_status: u16,
    pub download_url: String,
    pub is_alternate: bool,
    pub alternate_file_id: u32,
    pub dependencies: Vec<File>,
    pub is_available: bool,
    pub modules: Vec<Module>,
    pub package_fingerprint: f64,
    pub game_version: Vec<String>,
    pub sortable_game_version: Vec<SortableGameVersion>,
    pub install_metadata: Option<String>,
    pub changelog: Option<String>,
    pub has_install_script: bool,
    pub is_compatible_with_client: bool,
    pub category_section_package_type: u16,
    pub restrict_project_file_access: u16,
    pub project_status: u16,
    pub render_cache_id: f64,
    pub file_legacy_mapping_id: Option<f64>,
    pub project_id: f64,
    pub parent_project_file_id: Option<f64>,
    pub parent_file_legacy_mapping_id: Option<f64>,
    pub file_type_id: Option<f64>,
    pub expose_as_alternative: Box<Option<File>>,
    pub package_fingerprint_id: f64,
    pub game_version_date_released: String,
    pub game_version_mapping_id: f64,
    pub game_version_id: u32,
    pub game_id: u32,
    pub is_server_pack: bool,
    pub server_pack_file_id: Option<u32>,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Module {
    #[serde(rename = "foldername")]
    pub folder_name: String,
    pub fingerprint: f64,
    #[serde(rename = "type")]
    pub module_type: u16,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SortableGameVersion {
    pub game_version_padded: String,
    pub game_version: String,
    pub game_version_release_date: String,
    pub game_version_name: String,
}

impl Addon {
    pub async fn from_id(client: &reqwest::Client, id: u64) -> Result<Self> {
        let addon = client
            .get(&*format!("{}/addon/{}", TWITCH_API_BASE_URL, id))
            .send()
            .await?;
        let addon: Self = addon.json().await?;

        Ok(addon)
    }
}

#[derive(Deserialize, Debug)]
pub struct Category {
    pub id: f64,
    pub name: String,
    pub slug: String,
    pub avatar_url: String,
    pub date_modified: String,
    pub parent_game_category_id: f32,
    pub root: f32,
    pub game_id: f32,
}

impl Category {
    pub async fn from_id(client: &reqwest::Client, id: u64) -> Result<Self> {
        let category = client
            .get(&*format!("{}/category/{}", TWITCH_API_BASE_URL, id))
            .send()
            .await?;
        let category: Self = category.json().await?;

        Ok(category)
    }

    pub async fn section_from_id(client: reqwest::Client, id: u64) -> Result<Vec<Self>> {
        let section = client
            .get(&*format!("{}/category/section/{}", TWITCH_API_BASE_URL, id))
            .send()
            .await?;
        let section: Vec<Category> = section.json().await?;

        Ok(section)
    }
}

pub fn build_api_ready_client() -> Result<reqwest::Client> {
    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert(
        "Accept",
        reqwest::header::HeaderValue::from_static("application/json"),
    );
    let client = reqwest::Client::builder()
        .user_agent("twitch_app_api_rs/0.1.3")
        .default_headers(headers)
        .build()?;
    Ok(client)
}