hinge_rs/client/
ratings.rs1use super::HingeClient;
2use crate::errors::HingeError;
3use crate::models::{
4 CreateRate, CreateRateContent, CreateRateContentPrompt, LikeResponse, PhotoAsset,
5 PhotoAssetInput, RateInput, RateRespondRequest, RateRespondResponse, SkipInput,
6};
7use crate::storage::Storage;
8use chrono::Utc;
9use serde_json::json;
10use uuid::Uuid;
11
12impl<S: Storage + Clone> HingeClient<S> {
13 pub async fn skip(&mut self, input: SkipInput) -> Result<serde_json::Value, HingeError> {
14 let url = format!("{}/rate/v2/initiate", self.settings.base_url);
15 let payload = CreateRate {
16 rating_id: Uuid::new_v4().to_string().to_uppercase(),
17 hcm_run_id: None,
18 session_id: self.session_id.clone(),
19 content: None,
20 created: Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string(),
21 rating_token: input.rating_token,
22 initiated_with: None,
23 rating: "skip".into(),
24 has_pairing: false,
25 origin: Some(input.origin.unwrap_or_else(|| "compatibles".into())),
26 subject_id: input.subject_id.clone(),
27 };
28 let body = serde_json::to_value(&payload).map_err(|e| HingeError::Serde(e.to_string()))?;
29 let res = self.http_post(&url, &body).await?;
30 let body = self.parse_response(res).await?;
31
32 self.remove_recommendation(&input.subject_id);
33 if self.auto_persist
34 && let Some(path) = self.recs_cache_path()
35 {
36 let _ = self.save_recommendations(&path);
37 }
38 Ok(body)
39 }
40
41 pub async fn rate_user(&mut self, input: RateInput) -> Result<LikeResponse, HingeError> {
42 let mut hcm_run_id: Option<String> = None;
43 if let Some(text) = input.comment.as_deref() {
44 let run_id = self.run_text_review(text, &input.subject_id).await?;
45 hcm_run_id = Some(run_id);
46 }
47 let prompt_answer = input.answer_text.clone().unwrap_or_default();
48 let prompt_question = input.question_text.clone().unwrap_or_default();
49 let prompt_content_id = input.content_id.clone();
50
51 let content = if let Some(photo) = input.photo {
52 let PhotoAssetInput {
53 url,
54 content_id,
55 cdn_id,
56 bounding_box,
57 selfie_verified,
58 } = photo;
59 Some(CreateRateContent {
60 comment: input.comment.clone(),
61 photo: Some(PhotoAsset {
62 id: None,
63 url,
64 cdn_id,
65 content_id,
66 prompt_id: None,
67 caption: None,
68 width: None,
69 height: None,
70 video_url: None,
71 selfie_verified,
72 bounding_box,
73 location: None,
74 source: None,
75 source_id: None,
76 p_hash: None,
77 }),
78 prompt: None,
79 })
80 } else {
81 let prompt = CreateRateContentPrompt {
82 answer: prompt_answer,
83 content_id: prompt_content_id,
84 question: prompt_question,
85 };
86 Some(CreateRateContent {
87 comment: input.comment.clone(),
88 photo: None,
89 prompt: Some(prompt),
90 })
91 };
92 let payload = CreateRate {
93 rating_id: Uuid::new_v4().to_string().to_uppercase(),
94 hcm_run_id,
95 session_id: self.session_id.clone(),
96 content,
97 created: Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string(),
98 rating_token: input.rating_token,
99 initiated_with: Some(if input.use_superlike.unwrap_or(false) {
100 "superlike".into()
101 } else {
102 "standard".into()
103 }),
104 rating: if input.comment.is_some() {
105 "note".into()
106 } else {
107 "like".into()
108 },
109 has_pairing: false,
110 origin: Some(input.origin.unwrap_or_else(|| "compatibles".into())),
111 subject_id: input.subject_id,
112 };
113 let url = format!("{}/rate/v2/initiate", self.settings.base_url);
114 let body = serde_json::to_value(&payload).map_err(|e| HingeError::Serde(e.to_string()))?;
115 let res = self.http_post(&url, &body).await?;
116 let body = self.parse_response::<LikeResponse>(res).await?;
117 if self.auto_persist
118 && let Some(path) = self.recs_cache_path()
119 {
120 let _ = self.save_recommendations(&path);
121 }
122 Ok(body)
123 }
124
125 pub async fn respond_rate(
126 &self,
127 mut payload: RateRespondRequest,
128 ) -> Result<RateRespondResponse, HingeError> {
129 if payload.rating_id.is_none() {
130 payload.rating_id = Some(Uuid::new_v4().to_string().to_uppercase());
131 }
132 if payload.session_id.is_none() {
133 payload.session_id = Some(self.session_id.clone());
134 }
135 if payload.created.is_none() {
136 payload.created = Some(Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string());
137 }
138
139 let url = format!("{}/rate/v2/respond", self.settings.base_url);
140 let body = serde_json::to_value(&payload).map_err(|e| HingeError::Serde(e.to_string()))?;
141 let res = self.http_post(&url, &body).await?;
142 self.parse_response(res).await
143 }
144
145 async fn run_text_review(&self, text: &str, receiver_id: &str) -> Result<String, HingeError> {
146 let url = format!("{}/flag/textreview", self.settings.base_url);
147 let res = self
148 .http
149 .post(url)
150 .headers(self.default_headers()?)
151 .json(&json!({ "text": text, "receiverId": receiver_id }))
152 .send()
153 .await?;
154 if !res.status().is_success() {
155 return Err(HingeError::Http(format!("status {}", res.status())));
156 }
157 let v = self.parse_response::<serde_json::Value>(res).await?;
158 Ok(v.get("hcmRunId")
159 .and_then(|v| v.as_str())
160 .unwrap_or("")
161 .to_string())
162 }
163}