前言
飞猫M20是一款实用的随身WiFi设备,除了基本的网络功能外,我们还可以深度挖掘其潜力,实现短信自动转发功能。本文将详细介绍如何在这款设备上实现短信内容自动推送到飞书,并确保系统重启后服务能自动运行,让您随时随地掌握重要短信信息。

实现原理
飞猫M20运行的是基于BusyBox的嵌入式Linux系统。通过编写Shell脚本实现短信监控和转发的核心功能,再配合系统自启动配置,确保服务的持久运行。整个过程主要包括:
- 自动登录设备管理界面
- 定期检查新短信
- 解析短信内容
- 通过飞书Webhook推送通知
- 配置系统自启动
核心脚本实现
1. 短信转发主脚本 (sms_forward.sh)
这是实现短信转发功能的核心脚本:
#!/bin/sh
# 检查是否已经有实例在运行
LOCKFILE="/tmp/sms_forward.lock"
if [ -e "$LOCKFILE" ] && kill -0 $(cat "$LOCKFILE") 2>/dev/null; then
echo "短信转发服务已经在运行"
exit 1
fi
# 设置锁定文件
echo $$ > "$LOCKFILE"
trap "rm -f '$LOCKFILE'" EXIT
# 配置 - 请根据需要修改这些值
SMS_API_URL="http://192.168.88.1/action/sms_get_sms_list"
LOGIN_URL="http://192.168.88.1/goform/login"
SYNC_URL="http://192.168.88.1/goform/sync"
FEISHU_WEBHOOK_URL="飞书webhook地址"
CHECK_INTERVAL=15 # 每隔几秒检查一次
DEVICE_MODEL="设备名称"
SIM_INFO="手机号"
# 文件路径
LAST_INDEX_FILE="/tmp/last_sms_index.txt"
LOG_FILE="/tmp/sms_forward.log"
COOKIE_FILE="/tmp/sms_session_cookie.txt"
# 如果索引文件不存在,则初始化
if [ ! -f "$LAST_INDEX_FILE" ]; then
echo "0" > "$LAST_INDEX_FILE"
fi
# 日志函数
log() {
local message="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $message" >> "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $message"
}
# 启动日志
log "短信转发服务已启动"
# 获取会话Cookie的函数
get_session_cookie() {
# 清理旧的cookie文件
rm -f "$COOKIE_FILE" "$COOKIE_FILE.session" 2>/dev/null
# 步骤1: 首先获取同步请求和初始Cookie
SYNC_RESPONSE=$(curl -s -i -X POST "$SYNC_URL" \
-H 'User-Agent: Mozilla/5.0' \
-H 'Accept: text/plain, */*; q=0.01' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: application/x-mgdata' \
-H 'Origin: http://192.168.88.1' \
-H 'Referer: http://192.168.88.1/common/login.html')
# 提取Cookie
SESSION_COOKIE=$(echo "$SYNC_RESPONSE" | grep -i "Set-Cookie:" | sed 's/^Set-Cookie: //i' | sed 's/;.*$//')
# 如果无法获取Cookie,则退出
if [ -z "$SESSION_COOKIE" ]; then
log "错误: 无法获取会话Cookie"
return 1
fi
echo "$SESSION_COOKIE" > "$COOKIE_FILE"
# 步骤2: 使用静态加密值进行登录,并获取登录后的新Cookie
# 使用固定的加密值 - 这些值在网页中已经预先计算好
USERNAME_STATIC="4cc68e3626e5b94602c325f7c4ca5dee"
PASSWORD_STATIC="95bf0786a20922aec868627af48aa872"
REMEMBER_STATIC="d2e6057958b411672c3028b1976a41e1"
LOGIN_DATA="{\"username\":\"$USERNAME_STATIC\",\"password\":\"$PASSWORD_STATIC\",\"remember\":\"$REMEMBER_STATIC\"}"
LOGIN_RESPONSE=$(curl -s -i -X POST "$LOGIN_URL" \
-H 'User-Agent: Mozilla/5.0' \
-H 'Accept: application/json, text/javascript, */*; q=0.01' \
-H 'Content-Type: application/json' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'Origin: http://192.168.88.1' \
-H 'Referer: http://192.168.88.1/common/login.html' \
-H "Cookie: $SESSION_COOKIE" \
-d "$LOGIN_DATA")
# 从登录响应中提取新的Cookie
AUTH_COOKIE=$(echo "$LOGIN_RESPONSE" | grep -i "Set-Cookie:" | sed 's/^Set-Cookie: //i' | sed 's/;.*$//')
# 检查登录响应是否成功
if echo "$LOGIN_RESPONSE" | grep -q '"retcode":0'; then
if [ -n "$AUTH_COOKIE" ]; then
echo "$AUTH_COOKIE" > "$COOKIE_FILE.session"
else
cp "$COOKIE_FILE" "$COOKIE_FILE.session"
fi
return 0
else
log "错误: 登录失败"
return 1
fi
}
# 提取指定索引的短信数据
extract_sms_data() {
local JSON="$1"
local INDEX="$2"
local TEMP_FILE="/tmp/sms_all.txt"
# 使用简单直接的方法:先提取所有短信到单独文件
echo "$JSON" | sed 's/{"index":/\n{"index":/g' > "$TEMP_FILE"
# 使用简单字符串匹配来查找目标短信
grep "\"index\":$INDEX," "$TEMP_FILE" > "/tmp/sms_found.txt"
if [ ! -s "/tmp/sms_found.txt" ]; then
log "错误: 无法找到索引为 $INDEX 的短信"
rm -f "$TEMP_FILE" "/tmp/sms_found.txt"
return 1
fi
# 从匹配行中提取字段
FOUND_LINE=$(cat "/tmp/sms_found.txt")
PHONE=$(echo "$FOUND_LINE" | sed 's/.*"phone":"\([^"]*\)".*/\1/')
DATETIME=$(echo "$FOUND_LINE" | sed 's/.*"datetime":\([0-9]*\).*/\1/')
CONTENT=$(echo "$FOUND_LINE" | sed 's/.*"content":"\([^"]*\)".*/\1/')
# 清理临时文件
rm -f "$TEMP_FILE" "/tmp/sms_found.txt"
# 验证提取结果
if [ -z "$PHONE" ] || [ -z "$CONTENT" ]; then
log "错误: 无法正确提取短信 #$INDEX 的数据"
return 1
fi
return 0
}
# 主循环
while true; do
# 获取上次处理的最新短信索引
LAST_INDEX=$(cat "$LAST_INDEX_FILE")
# 检查是否有有效的会话Cookie
if [ ! -f "$COOKIE_FILE.session" ]; then
log "获取新的会话凭证"
get_session_cookie
if [ $? -ne 0 ]; then
log "无法获取有效的会话凭证,将在5分钟后重试"
sleep 300
continue
fi
fi
# 获取短信列表
SESSION_COOKIE=$(cat "$COOKIE_FILE.session")
SMS_RESPONSE=$(curl -s "$SMS_API_URL" \
-H 'User-Agent: Mozilla/5.0' \
-H 'Accept: application/json, text/javascript, */*; q=0.01' \
-H 'Content-Type: application/json' \
-H 'Pragma: no-cache' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'Origin: http://192.168.88.1' \
-H 'Referer: http://192.168.88.1/html/settings.html' \
-H "Cookie: $SESSION_COOKIE" \
--data-raw '{"start":1,"end":10,"smsbox":1}')
# 检查响应是否有效
if echo "$SMS_RESPONSE" | grep -q '"retcode":0'; then
# 提取所有索引并排序
echo "$SMS_RESPONSE" | grep -o '"index":[0-9]*' | sed 's/"index"://g' > /tmp/sms_indices.txt
sort -nr /tmp/sms_indices.txt > /tmp/sms_indices_sorted.txt
# 获取最高索引 (第一行)
HIGHEST_INDEX=$(sed -n '1p' /tmp/sms_indices_sorted.txt)
# 只有当有新消息时才处理
if [ -n "$HIGHEST_INDEX" ] && [ "$HIGHEST_INDEX" -gt "$LAST_INDEX" ]; then
log "发现新短信 ($LAST_INDEX -> $HIGHEST_INDEX)"
NEW_MESSAGES=0
# 处理每条新短信
while read -r INDEX; do
if [ "$INDEX" -gt "$LAST_INDEX" ]; then
# 提取短信数据
extract_sms_data "$SMS_RESPONSE" "$INDEX"
if [ $? -eq 0 ]; then
# 格式化日期时间
DATE_HUMAN=$(date -d "@$DATETIME" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')
# 使用飞书卡片格式
JSON="{
\"msg_type\": \"interactive\",
\"card\": {
\"header\": {
\"title\": {
\"tag\": \"plain_text\",
\"content\": \"短信通知\"
},
\"template\": \"blue\"
},
\"elements\": [
{
\"tag\": \"div\",
\"fields\": [
{
\"is_short\": true,
\"text\": {
\"tag\": \"lark_md\",
\"content\": \"📞 **发件人:** $PHONE\"
}
}
]
},
{
\"tag\": \"div\",
\"fields\": [
{
\"is_short\": true,
\"text\": {
\"tag\": \"lark_md\",
\"content\": \"⏰ **时间:**\"
}
},
{
\"is_short\": true,
\"text\": {
\"tag\": \"lark_md\",
\"content\": \"🔍 **来源:**\"
}
}
]
},
{
\"tag\": \"div\",
\"fields\": [
{
\"is_short\": true,
\"text\": {
\"tag\": \"plain_text\",
\"content\": \"$DATE_HUMAN\"
}
},
{
\"is_short\": true,
\"text\": {
\"tag\": \"plain_text\",
\"content\": \"$PHONE\"
}
}
]
},
{
\"tag\": \"hr\"
},
{
\"tag\": \"div\",
\"text\": {
\"tag\": \"lark_md\",
\"content\": \"📄 **内容:**\"
}
},
{
\"tag\": \"div\",
\"text\": {
\"tag\": \"plain_text\",
\"content\": \"$CONTENT\"
}
},
{
\"tag\": \"hr\"
},
{
\"tag\": \"div\",
\"fields\": [
{
\"is_short\": true,
\"text\": {
\"tag\": \"lark_md\",
\"content\": \"📲 短信自动转发系统\"
}
},
{
\"is_short\": true,
\"text\": {
\"tag\": \"lark_md\",
\"content\": \"$SIM_INFO | $DEVICE_MODEL\"
}
}
]
}
]
}
}"
# 发送到飞书
RESPONSE=$(curl -s -X POST -H "Content-Type: application/json; charset=utf-8" -d "$JSON" "$FEISHU_WEBHOOK_URL")
if echo "$RESPONSE" | grep -q '"code":0'; then
log "转发短信 #$INDEX (来自 $PHONE)"
NEW_MESSAGES=$((NEW_MESSAGES + 1))
else
log "转发短信 #$INDEX 失败"
fi
fi
fi
done < /tmp/sms_indices_sorted.txt
# 更新上次处理的索引
echo "$HIGHEST_INDEX" > "$LAST_INDEX_FILE"
log "成功转发 $NEW_MESSAGES 条短信"
# 清理临时文件
rm -f /tmp/sms_indices.txt /tmp/sms_indices_sorted.txt
fi
else
log "获取短信列表失败,会话可能已过期"
# 尝试重新登录获取新的会话凭证
get_session_cookie
# 如果登录失败,等待一段时间再重试
if [ $? -ne 0 ]; then
log "登录失败,将在5分钟后重试"
sleep 300
fi
fi
# 等待下一次检查
sleep "$CHECK_INTERVAL"
done
2. 自启动脚本 (custom_init.sh)
#!/bin/sh
# 这个脚本将在系统启动时执行
# 启动短信转发服务
/home/scripts/sms_autostart.sh &
3. 服务自启动脚本 (sms_autostart.sh)
#!/bin/sh
# 描述: 短信转发服务自启动脚本
# 等待系统完全启动(30秒)
sleep 30
# 检查服务是否已经在运行
if ! pgrep -f sms_forward.sh > /dev/null; then
# 启动短信转发服务
nohup /home/scripts/sms_forward.sh > /dev/null 2>&1 &
echo "短信转发服务已启动"
fi
配置系统自启动
在飞猫M20上配置服务自启动有三种方案,推荐使用标准的init.d脚本方式:
方案一:标准Init.d脚本(推荐)
# 创建新的启动脚本
cat > /etc/init.d/sms_forward << 'EOF'
#!/bin/sh
### BEGIN INIT INFO
# Provides: sms_forward
# Required-Start: $network
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: SMS Forwarding Service
### END INIT INFO
case "$1" in
start)
echo "Starting SMS forwarding service"
/home/scripts/custom_init.sh &
;;
stop)
echo "Stopping SMS forwarding service"
killall sms_forward.sh
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0
EOF
# 设置执行权限
chmod +x /etc/init.d/sms_forward
# 创建启动链接
ln -sf /etc/init.d/sms_forward /etc/rc2.d/S99sms_forward
ln -sf /etc/init.d/sms_forward /etc/rc3.d/S99sms_forward
ln -sf /etc/init.d/sms_forward /etc/rc5.d/S99sms_forward
优势:
- 提供完整的服务管理功能(启动、停止、重启)
- 可靠的启动顺序控制
- 适应多种系统运行状态
方案二:修改系统启动脚本
如果您需要更简便的方法,可以修改现有启动脚本:
# 方法1: 修改rcS脚本
if [ -f /etc/init.d/rcS ]; then
echo "/home/scripts/custom_init.sh &" >> /etc/init.d/rcS
fi
# 方法2: 使用rc.local
if [ -f /etc/rc.local ]; then
sed -i '/exit 0/i \/home\/scripts\/custom_init.sh \&' /etc/rc.local
fi
优势:操作简单,无需创建额外文件
使用注意事项
1. 参数配置说明
使用前必须正确配置以下关键参数:
# 设备API地址
SMS_API_URL="http://192.168.88.1/action/sms_get_sms_list"
LOGIN_URL="http://192.168.88.1/goform/login"
SYNC_URL="http://192.168.88.1/goform/sync"
# 飞书配置
FEISHU_WEBHOOK_URL="飞书webhook地址" # 需要在飞书群组中获取
# 设备信息
DEVICE_MODEL="设备名称" # 如:飞猫M20-客厅
SIM_INFO="手机号" # 当前设备使用的SIM卡号码
# 检查间隔
CHECK_INTERVAL=15 # 建议15-30秒
2. 登录认证参数获取
脚本中使用的登录认证参数需要通过抓包获取:
USERNAME_STATIC="4cc68e3626e5b94602c325f7c4ca5dee"
PASSWORD_STATIC="95bf0786a20922aec868627af48aa872"
REMEMBER_STATIC="d2e6057958b411672c3028b1976a41e1"
获取方法:
- 打开浏览器开发者工具(F12)
- 登录飞猫M20管理界面(http://192.168.88.1)
- 在Network面板中找到login请求
- 查看请求数据中的加密值
- 替换脚本中对应的值
注意:这些是MD5加密后的值,不是明文账号密码。
3. 飞书Webhook配置
- 在飞书群组中添加"群机器人"
- 选择"自定义机器人"
- 获取Webhook地址
- 建议设置关键词过滤,如"短信通知"
脚本工作原理解析
核心功能流程
- 会话管理:使用Cookie机制实现自动登录
- 数据索引跟踪:记录已处理的最新短信索引,避免重复处理
- 数据解析:使用sed/grep等命令解析JSON数据
- 消息格式化:生成美观的飞书卡片消息
- 异常处理:会话过期自动重连,失败重试机制
技术难点突破
- BusyBox限制突破:
- 在没有Python等高级语言的环境下,使用纯Shell实现复杂功能
- 利用文件系统实现数据处理,避免内存限制
- JSON解析:
- 通过sed分行和grep匹配实现简单的JSON解析
- 使用临时文件处理复杂数据结构
- 飞书卡片构建:
- 设计美观的卡片布局,包含发件人、时间、内容等信息
- 添加表情符号提升可读性
排错指南
常见问题及解决方法
- 服务无法启动
- 检查脚本权限:
chmod +x /home/scripts/*.sh
- 查看日志:
cat /tmp/sms_forward.log
- 检查脚本权限:
- 登录失败
- 验证加密参数是否正确
- 确认设备IP是否为192.168.88.1
- 飞书消息发送失败
- 验证Webhook地址
- 检查网络连接
- 确认飞书机器人状态
- 自启动失败
- 检查init.d脚本权限
- 验证启动链接是否正确创建
- 查看系统日志
性能优化建议
- 检查间隔调整:根据实际需求调整CHECK_INTERVAL值,平衡实时性和系统负载
- 日志管理:定期清理日志文件,避免占用过多存储空间
- 内存优化:使用临时文件处理数据,定期清理
安全建议
- 修改默认密码:更改设备管理界面和WiFi密码
- 限制访问:设置MAC地址过滤,保护设备安全
- 数据保护:定期清理设备上的短信记录
总结
通过本文提供的脚本和配置,您可以将飞猫M20随身WiFi设备转变为一个功能强大的短信监控和转发工具。无论是接收验证码、重要通知,还是监控特定号码的短信,这套方案都能满足您的需求。脚本设计考虑了系统资源限制、稳定性和易用性,特别适合需要在移动场景下实时获取短信信息的用户。
希望本文对您有所帮助,如有任何问题,欢迎在评论区留言讨论。
评论