MicroSIP自定义web拨打协议

需求:通过网页电话号码呼叫指定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、网页呼叫 自定义 的前缀:电话号码

相关推荐
yuanmenghao2 小时前
Linux 配置Samba,Windows登录,开机自动映射登录
linux·运维·windows·操作系统·samba
秋天枫叶352 小时前
【k8s集群Docker + cri-dockerd】服务器重启或关机后 apiserver/controller/scheduler 无法自动恢复
linux·运维·服务器·容器·kubernetes·bug
赋创小助手2 小时前
超微2U高密度服务器AS-2126HS-TN评测(双AMD EPYC 9005 Turin)
运维·服务器·人工智能·深度学习·神经网络·自然语言处理·架构
松涛和鸣2 小时前
59、 IMX6ULL按键驱动开发:轮询到中断的实现
linux·服务器·c语言·arm开发·数据库·驱动开发
幻云20102 小时前
Python机器学习:筑基与实践
前端·人工智能·python
web小白成长日记2 小时前
Vue3中如何优雅实现支持多绑定变量和修饰符的双向绑定组件?姜姜好
前端·javascript·vue.js
LcVong2 小时前
C# 基于MemoryMappedFile实现进程间通信(服务端+客户端完整范例)
linux·服务器·c#
晴天飛 雪2 小时前
Spring Boot 接口耗时统计
前端·windows·spring boot
0思必得02 小时前
[Web自动化] Selenium模拟用户的常见操作
前端·python·selenium·自动化