Skip to content

Commit

Permalink
feat(rbx_mantle,rbx_api): add notification string support (#187)
Browse files Browse the repository at this point in the history
* feat(rbx_mantle,rbx_api): add notification string support

* Add notifications to state

* changed notifications to a HashMap and fixed other issues

* fix: linting errors

* fix: wrong line ending

* improve naming

* fix naming

* add docs

* improve docs language

---------

Co-authored-by: Kevin <[email protected]>
  • Loading branch information
blake-mealey and brinkokevin authored Mar 25, 2023
1 parent 273cdf0 commit 955b3df
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 0 deletions.
1 change: 1 addition & 0 deletions mantle/rbx_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod game_passes;
pub mod groups;
mod helpers;
pub mod models;
pub mod notifications;
pub mod places;
pub mod social_links;
pub mod spatial_voice;
Expand Down
110 changes: 110 additions & 0 deletions mantle/rbx_api/src/notifications/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
pub mod models;

use serde_json::json;

use crate::{
errors::RobloxApiResult,
helpers::{handle, handle_as_json},
models::AssetId,
RobloxApi,
};

use self::models::{
CreateNotificationResponse, ListNotificationResponse, ListNotificationsResponse,
};

impl RobloxApi {
pub async fn create_notification(
&self,
experience_id: AssetId,
name: String,
content: String,
) -> RobloxApiResult<CreateNotificationResponse> {
let req = self
.client
.post("https://apis.roblox.com/notifications/v1/developer-configuration/create-notification")
.json(&json!({
"universeId": experience_id,
"name": name,
"content": content,
}));

handle_as_json(req).await
}

pub async fn update_notification(
&self,
notification_id: String,
name: String,
content: String,
) -> RobloxApiResult<()> {
let req = self
.client
.post("https://apis.roblox.com/notifications/v1/developer-configuration/update-notification")
.json(&json!({
"id": notification_id,
"name": name,
"content": content,
}));

handle(req).await?;

Ok(())
}

pub async fn archive_notification(&self, notification_id: String) -> RobloxApiResult<()> {
let req = self
.client
.post("https://apis.roblox.com/notifications/v1/developer-configuration/archive-notification")
.json(&json!({
"id": notification_id,
}));

handle(req).await?;

Ok(())
}

pub async fn list_notifications(
&self,
experience_id: AssetId,
count: u8,
page_cursor: Option<String>,
) -> RobloxApiResult<ListNotificationsResponse> {
let mut req = self
.client
.get("https://apis.roblox.com/notifications/v1/developer-configuration/experience-notifications-list")
.query(&[
("universeId", &experience_id.to_string()),
("count", &count.to_string()),
]);
if let Some(page_cursor) = page_cursor {
req = req.query(&[("cursor", &page_cursor)]);
}

handle_as_json(req).await
}

pub async fn get_all_notifications(
&self,
experience_id: AssetId,
) -> RobloxApiResult<Vec<ListNotificationResponse>> {
let mut all_notifications = Vec::new();

let mut page_cursor: Option<String> = None;
loop {
let res = self
.list_notifications(experience_id, 100, page_cursor)
.await?;
all_notifications.extend(res.notification_string_configs);

if res.next_page_cursor.is_none() {
break;
}

page_cursor = res.next_page_cursor;
}

Ok(all_notifications)
}
}
23 changes: 23 additions & 0 deletions mantle/rbx_api/src/notifications/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use serde::Deserialize;

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CreateNotificationResponse {
pub id: String,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ListNotificationsResponse {
pub notification_string_configs: Vec<ListNotificationResponse>,
pub previous_page_cursor: Option<String>,
pub next_page_cursor: Option<String>,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ListNotificationResponse {
pub id: String,
pub name: String,
pub content: String,
}
34 changes: 34 additions & 0 deletions mantle/rbx_mantle/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,40 @@ pub struct ExperienceTargetConfig {

/// Spatial voice configuration.
pub spatial_voice: Option<SpatialVoiceTargetConfig>,

/// Notification strings for your experience.
///
/// By default, the name of each notification (which is only visible to you in the creator portal) is set
/// to the label of the notification config. You can override this by setting the `name` property.
///
/// ```yml title="Example"
/// target:
/// experience:
/// notifications:
/// customInvitePrompt:
/// content: '{displayName} is inviting you to join {experienceName}!'
/// ```
///
/// ```yml title="Example with custom name"
/// target:
/// experience:
/// notifications:
/// customInvitePrompt:
/// name: Custom Invite Prompt
/// content: '{displayName} is inviting you to join {experienceName}!'
/// ```
pub notifications: Option<HashMap<String, NotificationTargetConfig>>,
}

#[derive(JsonSchema, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct NotificationTargetConfig {
/// The display name of the notification string on the Roblox website.
pub name: Option<String>,

/// The content of the notification string.
/// Must include {experienceName} placeholder and may include {displayName} placeholder once.
pub content: String,
}

#[derive(JsonSchema, Serialize, Deserialize, Clone)]
Expand Down
37 changes: 37 additions & 0 deletions mantle/rbx_mantle/src/roblox_resource_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rbx_api::{
experiences::models::{CreateExperienceResponse, ExperienceConfigurationModel},
game_passes::models::{CreateGamePassResponse, GetGamePassResponse},
models::{AssetId, AssetTypeId, CreatorType, UploadImageResponse},
notifications::models::CreateNotificationResponse,
places::models::{GetPlaceResponse, PlaceConfigurationModel},
social_links::models::{CreateSocialLinkResponse, SocialLinkType},
spatial_voice::models::UpdateSpatialVoiceSettingsRequest,
Expand Down Expand Up @@ -111,6 +112,13 @@ pub struct SpatialVoiceInputs {
pub enabled: bool,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NotificationInputs {
pub name: String,
pub content: String,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::large_enum_variant)]
Expand All @@ -134,6 +142,7 @@ pub enum RobloxInputs {
AudioAsset(FileWithGroupIdInputs),
AssetAlias(AssetAliasInputs),
SpatialVoice(SpatialVoiceInputs),
Notification(NotificationInputs),
}

#[derive(Serialize, Deserialize, Clone)]
Expand All @@ -149,6 +158,12 @@ pub struct AssetOutputs {
pub asset_id: AssetId,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NotificationOutputs {
pub id: String,
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaceFileOutputs {
Expand Down Expand Up @@ -211,6 +226,7 @@ pub enum RobloxOutputs {
AudioAsset(AssetOutputs),
AssetAlias(AssetAliasOutputs),
SpatialVoice,
Notification(NotificationOutputs),
}

#[derive(Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -704,6 +720,16 @@ impl ResourceManager<RobloxInputs, RobloxOutputs> for RobloxResourceManager {

Ok(RobloxOutputs::SpatialVoice)
}
RobloxInputs::Notification(inputs) => {
let experience = single_output!(dependency_outputs, RobloxOutputs::Experience);

let CreateNotificationResponse { id } = self
.roblox_api
.create_notification(experience.asset_id, inputs.name, inputs.content)
.await?;

Ok(RobloxOutputs::Notification(NotificationOutputs { id }))
}
}
}

Expand Down Expand Up @@ -889,6 +915,14 @@ impl ResourceManager<RobloxInputs, RobloxOutputs> for RobloxResourceManager {

Ok(RobloxOutputs::SpatialVoice)
}
(RobloxInputs::Notification(inputs), RobloxOutputs::Notification(outputs)) => {
let asset_id = outputs.id.clone();
self.roblox_api
.update_notification(asset_id, inputs.name, inputs.content)
.await?;

Ok(RobloxOutputs::Notification(outputs))
}
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -1029,6 +1063,9 @@ impl ResourceManager<RobloxInputs, RobloxOutputs> for RobloxResourceManager {
)
.await?;
}
RobloxOutputs::Notification(outputs) => {
self.roblox_api.archive_notification(outputs.id).await?;
}
}
Ok(())
}
Expand Down
34 changes: 34 additions & 0 deletions mantle/rbx_mantle/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,24 @@ fn get_desired_experience_graph(
));
}

if let Some(notifications) = &target_config.notifications {
for (label, notification) in notifications {
let name = match &notification.name {
Some(name) => name.clone(),
None => label.clone(),
};

resources.push(RobloxResource::new(
&format!("notification_{}", label),
RobloxInputs::Notification(NotificationInputs {
name,
content: notification.content.to_string(),
}),
&[&experience],
));
}
}

Ok(ResourceGraph::new(&resources))
}

Expand Down Expand Up @@ -820,6 +838,22 @@ pub async fn import_graph(
));
}

logger::log("Importing notifications");
let notifications = roblox_api.get_all_notifications(target_id).await?;
for notification in notifications {
resources.push(RobloxResource::existing(
&format!("notification_{}", notification.name),
RobloxInputs::Notification(NotificationInputs {
name: notification.name,
content: notification.content,
}),
RobloxOutputs::Notification(NotificationOutputs {
id: notification.id,
}),
&[&experience],
));
}

Ok(ResourceGraph::new(&resources))
}

Expand Down

1 comment on commit 955b3df

@vercel
Copy link

@vercel vercel bot commented on 955b3df Mar 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.