Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

9 changed files with 75 additions and 160 deletions

View File

@ -1,7 +0,0 @@
FRITZ_USERNAME="fritzab2matrix"
FRITZ_PASSWORD="S0meSecretPa5sw02d"
FRITZ_IP="192.168.178.1"
FRITZ_TMP="/tmp"
FRITZ_VOICEBOX_PATH="fritz.nas/FRITZ/voicebox"
FRITZ_TAM='{"0" : "!roomhash1:matrix.org", "1" : "!roomhash2:matrix.org"}'
FRITZ_CALL_WATCH=False

View File

@ -3,9 +3,7 @@
__FritzAB2Matrix__ reads out the answering machine (_TAM_) of a _Fritz!Box_ in your LAN and posts the messages into a private chat in the __matrix__ network. While you could let your _Fritz!Box_ send the messages by mail - unencrypted of course - the matrix chat is __e2e encrypted__. Which kindly acknowledges the privacy of any caller that leaves a message for you.
Uses the python based cmd-line-tool [matrix-commander](https://github.com/8go/matrix-commander) so the matrix-commander.py in this repo is just a copy of that file to ease testing.
## Features
* Since _v0.1.1_ __Multitam__ is integrated so that you can check multiple answering machines __and__ post the messages to different matrix rooms. (Use _FRITZ\_TAM_ variable in .env file)
* Set _FRITZ\_CALL\_WATCH_=True if you want to receive a message everytime you miss a call.
## Installation
If you like to test this repository you are recommended to use one of the following two options.
### Necessary preparations for both cases
@ -25,13 +23,11 @@ If you like to test this repository you are recommended to use one of the follow
* Inside the repo run `pip install --upgrade pip && pip install -r requirements.txt`
* Create an `.env` file with your favourite editor:
```
FRITZ_USERNAME="fritzab2matrix"
FRITZ_PASSWORD="S0meSecretPa5sw02d"
FRITZ_USERNAME="fritzab"
FRITZ_PASSWORD="SomeRand0mPa55word"
FRITZ_IP="192.168.178.1"
FRITZ_VOICEBOX_PATH="fritz.nas/FRITZ/voicebox"
FRITZ_TMP="/tmp"
# FRITZ_VOICEBOX_PATH="fritz.nas/FRITZ/voicebox"
FRITZ_TAM='{"0" : "!roomhash1:matrix.org", "1" : "!roomhash2:matrix.org"}'
FRITZ_CALL_WATCH=False
```
__.env__
@ -58,8 +54,6 @@ Provided you have docker and docker-compose installed on your system:
* Follow the appearing dialog and input your matrix account data.
* After all that the running docker container should watch your box and your TAM messages should be posted in the chosen matrix chat.
### Special Thx
Gratitude to all people that enabled that project by their passionate work and will to share it!
Especially to

View File

@ -1,9 +1,8 @@
# syntax=docker/dockerfile:1
FROM python:alpine
FROM ubuntu:latest
ENV TZ=Europe/Berlin
ENV UNBUFFERED=1
WORKDIR /app
VOLUME /app
@ -11,20 +10,23 @@ VOLUME /app
COPY . .
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone; \
apk --no-cache update && apk add --no-cache --virtual .build-deps \
gcc \
cargo \
libffi-dev \
openssl-dev \
olm-dev \
zlib-dev \
jpeg-dev ; \
apk add --no-cache olm libmagic ffmpeg speex; \
pip install pip -U --no-cache-dir; \
pip install --no-cache-dir -r requirements.txt ; \
apk del --no-cache .build-deps;
/bin/bash -c 'apt update && apt install -y libolm-dev python3-pip ffmpeg htop; \
pip install update pip && pip install -r requirements.txt ;'
ENTRYPOINT ["./fritzab2matrix.py "]
# ENTRYPOINT ["python3", "-u", "fritzab2matrix.py"]
ENTRYPOINT /bin/sh -c "export UNBUFFERED=1 && python3 -u ./fritzab2matrix.py"

View File

@ -7,7 +7,7 @@ services:
context: ../.
dockerfile: ./docker/Dockerfile
working_dir: /app
# entrypoint: /bin/sh -c "export UNBUFFERED=1 && python3 -u ./fritzab2matrix.py"
entrypoint: ./fritzab2matrix.py
volumes:
- ../.:/app

View File

@ -1,14 +1,13 @@
#!/usr/bin/env python3
from fritzconnection import FritzConnection
from fritzconnection.lib.fritzcall import FritzCall, Call
from dotenv import load_dotenv
from pydub import AudioSegment
from libs.monitoring import endedCall
from libs.message import conversion as conv
import urllib.request
import xmltodict, json
import sys, os, time
import xmltodict
import sys, os
import smbclient
@ -19,98 +18,51 @@ env_user = os.environ.get('FRITZ_USERNAME')
env_pass = os.environ.get('FRITZ_PASSWORD')
env_ip = os.environ.get('FRITZ_IP')
env_voicebox = os.environ.get('FRITZ_VOICEBOX_PATH')
env_tam = json.loads(os.environ.get('FRITZ_TAM'))
env_call_watch = eval(os.environ.get('FRITZ_CALL_WATCH'))
env_tmp = os.environ.get('TEMP_DIR')
if env_call_watch is None:
env_call_watch = False
elif env_call_watch:
at_least_one_new_message = False
if env_voicebox is None:
env_voicebox = "/fritz.nas/FRITZ/voicebox/"
if env_tam is None:
env_tam = {
"0" : "!MxRrNGhFuQwnIeEWnX:ismus.net"
}
#print(env_tam)
if env_tmp is None:
env_tmp = "/tmp"
def fritzab2matrix():
# Build the url to download the message via smb
def build_download_url(mid, tam=0):
recording = "rec." + str(tam) + r"." + str(mid).zfill(3)
url = os.path.join("//",env_ip,env_voicebox,"rec",recording)
return url
def download_speex_file(smb_url):
smbclient.register_session(server=env_ip, username=env_user, password=env_pass, auth_protocol="ntlm")
fd = smbclient.open_file(smb_url, mode="rb")
return fd
def get_message_list(url):
""" Get and and convert the xml formatted list of messages into a dictionary. """
with urllib.request.urlopen(url) as f:
doc = f.read()
# Convert the xml formatted message list to dict
messages = xmltodict.parse(doc)
return messages
def get_last_call():
""" Get the last Call. """
try:
fc = FritzCall(address=env_ip,password=env_pass)
except:
print("Couldn't connect to Box")
missed_calls = fc.get_missed_calls(False,1,1)
if len(missed_calls) > 0:
return missed_calls[0]
else:
return False
def lastcall2matrix(tam,tam_no):
# Call Watch to Matrix
if env_call_watch and not at_least_one_new_message:
c = get_last_call()
if c:
c_msg = "{} - {} ({})".format(c.Date, c.Caller, c.Name)
else:
return False
# ... and send message and file to Matrix Room
## if Number of a TAM and the last call match
if tam_no == c.CalledNumber:
cmd = "python3 matrix-commander.py --room {} -m '{}'".format(env_tam[tam],c_msg)
os.system(cmd)
else:
print("Call Watch is off.")
def fritzab2matrix(tam):
### CHECK AND GET MESSAGES FROM FRITZBOX ###
############################################
## Connect to the FritzBox in the LAN
# We don't use tls because the self-signed cert of the box leads to a malfunction in urllib later on.
fc = FritzConnection(address=env_ip, user=env_user, password=env_pass, use_tls=False)
at_least_one_new_message = False
## Get info about messages from the main answering machine
message_list = fc.call_action("X_AVM-DE_TAM1", "GetMessageList", NewIndex=tam)
message_list = fc.call_action("X_AVM-DE_TAM1", "GetMessageList", NewIndex=0)
message_list_url = message_list['NewURL']
# Build the url to download the message via smb
def build_download_url(mid, tam=0):
recording = "rec." + str(tam) + r"." + str(mid).zfill(3)
url = os.path.join("//",env_ip,env_voicebox,"rec",recording)
return url
def download_speex_file(smb_url):
smbclient.register_session(server=env_ip, username=env_user, password=env_pass, auth_protocol="ntlm")
fd = smbclient.open_file(smb_url, mode="rb")
return fd
def get_message_list(url):
""" Get and and convert the xml formatted list of messages into a dictionary. """
with urllib.request.urlopen(url) as f:
doc = f.read()
# Convert the xml formatted message list to dict
messages = xmltodict.parse(doc)
return messages
l = get_message_list(message_list_url)
if l['Root'] == None or l['Root']['Message'] == None:
return False
@ -124,7 +76,7 @@ def fritzab2matrix(tam):
for a in messages:
# format the information regarding the message
msg_info = a['Date'] + " - " + str(a['Number'])
msg_info = a['Date'] + " - " + a['Number']
if a['Name']:
msg_info += " (" + a['Name'] + ") "
@ -135,21 +87,21 @@ def fritzab2matrix(tam):
message_new = bool(int(a['New']))
if message_new == True:
at_least_one_new_message = True
# Download and convert the speex files to wav
smb_url = build_download_url(a['Index'])
speex_fd = download_speex_file(smb_url)
conv.speex_convert(speex_fd, os.path.join(env_tmp,"message{}.wav".format(tam)))
conv.speex_convert(speex_fd, os.path.join(env_tmp,"message.wav"))
# Convert wav to ogg
msg = AudioSegment.from_wav(os.path.join(env_tmp,"message{}.wav".format(tam)))
msg = AudioSegment.from_wav(os.path.join(env_tmp,"message.wav"))
# Only if message is longer than 5 seconds ...
if msg.duration_seconds > 5.0:
# ... export to ogg ...
msg.export(os.path.join(env_tmp,"message{}.ogg".format(tam)), format="ogg", tags=msg_tags)
msg.export(os.path.join(env_tmp,"message.ogg"), format="ogg", tags=msg_tags)
# ... and send message and file to Matrix Room
command = "python3 matrix-commander.py --room {} -a ".format(env_tam[tam]) + os.path.join(env_tmp,"message{}.ogg".format(tam)) + " -m '{}'".format(msg_info)
command = "python3 matrix-commander.py -a " + os.path.join(env_tmp,"message.ogg") + " -m '{}'".format(msg_info)
os.system(command)
else:
@ -160,33 +112,28 @@ def fritzab2matrix(tam):
print("** " + msg_info)
# Mark processed messages as 'read'
fc.call_action("X_AVM-DE_TAM1", "MarkMessage", NewIndex=tam, NewMessageIndex=int(a['Index']), NewMarkedAsRead=1)
fc.call_action("X_AVM-DE_TAM1", "MarkMessage", NewIndex=0, NewMessageIndex=int(a['Index']), NewMarkedAsRead=1)
else:
# Show that message is already read
print("__ " + msg_info)
tam_no = a['Called']
lastcall2matrix(tam,tam_no)
# ## For testing purposes only
# if a['Date'].endswith('20:53'):
# fc.call_action("X_AVM-DE_TAM1", "MarkMessage", NewIndex=0, NewMessageIndex=int(a['Index']), NewMarkedAsRead=0)
continue
def multitam(tams):
time.sleep(10)
for tam in tams.keys():
print("Check TAM {}.".format(tam))
fritzab2matrix(tam)
continue
if __name__ == "__main__":
multitam(env_tam)
fritzab2matrix()
### Monitor the FritzBox and trigger the main script whenever a call disconnects ###
###################################################################################
endedCall(multitam,env_tam, env_ip)
endedCall(fritzab2matrix, env_ip)

View File

@ -4,7 +4,7 @@ from fritzconnection.core.fritzmonitor import FritzMonitor
### Monitor the calls of a fritzbox continously ###
###################################################
def watch_disconnect(monitor, event_queue, func, tams, healthcheck_interval=10):
def watch_disconnect(monitor, event_queue, func, healthcheck_interval=10):
while True:
try:
event = event_queue.get(timeout=healthcheck_interval)
@ -15,20 +15,13 @@ def watch_disconnect(monitor, event_queue, func, tams, healthcheck_interval=10):
else:
# do event processing here:
print(event)
if 'DISCONNECT;0' in event:
print("Incoming call stopped. Check the TAM.\n")
func(tams)
elif 'DISCONNECT;1' in event:
print("Outgoing call stopped. Do nothing.\n")
else:
print("Unknown event.\n")
if 'DISCONNECT' in event:
print("Anruf beendet. Jetzt den AB checken.\n")
func()
def endedCall(func, tams, fritz_ip='192.168.1.1'):
def endedCall(func, fritz_ip='192.168.1.1'):
"""
Call this to trigger a given function if a call is disconnected
"""
@ -36,7 +29,7 @@ def endedCall(func, tams, fritz_ip='192.168.1.1'):
# as a context manager FritzMonitor will shut down the monitor thread
with FritzMonitor(address=fritz_ip) as monitor:
event_queue = monitor.start()
watch_disconnect(monitor, event_queue, func, tams)
watch_disconnect(monitor, event_queue, func)
except (OSError, KeyboardInterrupt) as err:
print(err)

View File

@ -1,4 +1,3 @@
fritzconnection
python-dotenv
xmltodict
@ -8,16 +7,13 @@ pydub
# for matrix-commander
aiohttp
aiofiles #<0.5.0,>=0.4.0
aiofiles<0.5.0,>=0.4.0
argparse
asyncio
atomicwrites
cachetools
datetime
markdown
matrix-nio[e2e]>=0.18.3
peewee
matrix-nio[e2e]>=0.14.1
Pillow
python-magic
python_magic
uuid

View File

@ -1,5 +0,0 @@
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import fritzab2matrix

View File

@ -1,5 +0,0 @@
from context import fritzab2matrix as fab2m