/*
* Zernio API
*
* API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api Versioning and deprecation: all endpoints are versioned in the URL path (current version: /v1). Breaking changes only ship in a new path version; existing versions keep working. Deprecated operations are marked 'deprecated: true' in this spec and announced in the changelog (https://zernio.com/changelog) before removal. Errors: every 4xx/5xx response is application/json with a machine-readable 'code' and a human-readable 'error' message (see the ErrorResponse schema). Request ids: responses carry an X-Request-Id header with the id we log the request under. Quote it when reporting a problem. A valid x-request-id you send is reused as that id.
*
* The version of the OpenAPI document: 1.413.0
* Contact: support@zernio.com
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct CreateCommentAutomationRequest {
#[serde(rename = "profileId")]
pub profile_id: String,
/// Instagram, Facebook, TikTok, Threads, LinkedIn or YouTube account ID. On the last four the automation only posts the public reply. X accounts are refused (400, code platform_not_supported) while X comment polling is off.
#[serde(rename = "accountId")]
pub account_id: String,
/// What fires the automation: - 'comment': keyword comment on a post. - 'live_comment': keyword comment on an Instagram live broadcast. - 'story_reply': keyword reply to an Instagram story. platformPostId is the story media id (omit for any story). - 'story_mention': a story mentioning the Instagram account. Every trigger but 'comment' is Instagram only; reply-only platforms accept 'comment' only.
#[serde(rename = "trigger", skip_serializing_if = "Option::is_none")]
pub trigger: Option<Trigger>,
/// Platform media/post ID (or story media id when trigger=story_reply). Omit for an account-wide (any-post / any-story) automation.
#[serde(rename = "platformPostId", skip_serializing_if = "Option::is_none")]
pub platform_post_id: Option<String>,
/// Zernio post ID (24 hexadecimal characters); platform IDs return 400. Optional and never required. Use it INSTEAD of platformPostId to bind a per-post automation to a not-yet-published Zernio post: the automation stays pending and arms itself when that post publishes. For a post already live on the platform, pass platformPostId alone and omit this.
#[serde(rename = "postId", skip_serializing_if = "Option::is_none")]
pub post_id: Option<String>,
/// Post content snippet for display
#[serde(rename = "postTitle", skip_serializing_if = "Option::is_none")]
pub post_title: Option<String>,
/// Automation label
#[serde(rename = "name")]
pub name: String,
/// Trigger keywords (empty = any comment triggers)
#[serde(rename = "keywords", skip_serializing_if = "Option::is_none")]
pub keywords: Option<Vec<String>>,
/// How a keyword is compared with the comment. 'contains' (default) matches anywhere, even inside another word (keyword 'app' fires on 'happy'). 'word' matches the keyword only as a standalone word. 'exact' requires the whole comment to be exactly the keyword.
#[serde(rename = "matchMode", skip_serializing_if = "Option::is_none")]
pub match_mode: Option<MatchMode>,
/// Comments containing one of these never trigger the automation, even when a trigger keyword also matches. Compared using the same matchMode.
#[serde(rename = "excludeKeywords", skip_serializing_if = "Option::is_none")]
pub exclude_keywords: Option<Vec<String>>,
/// Only with matchMode=word: also fire on close misspellings of a keyword (one edit for 4-7 character keywords, two from 8 up). Keywords shorter than 4 characters are never fuzzy-matched.
#[serde(rename = "typoTolerance", skip_serializing_if = "Option::is_none")]
pub typo_tolerance: Option<bool>,
/// DM text sent to the commenter. Required on Instagram and Facebook (400 missing_required_field when absent). Rejected on TikTok, Threads, LinkedIn and YouTube, which have no private reply (400 invalid_field_value). Max 640 chars when buttons are set, otherwise ~1000.
#[serde(rename = "dmMessage", skip_serializing_if = "Option::is_none")]
pub dm_message: Option<String>,
/// Optional inline DM buttons (1-3). Phone buttons are Facebook-only. Omit or pass [] for a plain-text DM.
#[serde(rename = "buttons", skip_serializing_if = "Option::is_none")]
pub buttons: Option<Vec<models::DmButton>>,
/// Optional product card sent INSTEAD of the plain dmMessage bubble. Mutually exclusive with buttons. dmMessage stays required: it is what gets sent the moment the card is cleared.
#[serde(
rename = "template",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub template: Option<Option<Box<models::CommentAutomationTemplate>>>,
/// Public reply to the comment. Optional on Instagram and Facebook; required on TikTok, Threads, LinkedIn and YouTube, where it is the automation's only action.
#[serde(rename = "commentReply", skip_serializing_if = "Option::is_none")]
pub comment_reply: Option<String>,
/// Optional alternate DM texts for random rotation. When set, each triggered comment sends one picked at random from [dmMessage, ...dmMessageVariations], so repeat commenters get slightly different DMs (helps avoid identical-message patterns). Up to 5. Buttons are attached to whichever text is picked, not varied.
#[serde(
rename = "dmMessageVariations",
skip_serializing_if = "Option::is_none"
)]
pub dm_message_variations: Option<Vec<String>>,
/// Optional alternate public replies, rotated at random alongside commentReply (picked independently of the DM). Up to 5.
#[serde(
rename = "commentReplyVariations",
skip_serializing_if = "Option::is_none"
)]
pub comment_reply_variations: Option<Vec<String>>,
/// Wrap link buttons in the DM in a tracked redirect so clicks are counted (Link Clicks / CTR). Pass false to send links exactly as written. Defaults to on.
#[serde(rename = "linkTracking", skip_serializing_if = "Option::is_none")]
pub link_tracking: Option<bool>,
/// Optional tag applied to a contact when they click a tracked link (requires linkTracking). Lets you segment clickers for broadcasts/sequences.
#[serde(rename = "clickTag", skip_serializing_if = "Option::is_none")]
pub click_tag: Option<String>,
/// Seconds to wait after the trigger before sending the DM. Omit or send 0 to reply immediately (the default). Max 86400 (24h). The trigger is still matched and deduplicated the moment the comment arrives, so a delay only moves when the response is sent.
#[serde(rename = "dmDelaySeconds", skip_serializing_if = "Option::is_none")]
pub dm_delay_seconds: Option<i32>,
/// Seconds to wait before posting the public comment reply. Omit or send 0 to post it right after the DM (the default). The reply never goes out before the DM, so a value below dmDelaySeconds is raised to it. Ignored when trigger=story_reply, which has no public reply.
#[serde(
rename = "commentReplyDelaySeconds",
skip_serializing_if = "Option::is_none"
)]
pub comment_reply_delay_seconds: Option<i32>,
/// Also fire these keywords on a plain inbound DM, so the automation answers people who message the keyword instead of commenting it. - Requires at least one keyword (an empty list means 'match anything', which would answer every inbound message). - Rejected on story_reply automations, which already trigger on DMs. - Dedup is per door: a contact who already received the DM from their comment can still receive it from a DM.
#[serde(rename = "alsoMatchInDms", skip_serializing_if = "Option::is_none")]
pub also_match_in_dms: Option<bool>,
#[serde(rename = "audience", skip_serializing_if = "Option::is_none")]
pub audience: Option<Box<models::CommentAutomationAudience>>,
#[serde(rename = "followGate", skip_serializing_if = "Option::is_none")]
pub follow_gate: Option<Box<models::CommentAutomationFollowGate>>,
#[serde(rename = "repeatPolicy", skip_serializing_if = "Option::is_none")]
pub repeat_policy: Option<Box<models::CommentAutomationRepeatPolicy>>,
/// Skip the DM when this recipient already received identical DM text (after personalisation) from this account, from any automation, within this many hours. The skip is logged with status skipped.
#[serde(
rename = "dedupeSameTextHours",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub dedupe_same_text_hours: Option<Option<i32>>,
/// 'after_dm' posts commentReply only after a successful DM. 'always' posts it whatever the audience rule, dedupe or DM outcome: the moment a comment matches, or after commentReplyDelaySeconds when set (raised to dmDelaySeconds, so it never precedes the DM attempt).
#[serde(rename = "publicReplyPolicy", skip_serializing_if = "Option::is_none")]
pub public_reply_policy: Option<PublicReplyPolicy>,
#[serde(rename = "actions", skip_serializing_if = "Option::is_none")]
pub actions: Option<Box<models::CommentAutomationActions>>,
/// Opt-in quick-reply chips on the DM (up to 13). Chips do not render in Message Requests, where a first DM to a cold commenter lands, so prefer buttons for first contact. Mutually exclusive with buttons and template (400).
#[serde(rename = "quickReplies", skip_serializing_if = "Option::is_none")]
pub quick_replies: Option<Vec<models::CommentAutomationQuickReply>>,
#[serde(
rename = "dmMedia",
default,
with = "::serde_with::rust::double_option",
skip_serializing_if = "Option::is_none"
)]
pub dm_media: Option<Option<Box<models::CommentAutomationDmMedia>>>,
/// Create the automation paused with false.
#[serde(rename = "isActive", skip_serializing_if = "Option::is_none")]
pub is_active: Option<bool>,
}
impl CreateCommentAutomationRequest {
pub fn new(
profile_id: String,
account_id: String,
name: String,
) -> CreateCommentAutomationRequest {
CreateCommentAutomationRequest {
profile_id,
account_id,
trigger: None,
platform_post_id: None,
post_id: None,
post_title: None,
name,
keywords: None,
match_mode: None,
exclude_keywords: None,
typo_tolerance: None,
dm_message: None,
buttons: None,
template: None,
comment_reply: None,
dm_message_variations: None,
comment_reply_variations: None,
link_tracking: None,
click_tag: None,
dm_delay_seconds: None,
comment_reply_delay_seconds: None,
also_match_in_dms: None,
audience: None,
follow_gate: None,
repeat_policy: None,
dedupe_same_text_hours: None,
public_reply_policy: None,
actions: None,
quick_replies: None,
dm_media: None,
is_active: None,
}
}
}
/// What fires the automation: - 'comment': keyword comment on a post. - 'live_comment': keyword comment on an Instagram live broadcast. - 'story_reply': keyword reply to an Instagram story. platformPostId is the story media id (omit for any story). - 'story_mention': a story mentioning the Instagram account. Every trigger but 'comment' is Instagram only; reply-only platforms accept 'comment' only.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Trigger {
#[serde(rename = "comment")]
Comment,
#[serde(rename = "live_comment")]
LiveComment,
#[serde(rename = "story_reply")]
StoryReply,
#[serde(rename = "story_mention")]
StoryMention,
}
impl Default for Trigger {
fn default() -> Trigger {
Self::Comment
}
}
/// How a keyword is compared with the comment. 'contains' (default) matches anywhere, even inside another word (keyword 'app' fires on 'happy'). 'word' matches the keyword only as a standalone word. 'exact' requires the whole comment to be exactly the keyword.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum MatchMode {
#[serde(rename = "exact")]
Exact,
#[serde(rename = "contains")]
Contains,
#[serde(rename = "word")]
Word,
}
impl Default for MatchMode {
fn default() -> MatchMode {
Self::Exact
}
}
/// 'after_dm' posts commentReply only after a successful DM. 'always' posts it whatever the audience rule, dedupe or DM outcome: the moment a comment matches, or after commentReplyDelaySeconds when set (raised to dmDelaySeconds, so it never precedes the DM attempt).
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum PublicReplyPolicy {
#[serde(rename = "after_dm")]
AfterDm,
#[serde(rename = "always")]
Always,
}
impl Default for PublicReplyPolicy {
fn default() -> PublicReplyPolicy {
Self::AfterDm
}
}