iPhone通过Shortcut(捷径)将验证码短信发送到Windows/macOS电脑

2026-07-24 0 786

文章摘要

本文介绍了一个通过iOS快捷指令自动化功能,将手机收到的验证码短信解析后,发送至同WiFi网络下电脑的监听服务,并自动复制到系统剪贴板的工具。该工具由Rust编写,支持Windows和macOS,旨在解决重装系统后频繁输入验证码的烦恼。用户需安装工作流、连接同一WiFi、运行程序并设置局域网IP。核心功能包括自动复制验证码、显示通知及系统托盘操作,源码已开源。关键词:验证码、自动化、Rust、剪贴板、WiFi。

功能说明
通过 IOS Shortcut的Automation功能,在收到验证码短信的时候,解析数字验证码,并将其发送到【同 wifi 网络下】电脑(windows/macos)的监听服务中,程序会自动复制到系统剪切板,我们只需要ctrl + v粘贴就可以了。

如果你使用mac,可以不用看了,你可以通过iphone message 自带的 Text Message Forwarding功能,在mac上直接看到验证码。但是我不喜欢这个功能,因为它会把所有的短信都转发,有泄露隐私的风险。

写在前面(为什么做这个)
最近被气死!
重装了windows系统,在重新登陆很多网站的时候发现即使输入了账户密码,网站还是要求要验证码验证。 要不停在手机和电脑屏幕间切换,一下子就把想专注工作的心思打碎了。
为了避免下次重装再被气死,也方便有同样需求的网友,于是就有了这个帖子,因为最近在学习rust,所以这个算是用rust写的练手项目。

如何使用
1. 安装我的工作流(https://www.icloud.com/shortcuts/750e2f275d464e9da34812eabc289d74)或者看图自己设置:
iPhone通过Shortcut(捷径)将验证码短信发送到Windows/macOS电脑
2.  让iPhone和电脑连接同个网络(wifi);
3. 在电脑运行我的程序 sms_notification,sms_notification 使用 rust 编写:你根据平台可以下载附件中的【macOS版本】或【Windows版本】并运行(默认端口8080,当然你也可以在启动程序时传入不同的端口号):
包含mac和windows链接:https://pan.quark.cn/s/51a2245d6fe8
当然,你也可以下载代码自行编译【源码和cargo.toml】然后运行;
4. 在运行 sms_notification 的电脑获取电脑的局域网ip(比如 192.168.3.232):Windows: 使用命令 ipconfig /all 找到本地 ipv4 地址;macOS: 使用命令 ifconfig -a 找到本地 ipv4 地址;修改【工作流】中的 Text 中 网址部分的 ip;
5. 找个发送短信的网页(比如豆包)尝试验证码登陆;
效果展示
iPhone通过Shortcut(捷径)将验证码短信发送到Windows/macOS电脑
源码
如需自行编译,请使用cargo新建一个项目,并使用下方代码替换你的main.rs, cargo.toml。然后使用命令 cargo build –release编译release版本
main.rs

  1. use clap::Parser;
  2. use tracing::{info, error};
  3. use tokio::sync::mpsc;
  4. use std::convert::Infallible;
  5. use warp::Filter;
  6. #[derive(Parser, Debug)]
  7. #[command(name = “sms_notification”)]
  8. #[command(about = “HTTP server that copies SMS to clipboard and shows notifications”)]
  9. struct Args {
  10.     #[arg(short, long, default_value_t = 8080)]
  11.     port: u16,
  12. }
  13. #[derive(Clone)]
  14. struct AppState {
  15.     sms_tx: mpsc::Sender<String>,
  16. }
  17. async fn handle_sms(
  18.     query: std::collections::HashMap<String, String>,
  19.     state: AppState,
  20. ) -> Result<impl warp::Reply, Infallible> {
  21.     if let Some(sms) = query.get(“sms”) {
  22.         let sms_clone = sms.clone();
  23.         if state.sms_tx.send(sms_clone).await.is_err() {
  24.             error!(“Failed to send SMS to notification handler”);
  25.         }
  26.         info!(“Received SMS: {}”, sms);
  27.         println!(“sms: {}”, sms);
  28.         Ok(warp::reply::html(format!(“SMS received: {}”, sms)))
  29.     } else {
  30.         Ok(warp::reply::html(“Usage: GET /?sms=<message>”.to_string()))
  31.     }
  32. }
  33. async fn start_http_server(port: u16, sms_tx: mpsc::Sender<String>) {
  34.     let state = AppState { sms_tx };
  35.     let route = warp::path::end()
  36.         .and(warp::get())
  37.         .and(warp::query::<std::collections::HashMap<String, String>>())
  38.         .and(with_state(state))
  39.         .and_then(handle_sms);
  40.     let (_, server) = warp::serve(route)
  41.         .bind_with_graceful_shutdown(([0, 0, 0, 0], port), async {
  42.             tokio::time::sleep(tokio::time::Duration::MAX).await
  43.         });
  44.     info!(“HTTP server listening on port {}”, port);
  45.     server.await;
  46. }
  47. fn with_state(state: AppState) -> impl Filter<Extract = (AppState,), Error = Infallible> + Clone {
  48.     warp::any().map(move || state.clone())
  49. }
  50. fn copy_to_clipboard(text: &str) -> bool {
  51.     match arboard::Clipboard::new() {
  52.         Ok(mut clipboard) => {
  53.             match clipboard.set_text(text) {
  54.                 Ok(_) => {
  55.                     info!(“Copied to clipboard: {}”, text);
  56.                     true
  57.                 }
  58.                 Err(e) => {
  59.                     error!(“Failed to set clipboard: {:?}”, e);
  60.                     false
  61.                 }
  62.             }
  63.         }
  64.         Err(e) => {
  65.             error!(“Failed to open clipboard: {:?}”, e);
  66.             false
  67.         }
  68.     }
  69. }
  70. fn show_dialog(title: &str, body: &str) {
  71.     let result = rfd::MessageDialog::new()
  72.         .set_title(title)
  73.         .set_description(body)
  74.         .show();
  75.     if !result {
  76.         error!(“Failed to show dialog: show() returned false”);
  77.     } else {
  78.         info!(“Dialog shown: {} – {}”, title, body);
  79.     }
  80. }
  81. fn main() {
  82.     // Parse CLI
  83.     let args = Args::parse();
  84.     // Setup logging to file + stdout
  85.     let log_file = std::fs::File::create(“sms_notification.log”).unwrap();
  86.     let (non_blocking, _guard) = tracing_appender::non_blocking(log_file);
  87.     tracing_subscriber::fmt()
  88.         .with_writer(non_blocking)
  89.         .with_ansi(false)
  90.         .init();
  91.     info!(“SMS Notification App starting on port {}”, args.port);
  92.     // Shared state for last SMS (for tray click -> clipboard)
  93.     use std::sync::Arc;
  94.     use tokio::sync::Mutex;
  95.     let last_sms = Arc::new(Mutex::new(String::new()));
  96.     let (sms_tx, mut sms_rx) = mpsc::channel::<String>(100);
  97.     // Clipboard channel receiver — runs in background thread
  98.     let last_sms_for_receiver = last_sms.clone();
  99.     std::thread::spawn(move || {
  100.         let rt = tokio::runtime::Runtime::new().unwrap();
  101.         rt.block_on(async {
  102.             while let Some(sms) = sms_rx.recv().await {
  103.                 *last_sms_for_receiver.lock().await = sms.clone();
  104.                 copy_to_clipboard(&sms);
  105.                 // Show notification in a separate thread to avoid blocking the receiver
  106.                 let sms_for_notify = sms.clone();
  107.                 std::thread::spawn(move || {
  108.                     show_dialog(“SMS Received”, &sms_for_notify);
  109.                 });
  110.             }
  111.         });
  112.     });
  113.     // Spawn HTTP server
  114.     let http_port = args.port;
  115.     let sms_tx_clone = sms_tx.clone();
  116.     std::thread::spawn(move || {
  117.         let rt = tokio::runtime::Runtime::new().unwrap();
  118.         rt.block_on(start_http_server(http_port, sms_tx_clone));
  119.     });
  120.     // Build SystemTray with menu
  121.     use tao::{
  122.         menu::{ContextMenu, MenuItemAttributes, MenuId},
  123.         system_tray::{SystemTrayBuilder, Icon},
  124.         event_loop::{EventLoop, ControlFlow, EventLoopWindowTarget},
  125.     };
  126.     // Create a simple 1×1 icon (solid color)
  127.     let icon_data = vec![0u8; 4]; // RGBA: all zeros = transparent black
  128.     let icon = Icon::from_rgba(icon_data, 1, 1).expect(“Failed to create icon”);
  129.     // Build context menu
  130.     let mut menu = ContextMenu::new();
  131.     let about_item = MenuItemAttributes::new(“About”)
  132.         .with_id(MenuId::new(“about”));
  133.     let log_item = MenuItemAttributes::new(“Log”)
  134.         .with_id(MenuId::new(“log”));
  135.     let exit_item = MenuItemAttributes::new(“Exit”)
  136.         .with_id(MenuId::new(“exit”));
  137.     menu.add_item(about_item);
  138.     menu.add_item(log_item);
  139.     menu.add_item(exit_item);
  140.     // Create EventLoop and build SystemTray
  141.     let event_loop: EventLoop<()> = EventLoop::new();
  142.     let window_target: &EventLoopWindowTarget<()> = &event_loop;
  143.     let _system_tray = SystemTrayBuilder::new(icon, Some(menu))
  144.         .with_tooltip(“SMS Notification”)
  145.         .build(window_target)
  146.         .expect(“Failed to build system tray”);
  147.     let last_sms_for_tray = last_sms.clone();
  148.     event_loop.run(move |event, _, control_flow| {
  149.         use tao::event::Event;
  150.         use tao::event::TrayEvent as TrayEventType;
  151.         match event {
  152.             Event::TrayEvent { event: TrayEventType::LeftClick, .. } => {
  153.                 // Tray icon left click – copy last SMS to clipboard
  154.                 let last = last_sms_for_tray.clone();
  155.                 std::thread::spawn(move || {
  156.                     let rt = tokio::runtime::Runtime::new().unwrap();
  157.                     rt.block_on(async {
  158.                         let sms = last.lock().await;
  159.                         if !sms.is_empty() {
  160.                             copy_to_clipboard(&sms);
  161.                             show_dialog(“SMS Copied”, &sms);
  162.                         }
  163.                     });
  164.                 });
  165.             }
  166.             Event::MenuEvent { menu_id, .. } => {
  167.                 // Menu item clicked – compare by creating MenuId from same strings
  168.                 let about_id = MenuId::new(“about”);
  169.                 let log_id = MenuId::new(“log”);
  170.                 let exit_id = MenuId::new(“exit”);
  171.                 if menu_id == about_id {
  172.                     let about_text = format!(“SMS Notification App\nVersion 0.1.0\nPort: {}”, args.port);
  173.                     std::thread::spawn(move || {
  174.                         show_dialog(“About SMS Notification”, &about_text);
  175.                     });
  176.                 } else if menu_id == log_id {
  177.                     std::thread::spawn(move || {
  178.                         if let Ok(log_content) = std::fs::read_to_string(“sms_notification.log”) {
  179.                             let lines: Vec<&str> = log_content.lines().rev().take(20).collect();
  180.                             show_dialog(“Recent Logs”, &lines.join(“\n”));
  181.                         } else {
  182.                             show_dialog(“Logs”, “No logs found.”);
  183.                         }
  184.                     });
  185.                 } else if menu_id == exit_id {
  186.                     std::process::exit(0);
  187.                 }
  188.             }
  189.             _ => {}
  190.         }
  191.         *control_flow = ControlFlow::Wait;
  192.     });
  193. }
复制代码

cargo.toml

  1. [package]
  2. name = “sms_notification”
  3. version = “0.1.0”
  4. edition = “2021”
  5. [dependencies]
  6. tokio = { version = “1”, features = [“full”] }
  7. warp = “0.3”
  8. tao = { version = “0.18”, features = [“tray”] }
  9. arboard = “3”
  10. tracing = “0.1”
  11. tracing-subscriber = { version = “0.3”, features = [“env-filter”] }
  12. tracing-appender = “0.2”
  13. clap = { version = “4”, features = [“derive”] }
  14. rfd = “0.11”
  15. [profile.release]
  16. strip = true
  17. lto = true
复制代码

 

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

本站所有资源来源于网络,仅限用于学习研究;无任何技术支持!不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除内容。如果您喜欢,请支持正版。如有侵权请邮件与我们联系处理。

iPhone通过Shortcut(捷径)将验证码短信发送到Windows/macOS电脑
下一篇:

已经没有下一篇了!

常见问题
  • 网盘有时候会因为名字 关键词导致失效 大家可以给管理员提供失效信息,我们会给大家适当积分进行奖励 我们会第一时间进行补充修正 感谢大家的配合 让我们共同努力 打造良好的资源分享平台
查看详情

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务