Skip to content

Commit e0c6a7f

Browse files
authored
Add Support Bots Endpoint (#737)
* Add support bot endpoints * Improved date parsing * Clean up support bots endpoint
1 parent 39d3455 commit e0c6a7f

File tree

15 files changed

+596
-23
lines changed

15 files changed

+596
-23
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ scraper = "0.22"
5858
semver = "1.0"
5959
serde = "1.0"
6060
serde_json = "1.0"
61+
serde_repr = "0.1"
6162
serial_test = "3.2"
6263
strum = "0.27"
6364
strum_macros = "0.27"

native/swift/Sources/wordpress-api/Exports.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,9 @@ public typealias TokenValidationParameters = WordPressAPIInternal.TokenValidatio
143143
public typealias SubscribersListParams = WordPressAPIInternal.SubscribersListParams
144144
public typealias SubscriberImportJobsListParams = WordPressAPIInternal.SubscriberImportJobsListParams
145145
public typealias AddSubscribersParams = WordPressAPIInternal.AddSubscribersParams
146+
147+
// MARK: Support Bots
148+
public typealias CreateBotConversationParams = WordPressAPIInternal.CreateBotConversationParams
149+
public typealias AddMessageToBotConversationParams = WordPressAPIInternal.AddMessageToBotConversationParams
150+
public typealias GetBotConversationParams = WordPressAPIInternal.GetBotConversationParams
151+
public typealias CreateBotConversationFeedbackParams = WordPressAPIInternal.CreateBotConversationFeedbackParams

native/swift/Sources/wordpress-api/WPComApiClient.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@ public class WPComApiClient {
2121
public var subscribers: SubscribersRequestExecutor {
2222
internalClient.subscribers()
2323
}
24+
25+
public var supportBots: SupportBotsRequestExecutor {
26+
internalClient.supportBots()
27+
}
2428
}

wp_api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ rustls = { workspace = true, optional = true }
3333
scraper = { workspace = true }
3434
serde = { workspace = true, features = [ "derive", "rc" ] }
3535
serde_json = { workspace = true }
36+
serde_repr = { workspace = true }
3637
strum = { workspace = true }
3738
strum_macros = { workspace = true }
3839
thiserror = { workspace = true }

wp_api/src/wp_com/client.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use super::endpoint::jetpack_connection_endpoint::{
33
};
44
use super::endpoint::oauth2::{Oauth2RequestBuilder, Oauth2RequestExecutor};
55
use super::endpoint::subscribers::{SubscribersRequestBuilder, SubscribersRequestExecutor};
6+
use super::endpoint::support_bots_endpoint::{
7+
SupportBotsRequestBuilder, SupportBotsRequestExecutor,
8+
};
69
use crate::api_client_generate_request_builder;
710
use crate::{
811
ParsedUrl, WpApiClientDelegate, api_client_generate_api_client,
@@ -29,6 +32,7 @@ pub struct WpComApiRequestBuilder {
2932
jetpack_connection: Arc<JetpackConnectionRequestBuilder>,
3033
oauth2: Arc<Oauth2RequestBuilder>,
3134
subscribers: Arc<SubscribersRequestBuilder>,
35+
support_bots: Arc<SupportBotsRequestBuilder>,
3236
}
3337

3438
impl WpComApiRequestBuilder {
@@ -38,7 +42,8 @@ impl WpComApiRequestBuilder {
3842
auth_provider;
3943
jetpack_connection,
4044
oauth2,
41-
subscribers
45+
subscribers,
46+
support_bots
4247
)
4348
}
4449
}
@@ -62,6 +67,7 @@ pub struct WpComApiClient {
6267
jetpack_connection: Arc<JetpackConnectionRequestExecutor>,
6368
oauth2: Arc<Oauth2RequestExecutor>,
6469
subscribers: Arc<SubscribersRequestExecutor>,
70+
support_bots: Arc<SupportBotsRequestExecutor>,
6571
}
6672

6773
impl WpComApiClient {
@@ -74,10 +80,12 @@ impl WpComApiClient {
7480
delegate;
7581
jetpack_connection,
7682
oauth2,
77-
subscribers
83+
subscribers,
84+
support_bots
7885
)
7986
}
8087
}
8188
api_client_generate_endpoint_impl!(WpComApi, jetpack_connection);
8289
api_client_generate_endpoint_impl!(WpComApi, oauth2);
8390
api_client_generate_endpoint_impl!(WpComApi, subscribers);
91+
api_client_generate_endpoint_impl!(WpComApi, support_bots);

wp_api/src/wp_com/endpoint.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod jetpack_connection_endpoint;
22
pub mod oauth2;
33
pub mod subscribers;
4+
pub mod support_bots_endpoint;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::wp_com::support_bots::BotId;
2+
use crate::wp_com::support_bots::ChatId;
3+
use crate::wp_com::support_bots::MessageId;
4+
use crate::{
5+
request::endpoint::{AsNamespace, DerivedRequest},
6+
wp_com::{
7+
WpComNamespace,
8+
support_bots::{
9+
AddMessageToBotConversationParams, BotConversation, BotConversationSummary,
10+
CreateBotConversationFeedbackParams, CreateBotConversationParams,
11+
GetBotConversationParams,
12+
},
13+
},
14+
};
15+
use wp_derive_request_builder::WpDerivedRequest;
16+
17+
#[derive(WpDerivedRequest)]
18+
enum SupportBotsRequest {
19+
#[post(url = "/odie/chat/<bot_id>", params = &CreateBotConversationParams, output = BotConversation)]
20+
CreateBotConversation,
21+
#[get(url = "/odie/conversations/<bot_id>", output = Vec<BotConversationSummary>)]
22+
GetBotConverationList,
23+
#[get(url = "/odie/chat/<bot_id>/<chat_id>", params = &GetBotConversationParams, output = BotConversation)]
24+
GetBotConversation,
25+
#[post(url = "/odie/chat/<bot_id>/<chat_id>", params = &AddMessageToBotConversationParams, output = BotConversation)]
26+
AddMessageToBotConversation,
27+
#[post(url = "/odie/chat/<bot_id>/<chat_id>/<message_id>/feedback", params = &CreateBotConversationFeedbackParams, output = bool)]
28+
CreateBotConversationFeedback,
29+
}
30+
31+
impl DerivedRequest for SupportBotsRequest {
32+
fn namespace() -> impl AsNamespace {
33+
WpComNamespace::V2
34+
}
35+
}

wp_api/src/wp_com/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod endpoint;
77
pub mod jetpack_connection;
88
pub mod oauth2;
99
pub mod subscribers;
10+
pub mod support_bots;
1011

1112
impl_as_query_value_for_new_type!(WpComSiteId);
1213
uniffi::custom_newtype!(WpComSiteId, u64);

wp_api/src/wp_com/support_bots.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
use crate::{
2+
date::WpGmtDateTime,
3+
impl_as_query_value_for_new_type,
4+
url_query::{AppendUrlQueryPairs, QueryPairs, QueryPairsExtension},
5+
users::UserId,
6+
};
7+
use serde::{Deserialize, Serialize};
8+
use serde_repr::*;
9+
use std::collections::HashMap;
10+
11+
use super::WpComSiteId;
12+
13+
#[derive(Debug, PartialEq, Eq, Serialize, uniffi::Record)]
14+
pub struct CreateBotConversationParams {
15+
pub message: String,
16+
pub user_id: UserId,
17+
}
18+
19+
#[derive(Debug, PartialEq, Serialize, Deserialize, uniffi::Record)]
20+
pub struct BotConversation {
21+
pub chat_id: u64,
22+
pub wpcom_user_id: UserId,
23+
pub external_id: Option<String>,
24+
pub external_id_provider: Option<String>,
25+
pub session_id: String,
26+
pub bot_slug: String,
27+
pub bot_version: String,
28+
pub created_at: WpGmtDateTime,
29+
pub zendesk_ticket_id: Option<String>,
30+
pub messages: Vec<BotMessage>,
31+
}
32+
33+
#[derive(Debug, PartialEq, Serialize, Deserialize, uniffi::Record)]
34+
pub struct BotMessage {
35+
pub message_id: u64,
36+
pub content: String,
37+
pub role: String,
38+
#[serde(rename = "ts")]
39+
pub created_at: WpGmtDateTime,
40+
pub context: MessageContext,
41+
}
42+
43+
#[derive(Debug, PartialEq, Serialize, Deserialize, uniffi::Enum)]
44+
#[serde(untagged, rename_all = "lowercase")]
45+
pub enum MessageContext {
46+
User(UserMessageContext),
47+
Bot(BotMessageContext),
48+
}
49+
50+
#[derive(Debug, PartialEq, Serialize, Deserialize, uniffi::Record)]
51+
pub struct BotMessageContext {
52+
pub question_tags: HashMap<String, String>,
53+
pub sources: Vec<BotMessageContextSource>,
54+
pub flags: HashMap<String, bool>,
55+
}
56+
57+
#[derive(Debug, PartialEq, Serialize, Deserialize, uniffi::Record)]
58+
pub struct BotMessageContextSource {
59+
pub title: String,
60+
pub url: String,
61+
pub heading: String,
62+
pub content: String,
63+
pub blog_id: WpComSiteId,
64+
pub post_id: u64,
65+
pub score: f64,
66+
pub last_indexed_at: WpGmtDateTime,
67+
}
68+
69+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
70+
#[serde(rename_all = "snake_case")]
71+
pub struct UserMessageContext {
72+
#[serde(alias = "selectedSiteId")]
73+
pub selected_site_id: WpComSiteId,
74+
pub wpcom_user_id: UserId,
75+
pub wpcom_user_name: String,
76+
pub user_paid_support_eligibility: UserPaidSupportEligibility,
77+
pub plan: UserPaidSupportPlan,
78+
pub products: Vec<String>,
79+
pub plan_interface: bool,
80+
}
81+
82+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
83+
pub struct UserPaidSupportEligibility {
84+
pub is_user_eligible: bool,
85+
pub wapuu_assistant_enabled: bool,
86+
}
87+
88+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
89+
pub struct UserPaidSupportPlan {
90+
pub plan_name: String,
91+
pub is_free: bool,
92+
}
93+
94+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
95+
pub struct BotConversationSummary {
96+
pub chat_id: u64,
97+
pub created_at: WpGmtDateTime,
98+
pub last_message: BotMessageSummary,
99+
}
100+
101+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
102+
pub struct BotMessageSummary {
103+
pub content: String,
104+
pub role: String,
105+
pub created_at: WpGmtDateTime,
106+
}
107+
108+
#[derive(Debug, PartialEq, Eq, Deserialize, uniffi::Record)]
109+
pub struct GetBotConversationParams {
110+
// The number of the page to retrieve, limited to 100.
111+
#[uniffi(default = None)]
112+
pub page_number: Option<u64>,
113+
114+
// The number of items per page.
115+
#[uniffi(default = None)]
116+
pub items_per_page: Option<u64>,
117+
118+
// If true, include the feedback rating value for each message in the response.
119+
#[uniffi(default = false)]
120+
pub include_feedback: bool,
121+
}
122+
123+
impl AppendUrlQueryPairs for GetBotConversationParams {
124+
fn append_query_pairs(&self, query_pairs_mut: &mut QueryPairs) {
125+
query_pairs_mut
126+
.append_option_query_value_pair("page", self.page_number.as_ref())
127+
.append_option_query_value_pair("per_page", self.items_per_page.as_ref())
128+
.append_query_value_pair("include_feedback", &self.include_feedback.to_string());
129+
}
130+
}
131+
132+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
133+
pub struct AddMessageToBotConversationParams {
134+
pub message: String,
135+
pub context: HashMap<String, String>, // TODO: Once it's possible, this hashmap should default to empty
136+
}
137+
138+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
139+
pub struct CreateBotConversationFeedbackParams {
140+
pub rating_value: FeedbackRating,
141+
}
142+
143+
#[derive(Debug, PartialEq, Eq, uniffi::Enum, Serialize_repr, Deserialize_repr)]
144+
#[repr(u8)]
145+
pub enum FeedbackRating {
146+
Positive = 1,
147+
Negative = 0,
148+
}
149+
150+
impl_as_query_value_for_new_type!(BotId);
151+
uniffi::custom_newtype!(BotId, String);
152+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153+
#[serde(transparent)]
154+
pub struct BotId(pub String);
155+
156+
impl std::fmt::Display for BotId {
157+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158+
write!(f, "{}", self.0)
159+
}
160+
}
161+
162+
impl_as_query_value_for_new_type!(ChatId);
163+
uniffi::custom_newtype!(ChatId, u64);
164+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
165+
pub struct ChatId(pub u64);
166+
167+
impl std::str::FromStr for ChatId {
168+
type Err = std::num::ParseIntError;
169+
170+
fn from_str(s: &str) -> Result<Self, Self::Err> {
171+
s.parse().map(Self)
172+
}
173+
}
174+
175+
impl std::fmt::Display for ChatId {
176+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177+
write!(f, "{}", self.0)
178+
}
179+
}
180+
181+
impl_as_query_value_for_new_type!(MessageId);
182+
uniffi::custom_newtype!(MessageId, u64);
183+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
184+
pub struct MessageId(pub u64);
185+
186+
impl std::str::FromStr for MessageId {
187+
type Err = std::num::ParseIntError;
188+
189+
fn from_str(s: &str) -> Result<Self, Self::Err> {
190+
s.parse().map(Self)
191+
}
192+
}
193+
194+
impl std::fmt::Display for MessageId {
195+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196+
write!(f, "{}", self.0)
197+
}
198+
}
199+
200+
#[cfg(test)]
201+
mod tests {
202+
use super::*;
203+
204+
#[test]
205+
fn test_bot_create_conversation_response_deserialization() {
206+
let json = include_str!("../../tests/wpcom/support_bots/create-conversation-response.json");
207+
let conversation: BotConversation =
208+
serde_json::from_str(json).expect("Failed to deserialize bot conversation");
209+
assert_eq!(conversation.chat_id, 1965886);
210+
}
211+
212+
#[test]
213+
fn test_bot_conversation_deserialization() {
214+
let json = include_str!("../../tests/wpcom/support_bots/single-conversation.json");
215+
let conversation: BotConversation =
216+
serde_json::from_str(json).expect("Failed to deserialize bot conversation");
217+
assert_eq!(conversation.chat_id, 1965758);
218+
}
219+
220+
#[test]
221+
fn test_bot_conversation_summary_deserialization() {
222+
let json = include_str!("../../tests/wpcom/support_bots/converation-list.json");
223+
let conversations: Vec<BotConversationSummary> =
224+
serde_json::from_str(json).expect("Failed to deserialize bot conversation summary");
225+
assert_eq!(conversations.len(), 1);
226+
assert_eq!(conversations[0].chat_id, 1965758);
227+
}
228+
229+
#[test]
230+
fn test_add_message_to_bot_conversation_response_deserialization() {
231+
let json = include_str!(
232+
"../../tests/wpcom/support_bots/add-message-to-conversation-response.json"
233+
);
234+
let conversation: BotConversation =
235+
serde_json::from_str(json).expect("Failed to deserialize bot conversation");
236+
assert_eq!(conversation.chat_id, 1965758);
237+
}
238+
}

0 commit comments

Comments
 (0)