需求:通过网页电话号码呼叫指定MicroSIP。
技术调研:MicroSIP支持sip:10086 进行网页调用进行呼叫。
实现:一台电脑安装多个sip,可以自定义Session Initiation Protocol,会话初始协议,可以把sip换成自己任意的ket值,实现不同key值绑定不同的MicroSIP.exe客户端
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
import os
import subprocess
import json
import sys
CONFIG_FILE = "sip_config.json"
class SIPProtocolManager:
def __init__(self, root):
self.root = root
self.root.title("SIP 自定义协议管理器")
self.root.geometry("720x520")
self.root.resizable(True, True)
self.config = {
"microsip_path": "",
"protocols": ["sip2", "fcp"]
}
self.load_config()
self.create_widgets()
def create_widgets(self):
# === MicroSIP 路径 ===
path_frame = ttk.Frame(self.root)
path_frame.pack(fill=tk.X, padx=10, pady=10)
ttk.Label(path_frame, text="MicroSIP.exe 路径:").pack(anchor=tk.W)
self.path_var = tk.StringVar(value=self.config["microsip_path"])
path_entry = ttk.Entry(path_frame, textvariable=self.path_var, width=70)
path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
ttk.Button(path_frame, text="浏览...", command=self.browse_microsip).pack(side=tk.RIGHT)
# === 协议列表 ===
proto_frame = ttk.LabelFrame(self.root, text="自定义协议前缀 (如 sip2, fcp)")
proto_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
list_frame = ttk.Frame(proto_frame)
list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.proto_listbox = tk.Listbox(list_frame, selectmode=tk.SINGLE, height=6)
self.proto_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.proto_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.proto_listbox.config(yscrollcommand=scrollbar.set)
for proto in self.config["protocols"]:
self.proto_listbox.insert(tk.END, proto)
btn_frame = ttk.Frame(proto_frame)
btn_frame.pack(fill=tk.X, padx=5, pady=(0, 5))
ttk.Button(btn_frame, text="添加", command=self.add_protocol).pack(side=tk.LEFT, padx=2)
ttk.Button(btn_frame, text="删除", command=self.remove_protocol).pack(side=tk.LEFT, padx=2)
# === 测试拨号区域 ===
test_frame = ttk.LabelFrame(self.root, text="测试拨号")
test_frame.pack(fill=tk.X, padx=10, pady=5)
test_inner = ttk.Frame(test_frame)
test_inner.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(test_inner, text="测试号码:").pack(side=tk.LEFT)
self.test_number_var = tk.StringVar(value="10086")
ttk.Entry(test_inner, textvariable=self.test_number_var, width=20).pack(side=tk.LEFT, padx=5)
ttk.Button(test_inner, text="拨打测试", command=self.test_dial).pack(side=tk.LEFT, padx=5)
# === 底部按钮 ===
bottom_frame = ttk.Frame(self.root)
bottom_frame.pack(fill=tk.X, padx=10, pady=10)
ttk.Button(bottom_frame, text="生成注册表文件 (.reg)", command=self.generate_reg).pack(side=tk.LEFT, padx=2)
ttk.Button(bottom_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT, padx=2)
def browse_microsip(self):
file_path = filedialog.askopenfilename(
title="选择 MicroSIP.exe",
filetypes=[("Executable files", "*.exe"), ("All files", "*.*")]
)
if file_path:
# 强制转换为 Windows 反斜杠
file_path = file_path.replace("/", "\\")
self.path_var.set(file_path)
def add_protocol(self):
name = simpledialog.askstring("添加协议", "输入协议前缀(如 sip2, fcp):")
if name and name.strip():
name = name.strip()
if name not in self.config["protocols"]:
self.config["protocols"].append(name)
self.proto_listbox.insert(tk.END, name)
else:
messagebox.showwarning("警告", "该协议已存在!")
def remove_protocol(self):
sel = self.proto_listbox.curselection()
if sel:
idx = sel[0]
proto = self.proto_listbox.get(idx)
self.proto_listbox.delete(idx)
self.config["protocols"].remove(proto)
else:
messagebox.showinfo("提示", "请先选中一个协议")
def generate_reg(self):
microsip_path = self.path_var.get().strip()
if not microsip_path or not os.path.isfile(microsip_path):
messagebox.showerror("错误", "请先选择有效的 MicroSIP.exe 路径!")
return
protocols = self.config["protocols"]
if not protocols:
messagebox.showwarning("警告", "至少需要一个自定义协议!")
return
# ✅ 关键修复:正确转义路径
reg_exe_path = microsip_path.replace("/", "\\").replace("\\", "\\\\")
reg_content = "Windows Registry Editor Version 5.00\n\n"
for proto in protocols:
reg_content += f'[HKEY_CLASSES_ROOT\\{proto}]\n'
reg_content += f'@="Custom SIP Protocol ({proto})"\n'
reg_content += '"URL Protocol"=""\n'
reg_content += '"Owner Name"="SIPManager"\n\n'
reg_content += f'[HKEY_CLASSES_ROOT\\{proto}\\DefaultIcon]\n'
reg_content += f'@="{reg_exe_path},0"\n\n'
reg_content += f'[HKEY_CLASSES_ROOT\\{proto}\\shell]\n\n'
reg_content += f'[HKEY_CLASSES_ROOT\\{proto}\\shell\\open]\n\n'
reg_content += f'[HKEY_CLASSES_ROOT\\{proto}\\shell\\open\\command]\n'
# 假设 handler 与 MicroSIP 同目录
handler_path = os.path.join(os.path.dirname(microsip_path), "sip_handler.exe")
handler_reg_path = handler_path.replace("/", "\\").replace("\\", "\\\\")
reg_content += f'@="\\"{handler_reg_path}\\" \\"%1\\""\n\n'
save_path = filedialog.asksaveasfilename(
defaultextension=".reg",
filetypes=[("Registry files", "*.reg")],
title="保存注册表文件"
)
if save_path:
# ✅ 修复:显式写入 UTF-16 LE BOM
import codecs
with open(save_path, "wb") as f:
f.write(codecs.BOM_UTF16_LE)
f.write(reg_content.encode('utf-16-le'))
messagebox.showinfo("成功", f"注册表文件已保存到:\n{save_path}")
def test_dial(self):
number = self.test_number_var.get().strip()
if not number:
messagebox.showwarning("警告", "请输入测试号码!")
return
microsip_path = self.path_var.get().strip()
if not microsip_path or not os.path.isfile(microsip_path):
messagebox.showerror("错误", "MicroSIP 路径无效!")
return
try:
subprocess.Popen([microsip_path, f"sip:{number}"])
messagebox.showinfo("测试拨号", f"已拨打: sip:{number}")
except Exception as e:
messagebox.showerror("错误", f"启动失败:\n{str(e)}")
def save_config(self):
self.config["microsip_path"] = self.path_var.get().strip().replace("/", "\\")
try:
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
messagebox.showinfo("成功", f"配置已保存到 {CONFIG_FILE}")
except Exception as e:
messagebox.showerror("错误", f"保存失败:\n{str(e)}")
def load_config(self):
if os.path.isfile(CONFIG_FILE):
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
loaded = json.load(f)
if "microsip_path" in loaded:
loaded["microsip_path"] = loaded["microsip_path"].replace("/", "\\")
self.config.update(loaded)
except:
pass
# ========================
# 协议处理器(打包后作为 sip_handler.exe)
# ========================
def protocol_handler():
if len(sys.argv) < 2:
return
uri = sys.argv[1]
if ":" not in uri:
return
_, number = uri.split(":", 1)
if not number:
return
# 从 handler 所在目录找 MicroSIP.exe
base_dir = os.path.dirname(os.path.abspath(sys.executable))
microsip = os.path.join(base_dir, "MicroSIP.exe")
if os.path.isfile(microsip):
try:
subprocess.Popen([microsip, f"sip:{number}"])
except:
pass
if __name__ == "__main__":
if len(sys.argv) >= 2 and (":" in sys.argv[1]):
protocol_handler()
else:
root = tk.Tk()
app = SIPProtocolManager(root)
root.mainloop()
代码打包以后会变成sip_handler.exe。
使用方法:
1、选择呼叫客户端
2、添加协议前缀
3、生成注册表
4、保存配置
5、注入注册表
6、网页呼叫 自定义 的前缀:电话号码
