phemios_core/encyclopedia/
translation.rs1use super::{Client, article_result, checked_response, language, page_id, title};
3use serde_json::Value;
4use std::time::{SystemTime, UNIX_EPOCH};
5const MISSING: &str = "Cet article n’a pas de version dans la langue demandée.";
6fn counterpart_title<'a>(value: &'a Value, id: u64, target: &str) -> Result<&'a str, String> {
7 let pages = checked_response(value)?["pages"]
8 .as_array()
9 .filter(|pages| pages.len() == 1)
10 .ok_or("Réponse Wikipédia invalide.")?;
11 let page = &pages[0];
12 if page["pageid"].as_u64() != Some(id)
13 || page["ns"].as_u64() != Some(0)
14 || page.get("missing").is_some()
15 || page.get("invalid").is_some()
16 {
17 return Err("Article Wikipédia indisponible.".into());
18 }
19 let Some(links) = page.get("langlinks") else {
20 return Err(MISSING.into());
21 };
22 let links = links.as_array().ok_or("Réponse Wikipédia invalide.")?;
23 if links.is_empty() {
24 return Err(MISSING.into());
25 }
26 if links.len() != 1 || links[0]["lang"].as_str() != Some(target) {
27 return Err("Réponse Wikipédia invalide.".into());
28 }
29 let name = title(&links[0])?;
30 if name.contains(['|', '#']) {
32 return Err("Réponse Wikipédia invalide.".into());
33 }
34 Ok(name)
35}
36fn counterpart_result(value: &Value, target: &str, at: u64) -> Result<Value, String> {
37 let pages = checked_response(value)?["pages"]
38 .as_array()
39 .filter(|pages| pages.len() == 1)
40 .ok_or("Réponse Wikipédia invalide.")?;
41 let id = page_id(
42 pages[0]["pageid"]
43 .as_u64()
44 .ok_or("Article Wikipédia indisponible.")?,
45 )?;
46 article_result(value, target, id, at)
47}
48pub(super) fn checked_article_title(name: &str) -> Result<&str, String> {
49 if name.trim().is_empty()
50 || name.len() > 2048
51 || name.chars().any(char::is_control)
52 || name.contains(['|', '#'])
53 {
54 return Err("Titre Wikipédia invalide.".into());
55 }
56 Ok(name)
57}
58impl Client {
59 pub fn named_article(&self, lang: &str, name: &str) -> Result<Value, String> {
61 language(lang)?;
62 let name = checked_article_title(name)?;
63 let article = self.request_article(
64 lang,
65 &[
66 ("titles", name),
67 ("redirects", "1"),
68 ("prop", "extracts|info"),
69 ("explaintext", "1"),
70 ("exsectionformat", "wiki"),
71 ],
72 )?;
73 let at = SystemTime::now()
74 .duration_since(UNIX_EPOCH)
75 .map_err(|_| "Horloge système invalide.")?
76 .as_millis();
77 counterpart_result(
78 &article,
79 lang,
80 u64::try_from(at).map_err(|_| "Horloge système invalide.")?,
81 )
82 }
83 pub fn translated_article(&self, source: &str, id: u64, target: &str) -> Result<Value, String> {
86 language(source)?;
87 language(target)?;
88 let id = page_id(id)?;
89 if source == target {
90 return self.article(source, id);
91 }
92 let relation = self.request(
93 source,
94 &[
95 ("pageids", &id.to_string()),
96 ("prop", "langlinks"),
97 ("lllang", target),
98 ("lllimit", "1"),
99 ],
100 )?;
101 let name = counterpart_title(&relation, id, target)?;
102 let article = self.request_article(
103 target,
104 &[
105 ("titles", name),
106 ("redirects", "1"),
107 ("prop", "extracts|info"),
108 ("explaintext", "1"),
109 ("exsectionformat", "wiki"),
110 ],
111 )?;
112 let at = SystemTime::now()
113 .duration_since(UNIX_EPOCH)
114 .map_err(|_| "Horloge système invalide.")?
115 .as_millis();
116 counterpart_result(
117 &article,
118 target,
119 u64::try_from(at).map_err(|_| "Horloge système invalide.")?,
120 )
121 }
122}
123#[cfg(test)]
124#[path = "translation_tests.rs"]
125mod tests;