apple_apns_cli/
cli.rs

1use std::path::PathBuf;
2
3use anyhow::{anyhow, Result};
4use apple_apns::{Endpoint, InterruptionLevel, Priority, PushType};
5use clap::{ArgGroup, Parser};
6use humantime::parse_duration;
7use time::{format_description::well_known::Iso8601, OffsetDateTime};
8use uuid::Uuid;
9
10/// Apple APNS
11#[derive(Parser)]
12#[command(author, version, about)]
13#[command(group(
14    ArgGroup::new("authentication")
15        .args(["client_pem_file", "key_pem_file"])
16        .required(true)
17), group(
18    ArgGroup::new("certificate")
19        .conflicts_with("token")
20        .arg("client_pem_file")
21        .requires("client_pem_file")
22), group(
23    ArgGroup::new("token")
24        .conflicts_with("certificate")
25        .args(["key_id", "key_pem_file", "team_id"])
26        .requires_all(["key_id", "key_pem_file", "team_id"])
27        .multiple(true)
28))]
29pub struct Cli {
30    #[arg(long, env)]
31    pub ca_pem_file: Option<PathBuf>,
32
33    #[arg(long, env)]
34    pub client_pem_file: Option<PathBuf>,
35
36    #[arg(long, env)]
37    pub key_id: Option<String>,
38
39    #[arg(long, env)]
40    pub key_pem_file: Option<String>,
41
42    #[arg(long, env)]
43    pub team_id: Option<String>,
44
45    #[arg(long, env)]
46    pub endpoint: Option<Endpoint>,
47
48    #[arg(long, env)]
49    pub user_agent: Option<String>,
50
51    /// The hex-encoded device token.
52    #[arg(long, env)]
53    pub device_token: String,
54
55    /// The push type of the notification to send.
56    #[arg(long, env, default_value_t = PushType::Alert)]
57    pub push_type: PushType,
58
59    /// A canonical UUID that is the unique ID for the notification.
60    #[arg(long, env)]
61    pub id: Option<Uuid>,
62
63    /// The date at which the notification is no longer valid.
64    #[arg(long, env, value_parser = parse_timestamp)]
65    pub expiration: Option<OffsetDateTime>,
66
67    /// The priority of the notification.
68    #[arg(long, env, default_value_t = Default::default())]
69    pub priority: Priority,
70
71    /// The topic for the notification, e.g. bundle ID or app ID.
72    #[arg(long, env)]
73    pub topic: Option<String>,
74
75    /// An identifier you use to coalesce multiple notifications into a single
76    /// notification for the user.
77    #[arg(long, env)]
78    pub collapse_id: Option<String>,
79
80    /// The title of the notification.
81    #[arg(long, env)]
82    pub title: Option<String>,
83
84    /// Additional information that explains the purpose of the
85    /// notification.
86    #[arg(long, env)]
87    pub subtitle: Option<String>,
88
89    /// The content of the alert message.
90    #[arg(long, env)]
91    pub body: Option<String>,
92
93    /// The name of the launch image file to display.
94    #[arg(long, env)]
95    pub launch_image: Option<String>,
96
97    /// The number to display in a badge on your app’s icon. Specify `0` to
98    /// remove the current badge, if any.
99    #[arg(long, env)]
100    pub badge: Option<u32>,
101
102    /// The name of a sound file in your app’s main bundle or in the
103    /// `Library/Sounds` folder of your app’s container directory.
104    #[arg(long, env)]
105    pub sound: Option<String>,
106
107    /// The volume for the critical alert’s sound. Set this to a value
108    /// between `0` (silent) and `1` (full volume).
109    #[arg(long, env)]
110    pub volume: Option<f64>,
111
112    /// An app-specific identifier for grouping related notifications.
113    #[arg(long, env)]
114    pub thread_id: Option<String>,
115
116    /// The notification’s type.
117    #[arg(long, env)]
118    pub category: Option<String>,
119
120    /// The background notification flag.
121    #[arg(long, env, default_value_t = false)]
122    pub content_available: bool,
123
124    /// The notification service app extension flag.
125    #[arg(long, env, default_value_t = false)]
126    pub mutable_content: bool,
127
128    /// The identifier of the window brought forward.
129    #[arg(long, env)]
130    pub target_content_id: Option<String>,
131
132    /// The importance and delivery timing of a notification.
133    #[arg(long, env)]
134    pub interruption_level: Option<InterruptionLevel>,
135
136    /// The relevance score, a number between `0` and `1`.
137    #[arg(long, env)]
138    pub relevance_score: Option<f64>,
139
140    /// Additional data to send.
141    #[arg(long, env)]
142    pub user_info: Option<serde_json::Value>,
143}
144
145fn parse_timestamp(arg: &str) -> Result<OffsetDateTime> {
146    match OffsetDateTime::parse(arg, &Iso8601::DEFAULT) {
147        Ok(timestamp) => Ok(timestamp),
148        Err(duration_err) => match parse_duration(arg) {
149            Ok(duration) => Ok(OffsetDateTime::now_utc() + duration),
150            Err(timestamp_err) => Err(anyhow!("Invalid expiration; invalid timestamp: {timestamp_err}; invalid duration: {duration_err}")),
151        },
152    }
153}