Vigorous Pro

The world can always use more heroes.

  1. 1. 使用的设备
  2. 2. 使用的程序
  3. 3. 使用的接口
  4. 4. 准备过程
    1. 4.1. 安装与配置 Gammu
    2. 4.2. 安装 NodeJS
    3. 4.3. 安装与配置 Gammu-smsd
  5. 5. 原本的执行方式(过时)
    1. 5.1. 使用的程序
    2. 5.2. 安装必要模块
    3. 5.3. 修改 Python package
    4. 5.4. Python 文件部分
  6. 6. 参考链接

尽管可以利用闲置安卓手机加 IFTTT 实现接收短信并转发至 Telegram 的操作,但是还是没有自己制作的更让自己满意,毕竟自己的一些奇思妙想是其无法直接实现的。

使用的设备

  • Raspberry 3B+
  • Air720H (理论上同样支持其他模块)

树莓派是白嫖 Peter 的 ヽ(‘ ∇‘ )ノ

使用的程序

  • NodeJS
  • Yarn
  • Gammu

树莓派的系统版本为 Raspbian

e615ac425de6726037549bd232218ec8.png

使用的接口

  • Telegram Bot
  • 腾讯云短信接口 (可移除,或替换为其他平台)

准备过程

首先将模块与树莓派连接好之后,lsusb 就会看到相应的设备了因为我是用 USB2TTL 来进行连接,所以此处看到的设备为

1
Bus 001 Device 038: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter

接下来,在 /dev 目录下找到对应的 ttyUSB

1
ls /dev/ttyUSB*

因为我只插了这一个 USB 设备,所以很容易确定对应的地址为 /dev/ttyUSB0

从此处开始,以 root 身份进行操作

安装与配置 Gammu

从此处开始,以 root 身份进行操作

接下来,开始安装 Gammu

1
apt install gammu

使用 gammu-config 来进行配置

根据设备的不同配置可能会略有不同

Port 修改为 /dev/ttyUSB0
Connection 修改为 at115200

398c9e9887a39cef6f690c0063d326fa.png

现在,我们来验证配置是否成功

1
2
3
4
5
6
7
8
gammu --identify

Device : /dev/ttyUSB0
Manufacturer : "AirM2M"
Model : unknown (Air720H)
Firmware : "AirM2M_720H_V1381_LTE_AT"
IMEI : ×××××××××××××××
SIM IMSI : ×××××××××××××××

接下来,测试发送短信

1
2
3
4
echo "test" | gammu sendsms TEXT 手机号

If you want break, press Ctrl+C...
Sending SMS 1/1....waiting for network answer..OK, message reference=44

如果没问题的话,你应该会顺利收到自己发出的短信了

安装 NodeJS

1
2
3
4
5
6
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
apt install gcc g++ make
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | apt add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
apt update
apt install yarn nodejs

安装与配置 Gammu-smsd

安装 gammu-smsd

1
apt-get install gammu-smsd -y

启动并设置开机启动

1
2
systemctl start gammu-smsd
systemctl enable gammu-smsd

编辑 /etc/gammu-smsdrc 文件

其中部分内容需要根据个人情况进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[gammu]

port = /dev/ttyUSB0
connection = at115200
# Debugging
#logformat = textall

# SMSD configuration, see gammu-smsdrc(5)
[smsd]
service = files
logfile = syslog
# Increase for debugging information
debuglevel = 0
RunOnReceive=/root/gammu/index.js
hangupcalls = 1
RunOnIncomingCall=/root/gammu/call.js


# Paths where messages are stored
inboxpath = /var/spool/gammu/inbox/
outboxpath = /var/spool/gammu/outbox/
sentsmspath = /var/spool/gammu/sent/
errorsmspath = /var/spool/gammu/error/

接下来,在 /root/gammu 下创建 package.json

1
2
3
4
5
6
7
8
9
10
11
{
"name": "gammu-telegram",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"axios": "^0.18.0",
"qcloudsms_js": "^0.1.1",
"socks-proxy-agent": "^4.0.1"
}
}

创建并编辑 index.js 文件

其中部分内容需要根据个人情况进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/node
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const SocksProxyAgent = require('socks-proxy-agent')
const botToken = "*****[REPLACE×THIS×PART]*****"
const chatId = *****[REPLACE×THIS×PART]*****
const proxyOptions = 'socks5://127.0.0.1:1080'
const httpsAgent = new SocksProxyAgent(proxyOptions)
const client = axios.create({ baseURL: `https://api.telegram.org/bot${botToken}`, httpsAgent})
const { SMS_1_NUMBER, SMS_1_TEXT, DECODED_0_TEXT } = process.env

let smsData

if (DECODED_0_TEXT) {
smsData = DECODED_0_TEXT
} else {
smsData = SMS_1_TEXT
}

async function main() {
await client.post('/sendMessage', {
chat_id: chatId,
text: "Phone Number: `" + SMS_1_NUMBER + "`\n" + "Tag: #sms\nData: `" + smsData + "`",
parse_mode: 'Markdown'
})
}

main()
.catch(err => console.log(err))

创建并编辑 call.js 文件

其中部分内容需要根据个人情况进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/node

var QcloudSms = require("qcloudsms_js");
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const SocksProxyAgent = require('socks-proxy-agent')
const botToken = "*****[REPLACE×THIS×PART]*****"
const chatId = *****[REPLACE×THIS×PART]*****
const proxyOptions = 'socks5://127.0.0.1:1080'
const httpsAgent = new SocksProxyAgent(proxyOptions)
const client = axios.create({ baseURL: `https://api.telegram.org/bot${botToken}`, httpsAgent})
const INCOMING_NUMBER = process.argv.slice()[2];

var appid = *****[REPLACE×THIS×PART]*****;
var appkey = "*****[REPLACE×THIS×PART]*****";
var templateId = *****[REPLACE×THIS×PART]*****;
var smsSign = "*****[REPLACE×THIS×PART]*****";
var qcloudsms = QcloudSms(appid, appkey);
var ssender = qcloudsms.SmsSingleSender();
var params = [];
async function main() {
await client.post('/sendMessage', {
chat_id: chatId,
text: "Phone Number: `" + INCOMING_NUMBER + "`\n" + "Tag: #call",
parse_mode: 'Markdown'
})
ssender.sendWithParam("86", INCOMING_NUMBER, templateId,
params, smsSign, "", "", callback);
function callback(err, res, resData) {
if (err) {
client.post('/sendMessage', {
chat_id: chatId,
text: "Err: `" + err + "`\n" + "Tag: #error",
parse_mode: 'Markdown'
})
}
}
}

main()
.catch(err => console.log(err))

接下来,安装 dependence

1
2
3
4
yarn install

chmod +x index.js
chmod +x call.js

然后收到短信时,自动将短信转发至 Telegram。来电时,挂断电话,将来电人信息转发至 Telegram,并通过短信接口给来电者返回短信通知。

原本的执行方式(过时)

因为 Python 2 已经于 2020年1月1日寿命已经终结,所以此部分理论上已经过时

感谢 @Peter 提供技术支持

使用的程序

  • Python 2
  • python-pip

安装必要模块

1
2
pip install python-gsmmodem-new
pip install qcloudsms_py

修改 Python package

因为这个包已经很久不更新,而且在 Python 3 下运行存在问题,所以我们需要做一些修改

/usr/local/lib/python2.7/dist-packages/gsmmodem

修改 modem.py 文件的以下部分,当然我也在下方提供了修改后的版本,替换即可

57 行

1
2
3
4
5
6
def __init__(self, gsmModem, status, number, time, text, smsc=None, udh=[]):
super(ReceivedSms, self).__init__(number, text, smsc)
self._gsmModem = weakref.proxy(gsmModem)
self.status = status
self.time = time
self.udh = udh

132 行

1
CLIP_REGEX = re.compile('^\+CLIP:\s*\+{0,1}(\d+),\s*(\d+).*$')

1117 行

1
messages.append(ReceivedSms(self, Sms.TEXT_MODE_STATUS_MAP[msgStatus], number, parseTextModeTimeStr(msgTime), msgText))

1127 行

1
messages.append(ReceivedSms(self, Sms.TEXT_MODE_STATUS_MAP[msgStatus], number, parseTextModeTimeStr(msgTime), msgText))

1151 行

1
sms = ReceivedSms(self, int(msgStat), smsDict['number'], smsDict['time'], smsDict['text'], smsDict['smsc'], smsDict.get('udh', []))

1245 行

1
lines.pop(0) # Workaround for failing to get phone number

1266 行

1
callerNumber = clipMatch.group(1)

1384 行

1
self.smsStatusReportCallback(report)

1403 行

1
2
3
else:
# Nothing is waiting for this report directly - use callback
self.smsStatusReportCallback(report)

修改后的文件 下载链接

Python 文件部分

将下方内容写入 sms.py

收到短信时,自动将短信转发至 Telegram。来电时,挂断电话,将来电人信息转发至 Telegram,并通过短信接口给来电者返回短信通知。

此处使用的短信接口为腾讯云,可替换为其他提供商。

请自行替换其中的数据部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from gsmmodem.exceptions import InterruptedException 
import logging
import telegram
import time
from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError


bot = telegram.Bot(token='*****[REPLACE×THIS×PART]*****', base_url='https://api.telegram.org/bot')
appid = *****[REPLACE×THIS×PART]*****
appkey = "*****[REPLACE×THIS×PART]*****"
phone_number = 0


PORT = '/dev/ttyUSB0'
BAUDRATE = 115200
PIN = None # SIM card PIN (if any)

from gsmmodem.modem import GsmModem

def handleSms(sms):
logging.info(b'== SMS message received ==\nFrom: {0}\nTime: {1}\nMessage:\n{2}\n'.format(sms.number, sms.time, sms.text.encode('utf-8')))
bot.send_message("*****[REPLACE×THIS×PART]*****", b'{0}\n\nFrom {1}\n'.format(sms.text.encode('utf-8'), sms.number, sms.time))

def handleIncomingCall(call):
logging.info(b'Incoming call from {0}'.format(call.number))
call.hangup()
bot.send_message("*****[REPLACE×THIS×PART]*****", b'Incoming call from {0}'.format(call.number))
sms_sign = "*****[REPLACE×THIS×PART]*****"
template_id = *****[REPLACE×THIS×PART]*****
phone_number = call.number
sms_type = 0
ssender = SmsSingleSender(appid, appkey)
params = []
try:
result = ssender.send_with_param(86, phone_number,
template_id, params, sign=sms_sign, extend="", ext="")
except HTTPError as e:
print(e)
bot.send_message("*****[REPLACE×THIS×PART]*****", b'Error {0}'.format(e))
except Exception as e:
bot.send_message("*****[REPLACE×THIS×PART]*****", b'Error {0}'.format(e))
print(result)


def main():
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
logging.info('Initializing modem...')
# Uncomment the following line to see what the modem is doing:
modem = GsmModem(PORT, BAUDRATE, smsReceivedCallbackFunc=handleSms, incomingCallCallbackFunc=handleIncomingCall)
modem.smsTextMode = False
modem.connect(PIN)
allSms = modem.listStoredSms()
for sms in allSms:
handleSms(sms)
modem.deleteMultipleStoredSms()

logging.info('Waiting for SMS message...')
try:
modem.rxThread.join(2**31) # Specify a (huge) timeout so that it essentially blocks indefinitely, but still receives CTRL+C interrupt signal
finally:
modem.close()

if __name__ == '__main__':
main()

接下来,运行 sms.py 即可

参考链接

本文作者 : Edison Jwa
本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
本文链接 : https://www.wevg.org/archives/sms2tg/

本文最后更新于 天前,文中所描述的信息可能已发生改变