|
@@ -8,6 +8,7 @@ import glob
|
|
|
import io
|
|
|
import os
|
|
|
import signal
|
|
|
+import smtplib
|
|
|
import string
|
|
|
import subprocess
|
|
|
import sys
|
|
@@ -19,6 +20,7 @@ import wave
|
|
|
from datetime import datetime
|
|
|
|
|
|
import linphone
|
|
|
+import yaml
|
|
|
from enum import Enum
|
|
|
|
|
|
VOLUME_THRESHOLD = 100
|
|
@@ -64,51 +66,6 @@ replies_generic.sort()
|
|
|
|
|
|
THREADS_MUST_QUIT = False
|
|
|
|
|
|
-KTIP_LOOKUP_URL = "https://www.ktipp.ch/service/warnlisten/detail/?warnliste_id=7&ajax=ajax-search-form&keyword={" \
|
|
|
- "$number$}"
|
|
|
-
|
|
|
-SHOULDIANSWER_LOOKUP_URL = "https://ch.shouldianswer.net/telefonnummer/{$number$}"
|
|
|
-
|
|
|
-
|
|
|
-def is_in_blacklists(a_number):
|
|
|
- return is_in_local_blacklist(a_number) or is_in_ktipp_blacklist(a_number) or is_in_shiansw_blacklist(a_number)
|
|
|
-
|
|
|
-
|
|
|
-def is_in_local_blacklist(a_number):
|
|
|
- black_list = current_dir + "/blacklist.txt"
|
|
|
- if os.path.isfile(black_list):
|
|
|
- return a_number in open(current_dir + "/blacklist.txt").read()
|
|
|
-
|
|
|
-
|
|
|
-def is_in_ktipp_blacklist(a_number):
|
|
|
- # On peut interroger le site ktipp:
|
|
|
- # https://www.ktipp.ch/service/warnlisten/detail/?warnliste_id=7&ajax=ajax-search-form&keyword=0445510503
|
|
|
- # Si argument keyword pas trouvé, ca donne ca dans la réponse :
|
|
|
- # 0 Einträge
|
|
|
-
|
|
|
- the_number = a_number.lstrip("0")
|
|
|
- the_number = the_number.replace("+", "")
|
|
|
-
|
|
|
- url = KTIP_LOOKUP_URL.replace("{$number$}", the_number)
|
|
|
- response = ""
|
|
|
- try:
|
|
|
- response = urllib2.urlopen(url).read()
|
|
|
- except urllib2.HTTPError:
|
|
|
- pass
|
|
|
-
|
|
|
- return "0 Eintr" not in response
|
|
|
-
|
|
|
-
|
|
|
-def is_in_shiansw_blacklist(a_number):
|
|
|
- url = SHOULDIANSWER_LOOKUP_URL.replace("{$number$}", a_number)
|
|
|
- response = ""
|
|
|
- try:
|
|
|
- response = urllib2.urlopen(url).read()
|
|
|
- except urllib2.HTTPError:
|
|
|
- pass
|
|
|
-
|
|
|
- return '<div class="review_score negative"></div>' in response
|
|
|
-
|
|
|
|
|
|
def get_wav_duration(fname):
|
|
|
with contextlib.closing(wave.open(fname, 'r')) as f:
|
|
@@ -123,11 +80,23 @@ def sleep(duration):
|
|
|
|
|
|
|
|
|
class SipConnection(object):
|
|
|
+ class MailType(Enum):
|
|
|
+ Notify_Incoming_Call = 1
|
|
|
+ Notify_Incoming_Telemarketer_Call = 2
|
|
|
+
|
|
|
def log(self, msg):
|
|
|
to_show = str(self) + ": " + msg
|
|
|
print(to_show)
|
|
|
syslog.syslog(to_show)
|
|
|
|
|
|
+ def mail(self, mail_cfg, text):
|
|
|
+ try:
|
|
|
+ server = smtplib.SMTP(mail_cfg["smtp_host"])
|
|
|
+ server.sendmail(mail_cfg["from"], mail_cfg["to"], text)
|
|
|
+ server.quit()
|
|
|
+ except smtplib.SMTPException as e:
|
|
|
+ self.log("Error sending email " + e.message)
|
|
|
+
|
|
|
def say(self, core):
|
|
|
if self._conversation.status is not ConversationStatus.IMTALKING:
|
|
|
self._conversation.status = ConversationStatus.IMTALKING
|
|
@@ -211,13 +180,18 @@ class SipConnection(object):
|
|
|
if state == linphone.CallState.IncomingReceived:
|
|
|
self.log("Incoming call : {}".format(call.remote_address.username))
|
|
|
|
|
|
+ self.mail_if_needed(call.remote_address.username, self.MailType.Notify_Incoming_Call)
|
|
|
+
|
|
|
self._replies_pos = 0
|
|
|
|
|
|
- if is_in_blacklists(call.remote_address.username):
|
|
|
+ if self.is_in_blacklists(call.remote_address.username):
|
|
|
self.log("telemarketer calling : " + call.remote_address.username)
|
|
|
+ self.mail_if_needed(call.remote_address.username, self.MailType.Notify_Incoming_Telemarketer_Call)
|
|
|
|
|
|
call_params = core.create_call_params(call)
|
|
|
- os.makedirs(current_dir + "/out", exist_ok=True)
|
|
|
+ if not os.path.isdir(current_dir + "/out"):
|
|
|
+ os.makedirs(current_dir + "/out")
|
|
|
+
|
|
|
a_file = current_dir + "/out/call_from_" + slugify(call.remote_address.username) + \
|
|
|
"_" + datetime.now().strftime(
|
|
|
'%Y-%m-%d_%Hh%Mmn%Ss') + ".wav"
|
|
@@ -252,11 +226,13 @@ class SipConnection(object):
|
|
|
self._core.record_file = self._incoming_stream_file
|
|
|
|
|
|
proxy_cfg = self._core.create_proxy_config()
|
|
|
- proxy_cfg.identity_address = self._core.create_address('sip:' + self._username + '@' + self._domain + ':5060')
|
|
|
- proxy_cfg.server_addr = 'sip:' + self._domain + ':5060'
|
|
|
+ proxy_cfg.identity_address = self._core.create_address('sip:' + self._config_info["username"] +
|
|
|
+ '@' + self._config_info["domain"] + ':5060')
|
|
|
+ proxy_cfg.server_addr = 'sip:' + self._config_info["domain"] + ':5060'
|
|
|
proxy_cfg.register_enabled = True
|
|
|
self._core.add_proxy_config(proxy_cfg)
|
|
|
- auth_info = self._core.create_auth_info(self._username, None, self._password, None, None, self._domain)
|
|
|
+ auth_info = self._core.create_auth_info(self._config_info["username"], None, self._config_info["password"],
|
|
|
+ None, None, self._config_info["domain"])
|
|
|
self._core.add_auth_info(auth_info)
|
|
|
|
|
|
while not self._is_quitting:
|
|
@@ -268,19 +244,18 @@ class SipConnection(object):
|
|
|
self.__exit__(None, None, None)
|
|
|
|
|
|
def __str__(self):
|
|
|
- return self._username + "@" + self._domain
|
|
|
+ return self._config_info["username"] + "@" + str(self._config_info["domain"])
|
|
|
|
|
|
- def __init__(self, domain, username, password):
|
|
|
+ def __init__(self, config_info):
|
|
|
|
|
|
callbacks = {
|
|
|
'call_state_changed': self.call_state_changed,
|
|
|
'registration_state_changed': self.registration_state_changed,
|
|
|
}
|
|
|
|
|
|
+ self._config_info = config_info
|
|
|
+
|
|
|
self._core = linphone.Core.new(callbacks, None, None)
|
|
|
- self._domain = domain
|
|
|
- self._username = username
|
|
|
- self._password = password
|
|
|
self._is_quitting = False
|
|
|
|
|
|
self._registration_previous_message = ""
|
|
@@ -291,22 +266,96 @@ class SipConnection(object):
|
|
|
self._incoming_stream_file = tempfile.NamedTemporaryFile(delete=False).name
|
|
|
self._core.iterate()
|
|
|
|
|
|
+ def mail_if_needed(self, number, type):
|
|
|
+ if "mailer" in self._config_info:
|
|
|
+ mail_cfg = self._config_info["mailer"]
|
|
|
+ if type == self.MailType.Notify_Incoming_Call:
|
|
|
+ if mail_cfg["log_all_call"]:
|
|
|
+ self.mail(mail_cfg, "Appel entrant : " + number)
|
|
|
+
|
|
|
+ if type == self.MailType.Notify_Incoming_Telemarketer_Call:
|
|
|
+ self.mail(mail_cfg, "Appel télémarketeur entrant : " + number)
|
|
|
+
|
|
|
+ def is_in_blacklists(self, a_number):
|
|
|
+ return self.is_in_local_blacklist(a_number) or self.is_in_directory_ch_blacklist(
|
|
|
+ a_number) or self.is_in_ktipp_blacklist(a_number) \
|
|
|
+ or self.is_in_shiansw_blacklist(a_number)
|
|
|
+
|
|
|
+ def is_in_local_blacklist(self, a_number):
|
|
|
+ black_list = current_dir + "/blacklist.txt"
|
|
|
+ if os.path.isfile(black_list):
|
|
|
+ res = a_number in open(current_dir + "/blacklist.txt").read()
|
|
|
+ if res:
|
|
|
+ self.log(a_number + " Found in localblacklist")
|
|
|
+ return res
|
|
|
+
|
|
|
+ def is_in_ktipp_blacklist(self, a_number):
|
|
|
+ # On peut interroger le site ktipp:
|
|
|
+ # https://www.ktipp.ch/service/warnlisten/detail/?warnliste_id=7&ajax=ajax-search-form&keyword=0445510503
|
|
|
+ # Si argument keyword pas trouvé, ca donne ca dans la réponse :
|
|
|
+ # 0 Einträge
|
|
|
+
|
|
|
+ base_url = "https://www.ktipp.ch/service/warnlisten/detail/?warnliste_id=7&ajax=ajax-search-form&keyword={" \
|
|
|
+ "$number$}"
|
|
|
+
|
|
|
+ the_number = a_number.lstrip("0")
|
|
|
+ the_number = the_number.replace("+", "")
|
|
|
+
|
|
|
+ url = base_url.replace("{$number$}", the_number)
|
|
|
+ response = ""
|
|
|
+ try:
|
|
|
+ response = urllib2.urlopen(url).read()
|
|
|
+ except urllib2.HTTPError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ res = "0 Eintr" not in response
|
|
|
+ if res:
|
|
|
+ self.log(a_number + " found in ktipp blacklist")
|
|
|
+ return res
|
|
|
+
|
|
|
+ def is_in_shiansw_blacklist(self, a_number):
|
|
|
+ base_url = "https://ch.shouldianswer.net/telefonnummer/{$number$}"
|
|
|
+ url = base_url.replace("{$number$}", a_number)
|
|
|
+ response = ""
|
|
|
+ try:
|
|
|
+ response = urllib2.urlopen(url).read()
|
|
|
+ except urllib2.HTTPError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ res = '<div class="review_score negative"></div>' in response
|
|
|
+ if res:
|
|
|
+ self.log("Found in ch.shouldianswer.net blacklist")
|
|
|
+ return res
|
|
|
+
|
|
|
+ def is_in_directory_ch_blacklist(self, a_number):
|
|
|
+ base_url = "https://tel.local.ch/fr/{$number$}"
|
|
|
+ url = base_url.replace("{$number$}", a_number)
|
|
|
+ response = ""
|
|
|
+ try:
|
|
|
+ response = urllib2.urlopen(url).read()
|
|
|
+ except urllib2.HTTPError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ res = 'https://tel.local.ch/fr/spamnumber/' in response
|
|
|
+
|
|
|
+ if res:
|
|
|
+ self.log(a_number + " found in directories.ch blacklist")
|
|
|
+ return res
|
|
|
+
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
cfg = ConfigParser.SafeConfigParser()
|
|
|
|
|
|
- cfg_path = current_dir + "/config.ini"
|
|
|
+ cfg_path = current_dir + "/config.yml"
|
|
|
|
|
|
if len(sys.argv) == 2:
|
|
|
cfg_path = sys.argv[1]
|
|
|
|
|
|
connections = []
|
|
|
|
|
|
- cfg.read(cfg_path)
|
|
|
-
|
|
|
- for c in cfg.sections():
|
|
|
- connections.append(SipConnection(cfg.get(c, "domain"), cfg.get(c, "username"), cfg.get(c, "password")))
|
|
|
+ for connection_cfg in yaml.load(file(cfg_path)):
|
|
|
+ connections.append(SipConnection(connection_cfg))
|
|
|
|
|
|
for sip_c in connections:
|
|
|
threading.Thread(target=sip_c.start).start()
|
|
@@ -315,7 +364,6 @@ if __name__ == "__main__":
|
|
|
# Ensuring clean quit and ressource releasing
|
|
|
# when receiving ctrl-c from console or SIGTERM
|
|
|
# from daemon manager.
|
|
|
-
|
|
|
def signal_handler(sig, frame):
|
|
|
print('External stop request!')
|
|
|
for conn in connections:
|