This commit is contained in:
commit
89b7caaacc
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PB_APIKEY=xxxxxxxxx
|
||||||
|
PIN=0000
|
||||||
|
GW_API=192.168.1.1
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
launch.sh
|
||||||
|
app/__pycache__
|
||||||
|
data/*
|
||||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM "python:3.7-alpine"
|
||||||
|
LABEL maintainer="Cabillot Julien <dockerimages@cabillot.eu>"
|
||||||
|
|
||||||
|
COPY app /app
|
||||||
|
|
||||||
|
WORKDIR "/app"
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
USER "nobody"
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/app/entrypoint" ]
|
||||||
38
Jenkinsfile
vendored
Normal file
38
Jenkinsfile
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
pipeline {
|
||||||
|
environment {
|
||||||
|
registry = 'https://registry.hub.docker.com'
|
||||||
|
registryCredential = 'dockerhub_jcabillot'
|
||||||
|
dockerImage = 'jcabillot/huawei-3g-sms-api'
|
||||||
|
}
|
||||||
|
|
||||||
|
agent any
|
||||||
|
|
||||||
|
triggers {
|
||||||
|
cron('@midnight')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Clone repository') {
|
||||||
|
steps{
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build image') {
|
||||||
|
steps{
|
||||||
|
sh 'docker build -t ${dockerImage} .'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy Image') {
|
||||||
|
steps{
|
||||||
|
script {
|
||||||
|
withCredentials([usernamePassword(credentialsId: 'dockerhub_jcabillot', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
|
||||||
|
sh 'docker login --username ${DOCKER_USER} --password ${DOCKER_PASS}'
|
||||||
|
sh 'docker push ${dockerImage}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/README.md
Normal file
11
app/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# huawei-3G-SMS-API
|
||||||
|
|
||||||
|
Program to check for SMS messages, archive and send via email
|
||||||
|
|
||||||
|
In future it will also include sending SMS
|
||||||
|
|
||||||
|
|
||||||
|
Thanks to:
|
||||||
|
https://github.com/juslop/3G-tunnel/blob/master/pi_sms.py
|
||||||
|
https://github.com/trick77/huawei-hilink-status/blob/master/hstatus.py
|
||||||
|
https://trick77.com/query-status-information-huaweis-hilink-3g-lte-modems/
|
||||||
21
app/entrypoint.sh
Normal file
21
app/entrypoint.sh
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [[ -z "${PB_APIKEY}" ]]
|
||||||
|
then
|
||||||
|
echo 'Please define the env var PB_APIKEY, exit' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${PIN}" ]]
|
||||||
|
then
|
||||||
|
echo 'Please define the env var PIN, exit' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${GW_API}" ]]
|
||||||
|
then
|
||||||
|
echo 'Please define the env var GW_API, exit' >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 "/app/sms.py"
|
||||||
2
app/requirements.txt
Normal file
2
app/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pushbullet.py==0.11.0
|
||||||
|
xmltodict==0.12.0
|
||||||
14
app/sendEmail.py
Normal file
14
app/sendEmail.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import smtplib
|
||||||
|
|
||||||
|
def sendmail(subject, message, to):
|
||||||
|
fromaddr = 'EMAIL-ADDRESS'
|
||||||
|
toaddrs = to
|
||||||
|
msg = "Subject: %s\n\n%s" % (subject, message)
|
||||||
|
username = 'EMAIL-ADDRESS'
|
||||||
|
password = 'PASSWORD'
|
||||||
|
server = smtplib.SMTP('smtp.gmail.com:587') #default server for gmail, change if not using gmail
|
||||||
|
server.ehlo()
|
||||||
|
server.starttls()
|
||||||
|
server.login(username,password)
|
||||||
|
server.sendmail(fromaddr, toaddrs, msg)
|
||||||
|
server.quit()
|
||||||
180
app/sms.py
Normal file
180
app/sms.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
import xmltodict
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from pushbullet import Pushbullet
|
||||||
|
|
||||||
|
pb = Pushbullet(os.environ['PB_APIKEY'])
|
||||||
|
|
||||||
|
pin = os.environ['PIN']
|
||||||
|
|
||||||
|
PIN_ENTER_TEMPLATE = '''<request>
|
||||||
|
<OperateType>0</OperateType>
|
||||||
|
<CurrentPin>''' + PIN + '''</CurrentPin>
|
||||||
|
<NewPin></NewPin>
|
||||||
|
<PukCode></PukCode>
|
||||||
|
</request>'''
|
||||||
|
|
||||||
|
SMS_LIST_TEMPLATE = '''<request>
|
||||||
|
<PageIndex>1</PageIndex>
|
||||||
|
<ReadCount>20</ReadCount>
|
||||||
|
<BoxType>1</BoxType>
|
||||||
|
<SortType>0</SortType>
|
||||||
|
<Ascending>0</Ascending>
|
||||||
|
<UnreadPreferred>0</UnreadPreferred>
|
||||||
|
</request>'''
|
||||||
|
|
||||||
|
SMS_DEL_TEMPLATE = '<request><Index>{index}</Index></request>'
|
||||||
|
|
||||||
|
"""SMS_SEND_TEMPLATE = '''<request>
|
||||||
|
<Index>-1</Index>
|
||||||
|
<Phones><Phone>{phone}</Phone></Phones>
|
||||||
|
<Sca></Sca>
|
||||||
|
<Content>{content}</Content>
|
||||||
|
<Length>{length}</Length>
|
||||||
|
<Reserved>1</Reserved>
|
||||||
|
<Date>{timestamp}</Date>
|
||||||
|
</request>'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
def isHilink(device_ip):
|
||||||
|
try:
|
||||||
|
r = requests.get(url='http://' + device_ip + '/api/device/information', timeout=(2.0,2.0))
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return False;
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getHeaders(device_ip):
|
||||||
|
token = None
|
||||||
|
sessionID = None
|
||||||
|
try:
|
||||||
|
r = requests.get(url='http://' + device_ip + '/api/webserver/SesTokInfo')
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return (token, sessionID)
|
||||||
|
try:
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
if 'response' in d and 'TokInfo' in d['response']:
|
||||||
|
token = d['response']['TokInfo']
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
if 'response' in d and 'SesInfo' in d['response']:
|
||||||
|
sessionID = d['response']['SesInfo']
|
||||||
|
headers = {'__RequestVerificationToken': token, 'Cookie': sessionID}
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def getSMS(device_ip, headers):
|
||||||
|
r = requests.post(url = 'http://' + device_ip + '/api/sms/sms-list', data = SMS_LIST_TEMPLATE, headers = headers)
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
numMessages = int(d['response']['Count'])
|
||||||
|
messagesR = d['response']['Messages']['Message']
|
||||||
|
if numMessages == 1:
|
||||||
|
temp = messagesR
|
||||||
|
messagesR = [temp]
|
||||||
|
messages = getContent(messagesR, numMessages)
|
||||||
|
return messages, messagesR
|
||||||
|
|
||||||
|
def statusPin(device_ip, headers):
|
||||||
|
r = requests.get(url = 'http://' + device_ip + '/api/pin/status', headers = headers)
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
state = int(d['response']['SimState'])
|
||||||
|
return state
|
||||||
|
|
||||||
|
def enterPin(pin, device_ip, headers):
|
||||||
|
r = requests.post(url = 'http://' + device_ip + '/api/pin/operate', data = PIN_ENTER_TEMPLATE, headers = headers)
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
print(d['response'])
|
||||||
|
|
||||||
|
def getContent(data, numMessages):
|
||||||
|
messages = []
|
||||||
|
for i in range(numMessages):
|
||||||
|
message = data[i]
|
||||||
|
number = message['Phone']
|
||||||
|
content = message['Content']
|
||||||
|
date = message['Date']
|
||||||
|
messages.append('Message from ' + number + ' recieved ' + date + ' : ' + str(content))
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def delMessage(device_ip, headers, ind):
|
||||||
|
r = requests.post(url = 'http://' + device_ip + '/api/sms/delete-sms', data = SMS_DEL_TEMPLATE.format(index=ind), headers = headers)
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
print(d['response'])
|
||||||
|
|
||||||
|
def getUnread(device_ip, headers):
|
||||||
|
r = requests.get(url = 'http://' + device_ip + '/api/monitoring/check-notifications', headers = headers)
|
||||||
|
d = xmltodict.parse(r.text, xml_attribs=True)
|
||||||
|
unread = int(d['response']['UnreadMessage'])
|
||||||
|
return unread
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
while True:
|
||||||
|
device_ip = os.environ['GW_IP']
|
||||||
|
if not isHilink(device_ip):
|
||||||
|
print("Can't find a Huawei HiLink device on " + device_ip + ", exit")
|
||||||
|
print('')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
headers = getHeaders(device_ip)
|
||||||
|
# Require to enter the PIN to unlock device
|
||||||
|
if statusPin(device_ip, headers) == 260:
|
||||||
|
print('# Configure PIN')
|
||||||
|
enterPin(os.environ['PIN'], device_ip, headers)
|
||||||
|
|
||||||
|
unread = getUnread(device_ip, headers)
|
||||||
|
print('# Unread message(s) : %d' % unread)
|
||||||
|
if unread != 0:
|
||||||
|
messages, messagesR = getSMS(device_ip, headers)
|
||||||
|
|
||||||
|
#Read
|
||||||
|
if not os.path.exists(os.path.join(__location__,'data',)):
|
||||||
|
os.makedirs(os.path.join(__location__,'data'))
|
||||||
|
try:
|
||||||
|
f1 = open(os.path.join(__location__,'data',"sms.json"), 'r')
|
||||||
|
data = json.load(f1)
|
||||||
|
f1.close()
|
||||||
|
except:
|
||||||
|
data = []
|
||||||
|
|
||||||
|
f3 = open(os.path.join(__location__,'data',"sms.txt"), 'a')
|
||||||
|
|
||||||
|
# Ok, fonctionnel
|
||||||
|
for i in range(len(messages)):
|
||||||
|
# Json
|
||||||
|
print('# Log JSON')
|
||||||
|
data.append(messagesR[i])
|
||||||
|
|
||||||
|
# Text
|
||||||
|
f3.write(messages[i] + '\n')
|
||||||
|
# Pushbullet
|
||||||
|
print('# Notif pushbullet')
|
||||||
|
pb.push_note("SMS", "From: %s\nDate: %s\n%s" % (messagesR[i]['Phone'], messagesR[i]['Date'], messagesR[i]['Content']))
|
||||||
|
|
||||||
|
# Log HTTP
|
||||||
|
print('# Notif HTTP')
|
||||||
|
datam = {'sender' : messagesR[i]['Phone'], 'date': messagesR[i]['Date'], 'content': messagesR[i]['Content']}
|
||||||
|
r = requests.post(url="https://sms.cabillot.eu", headers={'Content-Type': 'application/json' }, json=datam)
|
||||||
|
#print(r.status_code)
|
||||||
|
|
||||||
|
#Save
|
||||||
|
f2 = open(os.path.join(__location__,'data',"sms.json"), 'w')
|
||||||
|
json.dump(data, f2)
|
||||||
|
f2.close()
|
||||||
|
|
||||||
|
f3.close()
|
||||||
|
|
||||||
|
#delete from device
|
||||||
|
for i in range(len(messagesR)):
|
||||||
|
headers = getHeaders(device_ip)
|
||||||
|
delMessage(device_ip, headers, messagesR[i]['Index'])
|
||||||
|
|
||||||
|
time.sleep(30)
|
||||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
version: '2.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
offlineimap:
|
||||||
|
image: "jcabillot/huawei-3g-sms-api"
|
||||||
|
container_name: "huawei-3g-sms-api"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
mem_limit: "256m"
|
||||||
|
cpus: 0.3
|
||||||
|
cpu_shares: 1024
|
||||||
|
pids_limit: 200
|
||||||
|
volumes:
|
||||||
|
# To store plain text & json logs
|
||||||
|
- "./data:/app/data"
|
||||||
|
environment:
|
||||||
|
- "PB_APIKEY"
|
||||||
|
- "PIN"
|
||||||
|
- "GW_API"
|
||||||
|
security_opt:
|
||||||
|
- "no-new-privileges"
|
||||||
Loading…
x
Reference in New Issue
Block a user