Skip to main content

hinge_rs/client/
prompts.rs

1use super::HingeClient;
2use crate::errors::HingeError;
3use crate::models::{
4    AnswerEvaluateRequest, CreatePromptPollRequest, CreatePromptPollResponse,
5    CreateVideoPromptRequest, CreateVideoPromptResponse, Prompt, PromptsResponse,
6};
7use crate::prompts_manager::HingePromptsManager;
8use crate::storage::Storage;
9use serde_json::json;
10use std::path::Path;
11
12impl<S: Storage + Clone> HingeClient<S> {
13    pub async fn fetch_prompts(&mut self) -> Result<PromptsResponse, HingeError> {
14        if self.auto_persist
15            && let Some(path) = self.prompts_cache_path()
16            && Path::new(&path).exists()
17            && let Ok(text) = std::fs::read_to_string(&path)
18            && let Ok(val) = serde_json::from_str::<PromptsResponse>(&text)
19        {
20            return Ok(val);
21        }
22
23        let url = format!("{}/prompts", self.settings.base_url);
24        let payload = self.prompt_payload().await;
25        let res = self.http_post(&url, &payload).await?;
26        let body = self.parse_response::<PromptsResponse>(res).await?;
27        if self.auto_persist
28            && let Some(path) = self.prompts_cache_path()
29        {
30            let _ = std::fs::write(
31                &path,
32                serde_json::to_string_pretty(&body).unwrap_or_else(|_| "{}".into()),
33            );
34        }
35        Ok(body)
36    }
37
38    pub async fn fetch_prompts_manager(&mut self) -> Result<HingePromptsManager, HingeError> {
39        let resp = self.fetch_prompts().await?;
40        Ok(HingePromptsManager::new(resp))
41    }
42
43    pub async fn get_prompt_text(&mut self, prompt_id: &str) -> Result<String, HingeError> {
44        let mgr = self.fetch_prompts_manager().await?;
45        Ok(mgr.get_prompt_display_text(prompt_id))
46    }
47
48    pub async fn search_prompts(&mut self, query: &str) -> Result<Vec<Prompt>, HingeError> {
49        let mgr = self.fetch_prompts_manager().await?;
50        let items = mgr.search_prompts(query);
51        Ok(items.into_iter().cloned().collect())
52    }
53
54    pub async fn get_prompts_by_category(
55        &mut self,
56        category_slug: &str,
57    ) -> Result<Vec<Prompt>, HingeError> {
58        let mgr = self.fetch_prompts_manager().await?;
59        let items = mgr.get_prompts_by_category(category_slug);
60        Ok(items.into_iter().cloned().collect())
61    }
62
63    pub async fn prompt_payload(&mut self) -> serde_json::Value {
64        if !self.is_session_valid().await.unwrap_or(false) {
65            return json!({});
66        }
67        let preferences = match self.get_self_preferences().await {
68            Ok(v) => v,
69            Err(_) => return json!({}),
70        };
71        let profile = match self.get_self_profile().await {
72            Ok(v) => v,
73            Err(_) => return json!({}),
74        };
75        let mut preferences_dict = serde_json::to_value(&preferences).unwrap_or(json!({}));
76        let profile_dict = serde_json::to_value(&profile).unwrap_or(json!({}));
77
78        let selected: Vec<String> = preferences_dict
79            .get("preferences")
80            .and_then(|p| p.get("genderPreferences"))
81            .and_then(|v| v.as_array())
82            .map(|arr| {
83                arr.iter()
84                    .filter_map(|x| x.as_u64().map(|n| n.to_string()))
85                    .collect()
86            })
87            .unwrap_or_default();
88
89        let keep_selected = |mut d: serde_json::Value| {
90            if let serde_json::Value::Object(map) = &mut d
91                && !selected.is_empty()
92            {
93                map.retain(|k, _| selected.contains(k));
94            }
95            d
96        };
97
98        if let Some(obj) = preferences_dict
99            .get_mut("preferences")
100            .and_then(|p| p.get_mut("genderedHeightRanges"))
101        {
102            *obj = keep_selected(obj.clone());
103        }
104        if let Some(obj) = preferences_dict
105            .get_mut("preferences")
106            .and_then(|p| p.get_mut("genderedAgeRanges"))
107        {
108            *obj = keep_selected(obj.clone());
109        }
110
111        if let Some(db) = preferences_dict
112            .get_mut("preferences")
113            .and_then(|p| p.get_mut("dealbreakers"))
114        {
115            if let Some(obj) = db.get_mut("genderedHeight") {
116                *obj = keep_selected(obj.clone());
117            }
118            if let Some(obj) = db.get_mut("genderedAge") {
119                *obj = keep_selected(obj.clone());
120            }
121        }
122
123        let p = profile_dict
124            .get("content")
125            .map(unwrap_visible)
126            .unwrap_or(json!({}));
127        let loc_name = profile_dict
128            .get("content")
129            .and_then(|c| c.get("location"))
130            .and_then(|l| l.get("name"))
131            .cloned()
132            .unwrap_or(json!(null));
133
134        let profile_payload = json!({
135          "works": match p.get("works") { Some(v) if v.is_string() => json!([v]), _ => field_or(&p, "works", json!([])) },
136          "sexualOrientations": field_or(&p, "sexualOrientations", json!([])),
137          "didJustJoin": false,
138          "smoking": field_or(&p, "smoking", json!(null)),
139          "selfieVerified": field_or(&p, "selfieVerified", json!(false)),
140          "politics": field_or(&p, "politics", json!(null)),
141          "relationshipTypesText": field_or(&p, "relationshipTypesText", json!("")),
142          "datingIntention": field_or(&p, "datingIntention", json!(null)),
143          "height": field_or(&p, "height", json!(null)),
144          "children": field_or(&p, "children", json!(null)),
145          "religions": field_or(&p, "religions", json!([])),
146          "relationshipTypes": field_or(&p, "relationshipTypeIds", json!([])),
147          "educations": field_or(&p, "educations", json!([])),
148          "age": field_or(&p, "age", json!(null)),
149          "jobTitle": field_or(&p, "jobTitle", json!(null)),
150          "birthday": field_or(&p, "birthday", json!(null)),
151          "drugs": field_or(&p, "drugs", json!(null)),
152          "content": json!({}),
153          "hometown": field_or(&p, "hometown", json!(null)),
154          "firstName": field_or(&p, "firstName", json!(null)),
155          "familyPlans": field_or(&p, "familyPlans", json!(null)),
156          "location": json!({"name": loc_name}),
157          "marijuana": field_or(&p, "marijuana", json!(null)),
158          "pets": field_or(&p, "pets", json!([])),
159          "datingIntentionText": field_or(&p, "datingIntentionText", json!("")),
160          "educationAttained": field_or(&p, "educationAttained", json!(null)),
161          "ethnicities": field_or(&p, "ethnicities", json!([])),
162          "pronouns": field_or(&p, "pronouns", json!([])),
163          "languagesSpoken": field_or(&p, "languagesSpoken", json!([])),
164          "lastName": field_or(&p, "lastName", json!("")),
165          "ethnicitiesText": field_or(&p, "ethnicitiesText", json!("")),
166          "drinking": field_or(&p, "drinking", json!(null)),
167          "userId": field_or(&profile_dict, "userId", json!(null)),
168          "genderIdentityId": field_or(&p, "genderIdentityId", json!(null)),
169        });
170
171        json!({
172          "preferences": preferences_dict.get("preferences").cloned().unwrap_or(json!({})),
173          "profile": profile_payload
174        })
175    }
176
177    pub async fn evaluate_answer(
178        &self,
179        payload: AnswerEvaluateRequest,
180    ) -> Result<serde_json::Value, HingeError> {
181        let url = format!("{}/content/v1/answer/evaluate", self.settings.base_url);
182        let body = serde_json::to_value(&payload).map_err(|e| HingeError::Serde(e.to_string()))?;
183        let res = self.http_post(&url, &body).await?;
184        self.parse_response(res).await
185    }
186
187    pub async fn create_prompt_poll(
188        &self,
189        payload: CreatePromptPollRequest,
190    ) -> Result<CreatePromptPollResponse, HingeError> {
191        let url = format!("{}/content/v1/prompt_poll", self.settings.base_url);
192        let body = serde_json::to_value(&payload).map_err(|e| HingeError::Serde(e.to_string()))?;
193        let res = self.http_post(&url, &body).await?;
194        self.parse_response(res).await
195    }
196
197    pub async fn create_video_prompt(
198        &self,
199        payload: CreateVideoPromptRequest,
200    ) -> Result<CreateVideoPromptResponse, HingeError> {
201        let url = format!("{}/content/v1/video_prompt", self.settings.base_url);
202        let body = serde_json::to_value(&payload).map_err(|e| HingeError::Serde(e.to_string()))?;
203        let res = self.http_post(&url, &body).await?;
204        self.parse_response(res).await
205    }
206}
207
208fn field_or(value: &serde_json::Value, key: &str, default: serde_json::Value) -> serde_json::Value {
209    value.get(key).cloned().unwrap_or(default)
210}
211
212fn unwrap_visible(obj: &serde_json::Value) -> serde_json::Value {
213    match obj {
214        serde_json::Value::Object(m) => {
215            if m.contains_key("value") && m.contains_key("visible") {
216                unwrap_visible(&m["value"])
217            } else {
218                let mut out = serde_json::Map::new();
219                for (k, v) in m.iter() {
220                    out.insert(k.clone(), unwrap_visible(v));
221                }
222                serde_json::Value::Object(out)
223            }
224        }
225        serde_json::Value::Array(arr) => {
226            serde_json::Value::Array(arr.iter().map(unwrap_visible).collect())
227        }
228        _ => obj.clone(),
229    }
230}