litchee/api/gameplay/games/
export.rs1use futures_util::stream::BoxStream;
5use reqwest::Method;
6use reqwest::header::ACCEPT;
7use serde::Serialize;
8
9use super::model::LichessGame;
10use crate::client::LichessClient;
11use crate::config::Host;
12use crate::error::Result;
13use crate::http;
14use crate::http::{ACCEPT_JSON, ACCEPT_NDJSON, ACCEPT_PGN};
15use crate::model::GameExportOptions;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub enum GameSort {
21 DateAsc,
23 DateDesc,
25}
26
27#[derive(Debug)]
29pub struct GameExportRequest<'a> {
30 client: &'a LichessClient,
31 game_id: &'a str,
32 filters: SingleGameFilters,
33 export: GameExportOptions,
34}
35
36#[derive(Debug, Default, Serialize)]
38struct SingleGameFilters {
39 #[serde(rename = "withBookmarked", skip_serializing_if = "Option::is_none")]
40 with_bookmarked: Option<bool>,
41}
42
43impl<'a> GameExportRequest<'a> {
44 pub(crate) fn new(client: &'a LichessClient, game_id: &'a str) -> Self {
46 Self {
47 client,
48 game_id,
49 filters: SingleGameFilters::default(),
50 export: GameExportOptions::default(),
51 }
52 }
53
54 #[must_use]
56 pub fn export(mut self, options: GameExportOptions) -> Self {
57 self.export = options;
58 self
59 }
60
61 #[must_use]
63 pub fn with_bookmarked(mut self, value: bool) -> Self {
64 self.filters.with_bookmarked = Some(value);
65 self
66 }
67
68 pub async fn json(self) -> Result<LichessGame> {
70 let path = format!("/game/export/{}", http::segment(self.game_id));
71 let request = self
72 .client
73 .request(Method::GET, Host::Default, &path)
74 .header(ACCEPT, ACCEPT_JSON)
75 .query(&self.filters)
76 .query(&self.export);
77 http::json(request, "LichessGame").await
78 }
79
80 pub async fn pgn(self) -> Result<String> {
82 let path = format!("/game/export/{}", http::segment(self.game_id));
83 let request = self
84 .client
85 .request(Method::GET, Host::Default, &path)
86 .header(ACCEPT, ACCEPT_PGN)
87 .query(&self.filters)
88 .query(&self.export);
89 http::text(request).await
90 }
91}
92
93#[derive(Debug)]
95pub struct UserGamesRequest<'a> {
96 client: &'a LichessClient,
97 username: &'a str,
98 query: UserGamesQuery<'a>,
99 export: GameExportOptions,
100}
101
102#[derive(Debug, Default, Serialize)]
105struct UserGamesQuery<'a> {
106 #[serde(skip_serializing_if = "Option::is_none")]
107 max: Option<u32>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 since: Option<i64>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 until: Option<i64>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 vs: Option<&'a str>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 rated: Option<bool>,
116 #[serde(rename = "perfType", skip_serializing_if = "Option::is_none")]
117 perf_type: Option<&'a str>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 color: Option<&'a str>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 analysed: Option<bool>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 ongoing: Option<bool>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 finished: Option<bool>,
126 #[serde(rename = "lastFen", skip_serializing_if = "Option::is_none")]
127 last_fen: Option<bool>,
128 #[serde(rename = "withBookmarked", skip_serializing_if = "Option::is_none")]
129 with_bookmarked: Option<bool>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 sort: Option<GameSort>,
132}
133
134impl<'a> UserGamesRequest<'a> {
135 pub(crate) fn new(client: &'a LichessClient, username: &'a str) -> Self {
137 Self {
138 client,
139 username,
140 query: UserGamesQuery::default(),
141 export: GameExportOptions::default(),
142 }
143 }
144
145 #[must_use]
147 pub fn max(mut self, count: u32) -> Self {
148 self.query.max = Some(count);
149 self
150 }
151
152 #[must_use]
154 pub fn since(mut self, timestamp: i64) -> Self {
155 self.query.since = Some(timestamp);
156 self
157 }
158
159 #[must_use]
161 pub fn until(mut self, timestamp: i64) -> Self {
162 self.query.until = Some(timestamp);
163 self
164 }
165
166 #[must_use]
168 pub fn vs(mut self, opponent: &'a str) -> Self {
169 self.query.vs = Some(opponent);
170 self
171 }
172
173 #[must_use]
175 pub fn rated(mut self, rated: bool) -> Self {
176 self.query.rated = Some(rated);
177 self
178 }
179
180 #[must_use]
182 pub fn perf_type(mut self, perf_type: &'a str) -> Self {
183 self.query.perf_type = Some(perf_type);
184 self
185 }
186
187 #[must_use]
189 pub fn color(mut self, color: &'a str) -> Self {
190 self.query.color = Some(color);
191 self
192 }
193
194 #[must_use]
196 pub fn analysed(mut self, analysed: bool) -> Self {
197 self.query.analysed = Some(analysed);
198 self
199 }
200
201 #[must_use]
203 pub fn ongoing(mut self, ongoing: bool) -> Self {
204 self.query.ongoing = Some(ongoing);
205 self
206 }
207
208 #[must_use]
210 pub fn finished(mut self, finished: bool) -> Self {
211 self.query.finished = Some(finished);
212 self
213 }
214
215 #[must_use]
217 pub fn last_fen(mut self, value: bool) -> Self {
218 self.query.last_fen = Some(value);
219 self
220 }
221
222 #[must_use]
224 pub fn with_bookmarked(mut self, value: bool) -> Self {
225 self.query.with_bookmarked = Some(value);
226 self
227 }
228
229 #[must_use]
231 pub fn sort(mut self, sort: GameSort) -> Self {
232 self.query.sort = Some(sort);
233 self
234 }
235
236 #[must_use]
238 pub fn export(mut self, options: GameExportOptions) -> Self {
239 self.export = options;
240 self
241 }
242
243 pub async fn stream(self) -> Result<BoxStream<'static, Result<LichessGame>>> {
245 let path = format!("/api/games/user/{}", http::segment(self.username));
246 let request = self
247 .client
248 .request(Method::GET, Host::Default, &path)
249 .header(ACCEPT, ACCEPT_NDJSON)
250 .query(&self.query)
251 .query(&self.export);
252 http::stream(request, self.client.max_line_bytes()).await
253 }
254
255 pub async fn pgn(self) -> Result<String> {
257 let path = format!("/api/games/user/{}", http::segment(self.username));
258 let request = self
259 .client
260 .request(Method::GET, Host::Default, &path)
261 .header(ACCEPT, ACCEPT_PGN)
262 .query(&self.query)
263 .query(&self.export);
264 http::text(request).await
265 }
266}
267
268#[derive(Debug)]
270pub struct ExportByIdsRequest<'a> {
271 client: &'a LichessClient,
272 ids: String,
273 export: GameExportOptions,
274}
275
276impl<'a> ExportByIdsRequest<'a> {
277 pub(crate) fn new(client: &'a LichessClient, ids: &[&str]) -> Self {
279 Self {
280 client,
281 ids: ids.join(","),
282 export: GameExportOptions::default(),
283 }
284 }
285
286 #[must_use]
288 pub fn export(mut self, options: GameExportOptions) -> Self {
289 self.export = options;
290 self
291 }
292
293 pub async fn stream(self) -> Result<BoxStream<'static, Result<LichessGame>>> {
295 let request = self
296 .client
297 .request(Method::POST, Host::Default, "/api/games/export/_ids")
298 .header(ACCEPT, ACCEPT_NDJSON)
299 .query(&self.export)
300 .text_body(self.ids);
301 http::stream(request, self.client.max_line_bytes()).await
302 }
303
304 pub async fn pgn(self) -> Result<String> {
306 let request = self
307 .client
308 .request(Method::POST, Host::Default, "/api/games/export/_ids")
309 .header(ACCEPT, ACCEPT_PGN)
310 .query(&self.export)
311 .text_body(self.ids);
312 http::text(request).await
313 }
314}
315
316#[derive(Debug)]
318pub struct ExportBookmarksRequest<'a> {
319 client: &'a LichessClient,
320 query: BookmarksQuery,
321 export: GameExportOptions,
322}
323
324#[derive(Debug, Default, Serialize)]
326struct BookmarksQuery {
327 #[serde(skip_serializing_if = "Option::is_none")]
328 since: Option<i64>,
329 #[serde(skip_serializing_if = "Option::is_none")]
330 until: Option<i64>,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 max: Option<u32>,
333 #[serde(rename = "lastFen", skip_serializing_if = "Option::is_none")]
334 last_fen: Option<bool>,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 sort: Option<GameSort>,
337}
338
339impl<'a> ExportBookmarksRequest<'a> {
340 pub(crate) fn new(client: &'a LichessClient) -> Self {
342 Self {
343 client,
344 query: BookmarksQuery::default(),
345 export: GameExportOptions::default(),
346 }
347 }
348
349 #[must_use]
351 pub fn since(mut self, timestamp: i64) -> Self {
352 self.query.since = Some(timestamp);
353 self
354 }
355
356 #[must_use]
358 pub fn until(mut self, timestamp: i64) -> Self {
359 self.query.until = Some(timestamp);
360 self
361 }
362
363 #[must_use]
365 pub fn max(mut self, count: u32) -> Self {
366 self.query.max = Some(count);
367 self
368 }
369
370 #[must_use]
372 pub fn last_fen(mut self, value: bool) -> Self {
373 self.query.last_fen = Some(value);
374 self
375 }
376
377 #[must_use]
379 pub fn sort(mut self, sort: GameSort) -> Self {
380 self.query.sort = Some(sort);
381 self
382 }
383
384 #[must_use]
386 pub fn export(mut self, options: GameExportOptions) -> Self {
387 self.export = options;
388 self
389 }
390
391 pub async fn stream(self) -> Result<BoxStream<'static, Result<LichessGame>>> {
393 let request = self
394 .client
395 .request(Method::GET, Host::Default, "/api/games/export/bookmarks")
396 .header(ACCEPT, ACCEPT_NDJSON)
397 .query(&self.query)
398 .query(&self.export);
399 http::stream(request, self.client.max_line_bytes()).await
400 }
401
402 pub async fn pgn(self) -> Result<String> {
404 let request = self
405 .client
406 .request(Method::GET, Host::Default, "/api/games/export/bookmarks")
407 .header(ACCEPT, ACCEPT_PGN)
408 .query(&self.query)
409 .query(&self.export);
410 http::text(request).await
411 }
412}
413
414#[derive(Debug)]
417pub struct CurrentGameRequest<'a> {
418 client: &'a LichessClient,
419 username: &'a str,
420 export: GameExportOptions,
421}
422
423impl<'a> CurrentGameRequest<'a> {
424 pub(crate) fn new(client: &'a LichessClient, username: &'a str) -> Self {
426 Self {
427 client,
428 username,
429 export: GameExportOptions::default(),
430 }
431 }
432
433 #[must_use]
435 pub fn export(mut self, options: GameExportOptions) -> Self {
436 self.export = options;
437 self
438 }
439
440 pub async fn json(self) -> Result<LichessGame> {
442 let path = format!("/api/user/{}/current-game", http::segment(self.username));
443 let request = self
444 .client
445 .request(Method::GET, Host::Default, &path)
446 .header(ACCEPT, ACCEPT_JSON)
447 .query(&self.export);
448 http::json(request, "LichessGame").await
449 }
450
451 pub async fn pgn(self) -> Result<String> {
453 let path = format!("/api/user/{}/current-game", http::segment(self.username));
454 let request = self
455 .client
456 .request(Method::GET, Host::Default, &path)
457 .header(ACCEPT, ACCEPT_PGN)
458 .query(&self.export);
459 http::text(request).await
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn game_sort_uses_camel_case() {
469 assert_eq!(
470 serde_urlencoded::to_string([("sort", GameSort::DateAsc)]).unwrap(),
471 "sort=dateAsc"
472 );
473 assert_eq!(
474 serde_urlencoded::to_string([("sort", GameSort::DateDesc)]).unwrap(),
475 "sort=dateDesc"
476 );
477 }
478}