RaspberryPiでPBXとFAXの構築 - 迷惑電話対策2

投稿 2025/02/02 (日) 午後 12:52 | パソコン | hotall

迷惑電話対策をした我家のPBX、それなりに効果はありましたが、欲が出てきました。


現状、見知らぬ番号から電話が掛かって来た場合の対応は、電話番号から種類と地域を判別し、そこから掛かってくる当てが無ければ、受話せず留守番電話に任せます。
後ほど、ネットでその電話番号を検索し、相手を特定し、着信電話番号リストファイルに名前と判定区分を追記することで、再度掛かってきた迷惑電話を自動的に拒否します。

これらの作業を手動で行っていますが、これが結構面倒なのです。

そもそも、電話に表示される電話番号だけで相手を推定するのは困難なのです。
それに、迷惑電話以外の場合でも、相手の名前などを着信電話番号リストファイルのコメント欄に追記するのですが、再度掛かって来た時に、即座にその情報を見ることができません。

今は仕方なく、子機側の電話帳に同じ情報を追加しています。
これでは、重複管理になり、煩わしいのです。

そこで、次の機能を追加することにしました。
  • 再度掛かってきた番号には、リストファイルの迷惑電話の判定と共に、コメントを着信者名として子機に渡す。
    (通常、子機は自身の電話帳に無ければ、渡された名前を表示します。)
  • 初めて掛かって来た番号には、WEBから名前と迷惑電話に関する情報を取得・判定し、子機に渡す。
    また、リストファイルにこれらの情報を追記・保存する。

この文書は個人的なものであり、内容について一切の責任を負いかねます。



プログラムを変更・更新する。
場所:/usr/share/asterisk/agi-bin
ファイル名:meiwaku_check.py
#!/usr/bin/env python3
""" 迷惑電話判定 AGIスクリプト

着信電話番号が迷惑電話か否かを判定する。
判定結果は指定された変数に設定(クォートで囲まない)
'OK':迷惑電話ではない
'NG':迷惑電話
'' :不明

着信電話番号の判定は、着信電話番号リストファイルを検索し、
その判定区分(OK/NG/)で判断する。
ファイルに無い場合は、判定区分を不明:''としてファイルの先頭に追加する。
検索された着信電話番号のデータは日付時刻(yyyy/mm/dd hh:mm:ss)と回数を設定して
ファイルの先頭に移動する。
着信電話番号リストファイルのフォーマット:テキストファイル(utf-8)
<着信電話番号>,<判定区分>,<最新着信日時>,<回数>,<コメント/名前>LF
<着信電話番号>,<判定区分>,<最新着信日時>,<回数>,<コメント/名前>LF
...

Arguments
----------
resvar : str
判定結果を格納する変数名。デフォルト:'MEIWAKU_CHK'
comvar : str
コメント/名前を格納する変数名。デフォルト:'COMMENT'

Returns
-------
int
0 : 正常終了 その他 : 処理失敗

Example
-------
exten => s,1,AGI(meiwaku_check.py,meiwaku_chk,comment)
same => n,GotoIf($["${meiwaku_chk}" = "NG"]?meiwaku)
same => n,GotoIf($["${comment}" = ""]?callphones)
same => n,Set(CALLERID(name)=${comment})
same => n(callphones),Dial(...)

Notes
-----
コメント(名前)と判定区分は初回着信時にWEBから取得/判定する。
その内容に不備がある場合は、テキストエディタで着信電話番号リストファイルに
手作業で追記/修正する。
着信電話番号リストファイルのパスは変数:CALLER_LIST_FILEに格納。

"""
__author__ = "Yasuaki Nojiri"
__version__ = "1.0.0"
__date__ = "2024/10/30"

import sys
import os
import stat
import re
from datetime import datetime

# パッケージのインストールディレクトリと参照ディレクトリが一致しない場合
# pyst2パッケージインストールディレクトリを追加
# "pip show pyst2"コマンドでインストール先を確認する
sys.path.append('/home/pi/.local/lib/python3.9/site-packages')
from asterisk.agi import *
import requests

# 定数の設定
CALLER_LIST_DIR = '/usr/local/share/meiwakuchk'
CALLER_LIST_FILE = CALLER_LIST_DIR + '/' + 'caller-list.txt'
CALLER_LIST_FILE_NEW = CALLER_LIST_FILE + '.new'
CALLER_LIST_FILE_PRM = stat.S_IRWXU | \
stat.S_IRGRP | stat.S_IWGRP | \
stat.S_IROTH | stat.S_IWOTH
DEFAULT_RECORD = ['','','','0','']
RECORD_LEN = len(DEFAULT_RECORD)
SEARCH_URL = r"https://search.yahoo.co.jp/search?ei=UTF-8&p="
SEARCH_NAME_REGEX = r"(<li><a[^>]+>[\s]*「([^」\d]+)」\()|" + \
r"([\d]+は[\s、]*([^<]+)の電話)|" + \
r"(電話番号(<b>|)[\d]+(</b>|)は([^<\-\s]+))|" + \
r"(電話番号(<b>|)[\d]+(</b>|)(([^<)]+))は)|" + \
r"(([ぁ-んァ-ヶア-ン゙゚一-龠ー]+)[とを](名乗|騙|偽))"
SEARCH_NAME_REGEX_IDXS = [2,4,8,12,13]
SEARCH_MEIWK_REGEX = r"([はの](営業|不用品買取|迷惑電話))|" + \
r"(([ぁ-んァ-ヶア-ン゙゚一-龠ー]+)[とを](名乗|騙|偽))|" + \
r"(架空請求|詐欺|未払|還付|未納|疑い)|" + \
r"(うとする)"
SEARCH_MEIWK_REGEX_IDXS = [2,3,6,7]

def main(args):
"""メイン関数

Parameters
----------
args : array
コマンドライン引数

Returns
-------
int
終了コード

"""
resvar_val = ''

# コマンドライン引数
resvar = args[1] if len(args) > 1 else 'MEIWAKU_CHK'
comvar = args[2] if len(args) > 2 else 'COMMENT'

# AGIの生成
agi = AGI()

# 着信情報の取得
caller_id = str(agi.env['agi_callerid']) # 着信電話番号

agi.verbose("meiwaku_check agi start : " + caller_id)

# 着信電話番号リストファイルの準備
os.makedirs(CALLER_LIST_DIR, exist_ok=True)
if not os.path.isfile(CALLER_LIST_FILE):
# 存在しない場合は、空ファイルを作成
with open(CALLER_LIST_FILE,'w'):pass

# 着信電話番号リストファイルの検索
resrec = None
resrec_index = -1
with open(CALLER_LIST_FILE, mode='r') as file_in:
# 1行毎に読込む
for i, rec_i in enumerate(file_in):
record = rec_i.split(',')
if len(record) > 0 and record[0] == caller_id:
# 見つかった場合、必要な情報を退避
resrec_index = i
resrec = record
resrec[-1] = resrec[-1].removesuffix('\n')
# 不足項目があれば、デフォルト値で埋める
for i in range(len(resrec),RECORD_LEN): resrec += [DEFAULT_RECORD[i]]
break

if resrec is None:
# 見つからなかった場合、新規に作成
resrec = DEFAULT_RECORD.copy()
resrec[0] = caller_id
# WEBで検索
rq = requests.get(SEARCH_URL+caller_id)
# 名前を取得
res = re.search(SEARCH_NAME_REGEX, rq.text)
if res:
# マッチできた場合、マッチした名前グループリストの列挙
for i, index in enumerate(SEARCH_NAME_REGEX_IDXS):
# マッチした情報を調べる
if res.group(index):
# マッチした名前があれば、これを取得
resrec[4] = res.group(index)
break
# 迷惑電話の判定
res = re.search(SEARCH_MEIWK_REGEX, rq.text)
if res:
# マッチできた場合、マッチした迷惑情報グループリストの列挙
for i, index in enumerate(SEARCH_MEIWK_REGEX_IDXS):
# マッチした情報を調べる
if res.group(index):
# マッチした情報があれば、迷惑電話として設定
resrec[1] = 'NG'
break

# 最新着信日時の更新
resrec[2] = datetime.now().strftime("%Y/%m/%d %H:%M:%S")

# 回数の更新
resrec[3] = str(int(resrec[3]) + 1) if resrec[3].isdecimal() else '1'

# 着信電話番号リストファイルの更新
with open(CALLER_LIST_FILE, mode='r') as file_in, \
open(CALLER_LIST_FILE_NEW, mode='w') as file_out:
# 今回のデータを先頭にする
print(*resrec, sep=',', end='\n', file=file_out)

# 元のファイルの内容を出力
for i, rec_i in enumerate(file_in):
if i != resrec_index:
# 今回の検索データ以外を出力
print(rec_i, end='', file=file_out)

# 権限の設定
os.chmod(CALLER_LIST_FILE_NEW, CALLER_LIST_FILE_PRM)

# 更新ファイルに置換
os.remove(CALLER_LIST_FILE)
os.rename(CALLER_LIST_FILE_NEW, CALLER_LIST_FILE)

# 判定結果を変数に格納
agi.set_variable(resvar,resrec[1])
agi.set_variable(comvar,resrec[4])

agi.verbose("meiwaku_check agi results : " + resvar + " = " + resrec[1])

return 0

if __name__ == "__main__":
# トップレベル環境で実行された場合、メイン関数を呼ぶ
sys.exit(main(sys.argv))
検索結果の様々なサイトのサマリ情報から名前と迷惑電話判定情報を取得するのですが、決まったフォーマットはありません。ここでは、現状の検索結果を参考に、正規表現でのパターンマッチングをしています。
精度を上げるためには、継続的なパターンの調整が必用です。


PythonのWebアクセスライブラリをインストールします。
pip3 install requests

ダイヤルプランを変更します。
処理内容は次の通り
  • 電話番号をユーザープログラムに渡し、判定結果とコメントを受取る。
  • 判定結果がNGであれば、子機への呼出は行わず、留守番電話に切り替える。
  • NGでなければ、以下の処理を行う。
    • コメントがあれば、これを子機に渡す。
    • 通常の処理を行う。
ファイル:/etc/asterisk/extensions.conf
変更内容
外線着信の処理の変更
[from-hikari]
exten => s,1,AGI(meiwaku_check.py,meiwaku_chk,comment)
same => n,GotoIf($["${meiwaku_chk}" = "NG"]?meiwaku)
same => n,GotoIf($["${comment}" = ""]?callphones)
same => n,Set(CALLERID(name)=${comment})
same => n(callphones),Dial(PJSIP/1001&PJSIP/1002,20,Tt)
same => n,Answer()
same => n,Wait(1)
same => n,Voicemail(5000)
same => n,Hangup
; Meiwaku denwa
same => n(meiwaku),Ringing()
same => n,Wait(8)
same => n,Answer()
same => n,Wait(1)
same => n,Voicemail(5000)
same => n,Hangup
5000は留守電、1001と1002は子機の内線番号です。実際の運用に合わせてください。

プログラム更新後、早速、初めての迷惑電話が入り、拒否してくれました。
ただ、着信者名は取得できず、マッチングパターンを修正しました。
(上記のプログラムは修正後のコードです。)

今後も、修正が必要になりそうですが、気長にメンテナンスしていこうと思います。



■ RaspberryPiでPBXとFAXの構築 - 目次 -
  1. 構成
  2. ひかり電話RV-230SEの設定
  3. OSのセットアップ
  4. LANアダプタの接続
  5. ユーティリティのセットアップ
  6. PBXのセットアップ
  7. ソフトウェアモデムのセットアップ
  8. FAXのセットアップ
  9. 自動再起動
  10. ステータスLEDの接続
  11. ケースに収納・設置
  12. トラブルと対応
  13. 迷惑電話対策
  14. ▶迷惑電話対策2
« Prev • Next »

コメント

コメントはありません。

コメントする