import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox
import ctypes
import platform
from zkNet import zkNet
import tools
from make import version

root = tk.Tk()

if platform.system().lower() == 'windows':
    # windows平台根据系统设置进行缩放适应高dpi

    # 调用api设置成由应用程序缩放
    ctypes.windll.shcore.SetProcessDpiAwareness(1)
    # 调用api获得当前的缩放因子
    ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0)
    # 设置缩放因子
    root.tk.call('tk', 'scaling', ScaleFactor / 75)

elif platform.system().lower == 'linux':
    pass

root.update()

root.geometry("400x300")

root.title('zkNet-v%s' % version)

root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
# root.rowconfigure(0, weight=1)

ttk.Label(root, text='账号 : ').grid(row=0, column=0)

ttk.Label(root, text='密码 : ').grid(row=1, column=0)

usernameEntry = ttk.Entry(root)

passwdEntry = ttk.Entry(root, show='*')

usernameEntry.grid(row=0,
                   column=1,
                   padx=10,
                   pady=5,
                   sticky=tk.W + tk.E,
                   columnspan=2)

passwdEntry.grid(row=1,
                 column=1,
                 padx=10,
                 pady=5,
                 sticky=tk.W + tk.E,
                 columnspan=2)

# 分割线
ttk.Separator(root, orient=tk.HORIZONTAL).grid(row=3,
                                               column=0,
                                               sticky=tk.W + tk.E,
                                               columnspan=3)


def msgBox(mtype, title, text):
    if mtype == 'info':
        tk.messagebox.showinfo(title, text)
    elif mtype == 'warn':
        tk.messagebox.showwarning(title, text)
    elif mtype == 'err':
        tk.messagebox.showerror(title, text)
    else:
        tk.messagebox.showerror(title, "无法确定消息类型，软件出现bug，请提issuses反馈")


def authAsPhone():
    net = zkNet(usernameEntry.get(), passwdEntry.get())
    r = net.autoWebAuth('phone')
    msgBox(r[0], r[0], r[1])
    # print("authAsPhone")


def authAsPC():
    net = zkNet(usernameEntry.get(), passwdEntry.get())
    r = net.autoWebAuth('pc')
    msgBox(r[0], r[0], r[1])
    # print("auth as pc")


def authIP():
    net = zkNet(usernameEntry.get(), passwdEntry.get())
    r = net.autoQuickAuthShare(ipEntry.get())
    msgBox(r[0], r[0], r[1])

    # print("auth ip")


def showMore():
    text = "我目前专注于将技术转化为实际收益，若有对计算机领域拥有兴趣与相关技能（如电子、网络、软件等），并且怀揣创新思路的同学，我诚挚地邀请您一同研究交流学习做大做强。如果我的微信号无法联系，请您查阅GitHub发布主页以获取最新信息。任何问题或建议，也都欢迎在GitHub上提出issues，期待与您的交流与合作。"
    github = "Github页面：https://github.com/LingMessy/zkNet"
    msgBox('info', "加入我们", text + '\n' + github)


ttk.Button(root, text='认证本机为手机', width=10,
           command=authAsPhone).grid(row=4,
                                     column=0,
                                     columnspan=3,
                                     sticky=tk.W + tk.E,
                                     padx=10,
                                     pady=5)

ttk.Button(root, text='认证本机为电脑', width=10,
           command=authAsPC).grid(row=5,
                                  column=0,
                                  columnspan=3,
                                  sticky=tk.W + tk.E,
                                  padx=10,
                                  pady=5)

ttk.Button(root, text='认证以下IP为电脑', width=10,
           command=authIP).grid(row=6,
                                column=0,
                                columnspan=3,
                                sticky=tk.W + tk.E,
                                padx=10,
                                pady=5)

ttk.Label(root, text='指定 IP : ').grid(row=7, column=0)

ipEntry = ttk.Entry(root)

ipEntry.grid(row=7,
             column=1,
             padx=10,
             pady=5,
             sticky=tk.W + tk.E,
             columnspan=2)

ttk.Separator(root, orient=tk.HORIZONTAL).grid(row=8,
                                               column=0,
                                               sticky=tk.W + tk.E,
                                               columnspan=3)

cacheTool = tools.cache()
startupTool = tools.startup4Win()

cacheInfoVar = tk.BooleanVar()
cacheInfoVar.set(cacheTool.checkCache())

startupVar = tk.BooleanVar()
startupVar.set(startupTool.checkStartup())

if cacheInfoVar.get():
    infoDict = cacheTool.readCache()
    if infoDict is not None:
        usernameEntry.delete(0, tk.END)
        passwdEntry.delete(0, tk.END)
        ipEntry.delete(0, tk.END)

        usernameEntry.insert(0, infoDict['username'])
        passwdEntry.insert(0, infoDict['passwd'])
        ipEntry.insert(0, infoDict['ip'])


def cacheInfoClick():
    if cacheInfoVar.get():
        infoDict = {
            'username': usernameEntry.get(),
            'passwd': passwdEntry.get(),
            'ip': ipEntry.get(),
        }

        cacheTool.cacheInfo(infoDict=infoDict)
    else:
        cacheTool.removeCache()


def startupClick():
    msgBox(
        'warn', '提示:',
        '为了实现该功能，程序会在启动目录里添加脚本，该操作可能会被杀软误报，导致程序被杀软删除。经测试，如果频繁切换该功能会被自带的defender识别为木马导致程序被删除。'
    )
    startupTool.updateStartup(startupVar.get())


ttk.Checkbutton(root,
                text='记住信息',
                variable=cacheInfoVar,
                command=cacheInfoClick).grid(row=9,
                                             column=0,
                                             columnspan=1,
                                             sticky=tk.W + tk.E,
                                             padx=10,
                                             pady=5)

ttk.Checkbutton(root,
                text='开机自启(仅限exe)',
                variable=startupVar,
                command=startupClick).grid(row=9,
                                           column=1,
                                           columnspan=1,
                                           sticky=tk.W + tk.E,
                                           padx=10,
                                           pady=5)

ttk.Button(root, text='加入我们', width=10, command=showMore).grid(row=9,
                                                               column=2,
                                                               columnspan=1,
                                                               sticky=tk.E,
                                                               padx=10,
                                                               pady=5)


def closeWindow():
    if cacheInfoVar.get():
        infoDict = {
            'username': usernameEntry.get(),
            'passwd': passwdEntry.get(),
            'ip': ipEntry.get(),
        }

        cacheTool.cacheInfo(infoDict=infoDict)

    startupTool.updateStartup(startupVar.get())

    root.destroy()


root.protocol('WM_DELETE_WINDOW', closeWindow)

tk.mainloop()

gui.py
LingMessy/zkNet
import os
import platform as pf

version = '0.9.1'

platform = pf.system().lower()
arch = pf.architecture()[0]
machine = pf.machine()

if __name__ == '__main__':

    if platform == 'windows':

        # TODO 有待补充，测试过的平台可以加到这里
        winX64Machine = ['AMD64']
        winX86Machine = []

        if arch == '64bit' and machine in winX64Machine:
            os.system(
                "pyinstaller -F -w gui.py -n zkNet-v%s-oldGui-x64-win.exe" %
                version)
            os.system("pyinstaller -F -w newGui.py -n zkNet-v%s-x64-win.exe" %
                      version)
        elif arch == '32bit' and machine in winX86Machine:
            os.system(
                "pyinstaller -F -w gui.py -n zkNet-v%s-oldGui-x86-win.exe" %
                version)
            os.system("pyinstaller -F -w newGui.py -n zkNet-v%s-x86-win.exe" %
                      version)
        else:
            print("该平台未被支持")

    elif platform == 'linux':
        # TODO 有待补充，测试过的平台可以加到这里
        linuxX64Machine = ['x86_64']
        linuxX86Machine = []
        if arch == '64bit' and machine == 'x86_64':
            os.system(
                "pyinstaller -F -w gui.py -n zkNet-v%s-oldGui-x64-linux.bin" %
                version)
            os.system(
                "pyinstaller -F -w newGui.py -n zkNet-v%s-x64-linux.bin" %
                version)
        elif arch == '32bit' and machine == 'i386':
            os.system(
                "pyinstaller -F -w gui.py -n zkNet-v%s-oldGui-x86-linux.bin" %
                version)
            os.system(
                "pyinstaller -F -w newGui.py -n zkNet-v%s-x86-linux.bin" %
                version)
        else:
            print("该平台未被支持")

    else:
        print("该平台未被支持")

    print("当前平台为：%s-%s-%s" % (platform, arch, machine))

make.py
LingMessy/zkNet
import customtkinter as ctk
from zkNet import zkNet
import tkinter as tk

ctk.set_appearance_mode("dark")  # Modes: system (default), light, dark
ctk.set_default_color_theme("blue")  # Themes: blue (default), dark-blue, green

root = ctk.CTk()

root.geometry("300x300")

root.title('zkNet')

root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=2)

ctk.CTkLabel(root, text='账号 : ').grid(row=0, column=0)

ctk.CTkLabel(root, text='密码 : ').grid(row=1, column=0)

e1 = ctk.CTkEntry(root)

e2 = ctk.CTkEntry(root, show='*')

e1.grid(row=0, column=1, padx=10, pady=5, sticky=ctk.W + ctk.E)

e2.grid(row=1, column=1, padx=10, pady=5, sticky=ctk.W + ctk.E)

# 分割线
# ctk.Separator(root, orient=tk.HORIZONTAL).grid(row=3,
#                                                column=0,
#                                                sticky=tk.W + tk.E,
#                                                columnspan=2)


def msgBox(mtype, title, text):
    if mtype == 'info':
        tk.messagebox.showinfo(title, text)
    elif mtype == 'warn':
        tk.messagebox.showwarning(title, text)
    elif mtype == 'err':
        tk.messagebox.showerror(title, text)
    else:
        tk.messagebox.showerror(title, "无法确定消息类型，软件出现bug，请提issuses反馈")


def authAsPhone():
    net = zkNet(e1.get(), e2.get())
    r = net.autoWebAuth('phone')
    msgBox(r[0], r[0], r[1])
    # print("authAsPhone")


def authAsPC():
    net = zkNet(e1.get(), e2.get())
    r = net.autoWebAuth('pc')
    msgBox(r[0], r[0], r[1])
    # print("auth as pc")


def authIP():
    net = zkNet(e1.get(), e2.get())
    r = net.autoQuickAuthShare(e3.get())
    msgBox(r[0], r[0], r[1])

    # print("auth ip")


def showMore():
    text = "我目前专注于将技术转化为实际收益，若有对计算机领域拥有兴趣与相关技能（如电子、网络、软件等），并且怀揣创新思路的同学，我诚挚地邀请您一同研究交流学习做大做强。如果我的微信号无法联系，请您查阅GitHub发布主页以获取最新信息。任何问题或建议，也都欢迎在GitHub上提出issues，期待与您的交流与合作。"
    github = "Github页面：https://github.com/LingMessy/zkNet"
    msgBox('info', "加入我们", text + '\n' + github)


ctk.CTkButton(root, text='认证本机为手机', width=10,
              command=authAsPhone).grid(row=4,
                                        column=0,
                                        columnspan=2,
                                        sticky=ctk.W + ctk.E,
                                        padx=10,
                                        pady=5)

ctk.CTkButton(root, text='认证本机为电脑', width=10,
              command=authAsPC).grid(row=5,
                                     column=0,
                                     columnspan=2,
                                     sticky=ctk.W + ctk.E,
                                     padx=10,
                                     pady=5)

ctk.CTkButton(root, text='认证以下IP为电脑', width=10,
              command=authIP).grid(row=6,
                                   column=0,
                                   columnspan=2,
                                   sticky=ctk.W + ctk.E,
                                   padx=10,
                                   pady=5)

ctk.CTkLabel(root, text='指定 IP : ').grid(row=7, column=0)

e3 = ctk.CTkEntry(root)

e3.grid(row=7, column=1, padx=10, pady=5, sticky=ctk.W + ctk.E)

# ctk.Separator(root, orient=tk.HORIZONTAL).grid(row=8,
#                                                column=0,
#                                                sticky=tk.W + tk.E,
#                                                columnspan=2)

ctk.CTkButton(root, text='加入我们', width=10, command=showMore).grid(row=9,
                                                                  column=1,
                                                                  columnspan=1,
                                                                  sticky=ctk.E,
                                                                  padx=10,
                                                                  pady=5)

root.mainloop()

newGui.py
LingMessy/zkNet
import os
import sys

# print(filePath)
# d = {'username': '123131231', 'passwd': '1233333', 'ip': '120.1.1.1'}


class cache:
    homePath = os.path.expanduser('~')
    cachePath = os.path.join(homePath, '.cache', 'zkNet')
    filePath = os.path.join(cachePath, 'zkNet.cache')

    def checkCache(self) -> bool:
        if os.path.exists(self.filePath):
            return True
        else:
            return False

    def cacheInfo(self, infoDict: dict[str, str]):
        dictStr = ""
        for k, v in infoDict.items():
            dictStr += "%s = %s\n" % (k, v)

        if not os.path.exists(self.cachePath):
            os.makedirs(self.cachePath)

        with open(self.filePath, 'w', encoding='utf-8') as f:
            f.write(dictStr)

    def readCache(self) -> dict[str, str] | None:
        if not os.path.exists(self.filePath):
            return None

        with open(self.filePath, 'r', encoding='utf-8') as f:
            dictStr = f.read()

        rDict = {}
        for line in dictStr.strip().splitlines():
            k, v = tuple(i.strip() for i in line.split('='))
            rDict[k] = v

        return rDict

    def removeCache(self):
        if os.path.exists(self.filePath):
            os.remove(self.filePath)
            if os.listdir(self.cachePath) == []:
                os.rmdir(self.cachePath)


# 可能被杀软误杀
class startup4Win:
    homePath = os.path.expanduser('~')
    zkNetPath = os.path.realpath(sys.argv[0])
    batPath = r'AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup'
    batPath = os.path.join(homePath, batPath, "zkNet.bat")
    batContent = r'start "" "%s"' % zkNetPath

    def updateStartup(self, startup: bool):
        if not startup:
            self.delStartup()
            return

        if not self.checkStartup():
            self.addStartup()

    def checkStartup(self) -> bool:

        if not os.path.exists(self.batPath):
            return False

        with open(self.batPath, "r") as f:
            content = f.read()

        if content != self.batContent:
            return False

        return True

    def addStartup(self):
        with open(self.batPath, "w") as f:
            f.write(self.batContent)

    def delStartup(self):
        if os.path.exists(self.batPath):
            os.remove(self.batPath)


# addToStartup4Win()

tools.py
LingMessy/zkNet
import requests
import json
import hashlib
import re
import time


def stringToMd5(v: str, coding='utf-8') -> str:
    md5 = hashlib.md5()
    md5.update(v.encode(coding))
    value = md5.hexdigest()
    return value


class zkNet:
    distoken = None

    def __init__(self,
                 userid,
                 passwd,
                 host="1.1.1.1",
                 port='8888',
                 wlanacname="zax_hzstu"):
        self.userid = userid
        self.passwd = passwd
        self.passmd5 = stringToMd5(passwd)
        self.host = host
        self.port = port
        self.url = 'http://' + host + ':' + port
        self.wlanacname = wlanacname

    def getCurDevIp(self) -> str:
        url = 'http://' + self.host
        x = requests
        r = x.get(url, allow_redirects=False)
        rtext = r.text

        start = rtext.find("wlanuserip=") + 11
        end = rtext[start:].find("&")
        ip = rtext[start:start + end]

        return ip

    def webAuth(self, platform: str = 'phone') -> (str, str):
        if platform == 'phone':
            ua = r'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
        # elif platform == 'pc':
        else:
            ua = r'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'

        headers = {
            'Upgrade-Insecure-Requests': '1',
            'Connection': 'keep-alive',
            # 'Referer': r'http://1.1.1.1:8888/webauth.do?wlanacip=192.16.100.1&wlanacname=zax_hzstu&wlanuserip=10.51.68.157&mac=80:05:88:71:a9:85&vlan=0&url=http://1.1.1.1',
            'Content-Type': 'application/x-www-form-urlencoded',
            # 'Origin': 'http://1.1.1.1:8888',
            'Accept-Encoding': 'gzip, deflate',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept':
            r'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            # 'Host':'1.1.1.1:8888',
            'User-Agent': ua,
        }
        session = requests.session()
        session.headers.update(headers)

        # 认证上网
        postData = r'hostIp=http%3A%2F%2F127.0.0.1%3A8081%2F&loginType=&auth_type=0&isBindMac1=0&pageid=1&templatetype=1&listbindmac=0&recordmac=0&isRemind=0&loginTimes=&groupId=&distoken=&echostr=&url=http%3A%2F%2F1.1.1.1&isautoauth=&notice_pic_float=%2Fportal%2Fuploads%2Fpc%2Fhb_pay%2Fimages%2Frrs_bg.jpg'
        postData += '&userId={}&passwd={}'.format(self.userid, self.passwd)
        try:
            r = session.post(
                self.url +
                r'/webauth.do?wlanacip=192.16.100.1&wlanacname=zax_hzstu&wlanuserip=10.51.68.157&mac=80:05:88:71:a9:85&vlan=0&url=http://1.1.1.1',
                data=postData)
        except requests.exceptions.RequestException as e:
            return ('err', '请求错误，请检查网络状态：' + str(e))

        rs = re.search(
            r'<input\sid="distoken"\sname="distoken"\stype="hidden"\svalue="([a-zA-Z0-9]+)"\s\/>',
            r.text)

        if "重复认证" in r.text:
            if rs is None:
                return ('err', '获取distoken失败，可能软件出现bug，请提issuses反馈')
            self.distoken = rs.group(1)
            return ('warn', '检测到重复认证')
        elif '<div class="login_out">' in r.text:
            if rs is None:
                return ('err', '获取distoken失败，可能软件出现bug，请提issuses反馈')
            self.distoken = rs.group(1)
            return ('info', '认证成功')
        else:
            return ('err', '认证可能失败：请检查账号密码')

    def autoWebAuth(self, platform: str = 'phone') -> str:
        r = self.webAuth(platform)
        if "重复认证" in r[1]:
            time.sleep(0.5)

            r1 = self.webOffLine()
            if r1 is not True:
                return r1

            time.sleep(0.5)

            r2 = self.webAuth(platform)
            if "认证成功" not in r2:
                return r2

            return ('warn', '检测到重复认证，该设备已被下线再重新上线')
        else:
            return r

    def isValidIp(ip: str) -> bool:
        pattern = r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
        if re.match(pattern=pattern, string=ip) is not None:
            return True
        else:
            return False

    def quickAuthShare(self, ip: str) -> (str, str):
        if not zkNet.isValidIp(ip):
            return ('err', '输入的IP地址不合法')

        session = requests.session()
        # session.headers.update(headers)

        try:
            # 认证上网
            quickAuthShare = session.get(
                self.url + "/quickAuthShare.do?wlanacip=&wlanacname=" +
                self.wlanacname + "&userId=" + self.userid + "&passwd=" +
                self.passwd + "&mac=&wlanuserip=" + ip)

            data = json.loads(quickAuthShare.text)
            loginmsg = data["message"]
            self.distoken = data["distoken"]
        except requests.exceptions.RequestException as e:
            return ('err', '请求错误，请检查网络连接：' + str(e))

        except Exception as e:
            return ('err', 'json 解析错误：' + str(e))

        if loginmsg == "认证成功" or "正在进行外网拨号请稍候" in loginmsg:
            return ('info', '认证成功')
        elif "重复认证" in loginmsg:
            return ('warn', '检测到重复认证')
        else:
            print(loginmsg)
            return ('err', '认证失败，请检查IP地址或账号密码')

    def autoQuickAuthShare(self, ip: str) -> str:
        r = self.quickAuthShare(ip)
        if "重复认证" in r[1]:
            time.sleep(0.5)

            r1 = self.quickAuthOffLine(ip)
            if r1 is not True:
                return r1

            time.sleep(0.5)

            r2 = self.quickAuthShare(ip)
            if "认证成功" not in r2:
                return r2

            return ('warn', '检测到重复认证，该设备已被下线再重新上线')
        else:
            return r

    def webOffLine(self):
        headers = {
            'Upgrade-Insecure-Requests':
            '1',
            'Connection':
            'keep-alive',
            # 'Referer': r'http://1.1.1.1:8888/webauth.do?wlanacip=192.16.100.1&wlanacname=zax_hzstu&wlanuserip=10.51.68.157&mac=80:05:88:71:a9:85&vlan=0&url=http://1.1.1.1',
            'Content-Type':
            'application/x-www-form-urlencoded',
            # 'Origin': 'http://1.1.1.1:8888',
            'Accept-Encoding':
            'gzip, deflate',
            'Accept-Language':
            'en-US,en;q=0.5',
            'Accept':
            r'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            # 'Host':'1.1.1.1:8888',
            # 'User-Agent': ua,
        }
        session = requests.session()
        session.headers.update(headers)

        # 下线
        postData = r'hostIp=http%3A%2F%2F127.0.0.1%3A8080%2F&loginType=&auth_type=0&isBindMac1=0&pageid=1&templatetype=1&listbindmac=0&recordmac=0&isRemind=&loginTimes=&groupId=&distoken={distoken}&echostr=&url=http%3A%2F%2F1.1.1.1&isautoauth=&notice_pic_float=%2Fportal%2Fuploads%2Fpc%2Fhb_pay%2Fimages%2Frrs_bg.jpg&userId={userId}&other1=disconn&loginType='
        postData = postData.format(distoken=self.distoken, userId=self.userid)
        try:
            r = session.post(
                self.url +
                r'/webdisconn.do?wlanacip=192.16.100.1&wlanacname=zax_hzstu&wlanuserip=&mac=&vlan=0&act=LOGINSUCC&errorMsg=&url=http://1.1.1.1',
                headers=headers,
                data=postData)
        except requests.exceptions.RequestException as e:
            return ('err', '请求错误，请检查网络状态：' + str(e))

        if "下线成功" in r.text:
            return True
        elif "非法下线请求被拒绝" in r.text:
            return ('err', '非法下线请求被拒绝，可能软件出现bug，请提issuses反馈')
        else:
            return ('err', 'offLine未知错误，可能软件出现bug，请提issuses反馈')

    def quickAuthOffLine(self, ip: str):
        session = requests.session()

        # 下线
        getUrl = r'{url}/httpservice/appoffline.do?wlanacip=&wlanacname={wlanacname}&userId={userId}&passwd={passwd}&mac=&wlanuserip={ip}&distoken={distoken}'
        getUrl = getUrl.format(wlanacname=self.wlanacname,
                               distoken=self.distoken,
                               userId=self.userid,
                               passwd=self.passwd,
                               ip=ip,
                               url=self.url)

        try:
            r = session.get(getUrl)
            data = json.loads(r.text)
            message = data["message"]

        except requests.exceptions.RequestException as e:
            return ('err', '请求错误，请检查网络状态：' + str(e))
        except Exception as e:
            return ('err', 'json 解析错误：' + str(e))

        print("下线状态:", message)

        if "下线成功" in message:
            return True
        elif "下线失败" in message:
            return ('err', '非法下线请求被拒绝，可能软件出现bug，请提issuses反馈')
        else:
            return ('err', 'quickAuthOffLine未知错误，可能软件出现bug，请提issuses反馈')


if __name__ == '__main__':

    net = zkNet('202110000', '666666', wlanacname='zax_hzoffice')

    # 两种认证方式

    # 认证本机为手机
    r = net.autoWebAuth('phone')
    print(r)
    # 认证本机为 PC
    # r = net.webAuth('pc')
    # 使本机下线
    # net.webOffLine()

    # 指定 IP 认证为 PC
    # net.quickAuthShare("10.51.70.27")
    # 本机本机 IP 认证为 PC
    # net.quickAuthShare(net.getCurDevIp())
    # 指定IP下线
    # net.quickAuthOffLine(net.getCurDevIp())

zkNet.py
LingMessy/zkNet