无忧启动论坛

 找回密码
 注册
搜索
系统gho:最纯净好用系统下载站投放广告、加入VIP会员,请联系 微信:wuyouceo
查看: 791|回复: 53
打印 上一主题 下一主题

[原创] Todesk自动安装上线系统!

  [复制链接]
跳转到指定楼层
1#
发表于 17 小时前 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
直接上代码 自行修改编译 安装代码install_todesk.py
  1. import os
  2. import sys
  3. import subprocess
  4. import shutil
  5. import requests
  6. import time
  7. import winreg
  8. import psutil
  9. from pathlib import Path

  10. # 配置变量
  11. INSTALL_DIR = os.path.join(os.environ['ProgramFiles'], 'Todesk')
  12. DOWNLOAD_URL = 'http://down.jywangluo.cn:88/ToDesk/Todesk.7z'
  13. TOOLS_URL = 'http://down.jywangluo.cn:88/Tools/todesk.7z'
  14. # 日志文件路径
  15. LOG_FILE = os.path.join(os.environ['TEMP'], 'todesk_install.log')

  16. # 确保中文显示正常
  17. if sys.stdout is not None:
  18.     try:
  19.         sys.stdout.reconfigure(encoding='utf-8')
  20.     except AttributeError:
  21.         # 对于不支持reconfigure的Python版本或环境,忽略此错误
  22.         pass

  23. def log(message):
  24.     log_time = time.strftime('%Y-%m-%d %H:%M:%S')
  25.     log_message = f"[{log_time}] {message}"
  26.     # 输出到控制台
  27.     print(log_message)
  28.     # 写入到日志文件
  29.     try:
  30.         with open(LOG_FILE, 'a', encoding='utf-8') as f:
  31.             f.write(log_message + '\n')
  32.             f.flush()
  33.     except Exception as e:
  34.         print(f"无法写入日志文件: {e}")

  35. def stop_todesk_processes():
  36.     try:
  37.         log("开始停止ToDesk相关进程...")
  38.         
  39.         # 获取当前进程PID,避免终止自身
  40.         current_pid = os.getpid()
  41.         
  42.         # 使用psutil查找并终止ToDesk相关进程
  43.         processes_stopped = 0
  44.         for proc in psutil.process_iter(['pid', 'name']):
  45.             try:
  46.                 # 跳过当前进程
  47.                 if proc.info['pid'] == current_pid:
  48.                     continue
  49.                     
  50.                 proc_name = proc.info['name'].lower()
  51.                 # 只终止真正的ToDesk进程,排除安装程序自身
  52.                 if 'todesk' in proc_name and 'install' not in proc_name:
  53.                     log(f"发现ToDesk进程: {proc.info['name']} (PID: {proc.info['pid']})")
  54.                     proc.terminate()
  55.                     proc.wait(timeout=5)  # 等待进程终止
  56.                     log(f"已终止进程: {proc.info['name']} (PID: {proc.info['pid']})")
  57.                     processes_stopped += 1
  58.             except (psutil.NoSuchProcess, psutil.AccessDenied):
  59.                 # 进程已终止或无权限访问,继续处理其他进程
  60.                 continue
  61.             except psutil.TimeoutExpired:
  62.                 # 进程未在指定时间内终止,强制杀死
  63.                 log(f"进程 {proc.info['name']} 未正常终止,强制杀死")
  64.                 proc.kill()
  65.                 processes_stopped += 1
  66.         
  67.         # 停止ToDesk服务
  68.         try:
  69.             subprocess.run(['sc', 'stop', 'ToDesk_Service'], shell=True, capture_output=True, text=True)
  70.             log("已停止ToDesk服务")
  71.         except Exception as e:
  72.             log(f"停止ToDesk服务时出错: {e}")
  73.         
  74.         # 使用taskkill作为备用方案
  75.         try:
  76.             result = subprocess.run(['taskkill', '/f', '/im', 'ToDesk.exe', '/T'],
  77.                                   shell=True, capture_output=True, text=True)
  78.             if result.returncode == 0:
  79.                 log("taskkill命令成功执行")
  80.         except Exception as e:
  81.             log(f"taskkill执行出错: {e}")
  82.         
  83.         log(f"停止进程完成,共处理了 {processes_stopped} 个ToDesk相关进程")
  84.         
  85.     except Exception as e:
  86.         log(f"停止进程时出错: {e}")

  87. def get_todesk_client_id():

  88.     log("获取ToDesk机器码...")
  89.     max_attempts = 5
  90.     attempt = 0
  91.    
  92.     while attempt < max_attempts:
  93.         attempt += 1
  94.         try:
  95.             config_path = os.path.join(INSTALL_DIR, 'config.ini')
  96.             if os.path.exists(config_path):
  97.                 with open(config_path, 'r', encoding='utf-8') as f:
  98.                     content = f.read()
  99.                     for line in content.splitlines():
  100.                         if line.startswith('clientId='):
  101.                             client_id = line.split('=', 1)[1].strip()
  102.                             if client_id:
  103.                                 log(f"成功获取机器码: {client_id}")
  104.                                 return client_id
  105.                 log(f"尝试 {attempt}/{max_attempts}: 未在配置文件中找到有效的clientId")
  106.             else:
  107.                 log(f"尝试 {attempt}/{max_attempts}: 配置文件不存在: {config_path}")
  108.         except Exception as e:
  109.             log(f"尝试 {attempt}/{max_attempts}: 读取机器码时出错: {e}")
  110.         
  111.         if attempt < max_attempts:
  112.             log(f"等待3秒后重试...")
  113.             time.sleep(3)
  114.    
  115.     log("达到最大尝试次数,未能获取机器码")
  116.     return None

  117. def get_server_url():
  118.     """从配置文件读取服务器IP/域名,然后拼接成完整URL"""
  119.     # 配置文件路径 - 兼容开发和编译后的环境
  120.     if getattr(sys, 'frozen', False):
  121.         # 编译后的可执行文件环境
  122.         base_dir = os.path.dirname(os.path.abspath(sys.executable))
  123.     else:
  124.         # 开发环境
  125.         base_dir = os.path.dirname(os.path.abspath(__file__))
  126.     config_path = os.path.join(base_dir, 'config.ini')
  127.     default_server = 'localhost:7888'  # 默认服务器地址和端口
  128.     api_path = '/api/register'  # API路径部分
  129.    
  130.     # 如果配置文件不存在,创建默认配置文件
  131.     if not os.path.exists(config_path):
  132.         log(f"配置文件不存在,创建默认配置文件: {config_path}")
  133.         try:
  134.             with open(config_path, 'w', encoding='utf-8') as f:
  135.                 f.write('[ServerConfig]\n')
  136.                 f.write(f'server_host={default_server}\n')
  137.             log(f"默认配置文件创建成功")
  138.             server_host = default_server
  139.         except Exception as e:
  140.             log(f"创建配置文件失败: {e}")
  141.             server_host = default_server
  142.     else:
  143.         # 读取配置文件
  144.         try:
  145.             server_host = None
  146.             with open(config_path, 'r', encoding='utf-8') as f:
  147.                 for line in f:
  148.                     line = line.strip()
  149.                     if line.startswith('server_host='):
  150.                         server_host = line.split('=', 1)[1].strip()
  151.                         break
  152.                     # 向后兼容:如果配置文件仍使用旧的server_url格式
  153.                     elif line.startswith('server_url='):
  154.                         server_url = line.split('=', 1)[1].strip()
  155.                         # 从完整URL中提取host和port部分
  156.                         import re
  157.                         match = re.match(r'https?://([^/]+)', server_url)
  158.                         if match:
  159.                             server_host = match.group(1)
  160.                             log(f"检测到旧格式配置,提取服务器地址: {server_host}")
  161.                         break
  162.             
  163.             if not server_host:
  164.                 log("配置文件中未找到server_host,使用默认值")
  165.                 server_host = default_server
  166.         except Exception as e:
  167.             log(f"读取配置文件失败: {e}")
  168.             server_host = default_server
  169.    
  170.     # 构建完整URL
  171.     # 确保URL格式正确(添加http://前缀如果没有)
  172.     if not server_host.startswith(('http://', 'https://')):
  173.         server_url = f'http://{server_host}{api_path}'
  174.     else:
  175.         server_url = f'{server_host}{api_path}'
  176.    
  177.     log(f"服务器地址: {server_host}")
  178.     log(f"完整服务器URL: {server_url}")
  179.     return server_url

  180. def upload_client_id(client_id):
  181.     log(f"开始上传机器码到服务器: {client_id}")
  182.     max_retries = 3
  183.     retry_count = 0
  184.    
  185.     while retry_count < max_retries:
  186.         retry_count += 1
  187.         try:
  188.             # 从配置文件读取服务器URL
  189.             SERVER_URL = get_server_url()
  190.             
  191.             # 上传数据
  192.             data = {'client_id': client_id}
  193.             
  194.             # 发送请求
  195.             log(f"尝试 {retry_count}/{max_retries}: 发送请求到 {SERVER_URL}")
  196.             response = requests.post(
  197.                 SERVER_URL,
  198.                 json=data,
  199.                 timeout=30
  200.             )
  201.             
  202.             # 检查响应
  203.             if response.status_code == 200:
  204.                 try:
  205.                     result = response.json()
  206.                     if result.get('status') == 'success':
  207.                         log("机器码上传成功!")
  208.                         device_info = result.get('device_info', {})
  209.                         log(f"设备ID: {device_info.get('device_id')}")
  210.                         log(f"首次上线: {device_info.get('first_online')}")
  211.                         log(f"最后上线: {device_info.get('last_online')}")
  212.                         return True
  213.                     else:
  214.                         log(f"服务器返回错误: {result.get('message')}")
  215.                 except ValueError:
  216.                     log(f"无法解析服务器响应: {response.text}")
  217.             else:
  218.                 log(f"上传失败,HTTP状态码: {response.status_code}")
  219.                 log(f"响应内容: {response.text}")
  220.         except requests.exceptions.ConnectionError:
  221.             log("连接服务器失败,请检查服务器是否运行")
  222.         except requests.exceptions.Timeout:
  223.             log("连接服务器超时")
  224.         except Exception as e:
  225.             log(f"上传机器码时出错: {e}")
  226.         
  227.         if retry_count < max_retries:
  228.             log(f"等待5秒后重试...")
  229.             time.sleep(5)
  230.    
  231.     log("达到最大重试次数,上传失败")
  232.     return False

  233. def download_file(url, save_path):
  234.     """下载文件"""
  235.     log(f"下载文件: {url} -> {save_path}")
  236.     try:
  237.         response = requests.get(url, stream=True)
  238.         response.raise_for_status()
  239.         with open(save_path, 'wb') as f:
  240.             for chunk in response.iter_content(chunk_size=8192):
  241.                 if chunk:
  242.                     f.write(chunk)
  243.         log(f"文件下载成功: {save_path}")
  244.         return True
  245.     except Exception as e:
  246.         log(f"下载文件时出错: {e}")
  247.         return False

  248. def extract_7z(archive_path, extract_dir):
  249.     """使用py7zr库解压7z文件"""
  250.     log(f"解压文件: {archive_path} -> {extract_dir}")
  251.    
  252.     # 确保解压目录存在
  253.     os.makedirs(extract_dir, exist_ok=True)
  254.    
  255.     try:
  256.         # 优先使用py7zr库解压
  257.         try:
  258.             import py7zr
  259.             log("使用py7zr库解压...")
  260.             with py7zr.SevenZipFile(archive_path, mode='r') as z:
  261.                 z.extractall(extract_dir)
  262.             
  263.             # 验证解压结果
  264.             if os.listdir(extract_dir):
  265.                 log("文件解压成功")
  266.                 return True
  267.             else:
  268.                 log("解压目录为空,解压可能未完全成功")
  269.                 return False
  270.                
  271.         except ImportError:
  272.             log("py7zr库未安装,请先安装: pip install py7zr --only-binary :all:")
  273.             return False
  274.         except Exception as e:
  275.             log(f"使用py7zr解压时出错: {e}")
  276.             return False
  277.             
  278.     except Exception as e:
  279.         log(f"解压过程发生未预期错误: {e}")
  280.         return False

  281. def get_file_version(file_path):
  282.     """获取Windows可执行文件的版本信息"""
  283.     try:
  284.         import ctypes
  285.         from ctypes import wintypes
  286.         
  287.         # 定义所需的结构体
  288.         class VS_FIXEDFILEINFO(ctypes.Structure):
  289.             _fields_ = [
  290.                 ('dwSignature', wintypes.DWORD),
  291.                 ('dwStrucVersion', wintypes.DWORD),
  292.                 ('dwFileVersionMS', wintypes.DWORD),
  293.                 ('dwFileVersionLS', wintypes.DWORD),
  294.                 ('dwProductVersionMS', wintypes.DWORD),
  295.                 ('dwProductVersionLS', wintypes.DWORD),
  296.                 ('dwFileFlagsMask', wintypes.DWORD),
  297.                 ('dwFileFlags', wintypes.DWORD),
  298.                 ('dwFileOS', wintypes.DWORD),
  299.                 ('dwFileType', wintypes.DWORD),
  300.                 ('dwFileSubtype', wintypes.DWORD),
  301.                 ('dwFileDateMS', wintypes.DWORD),
  302.                 ('dwFileDateLS', wintypes.DWORD),
  303.             ]
  304.         
  305.         # 获取文件版本信息
  306.         size = ctypes.windll.version.GetFileVersionInfoSizeW(file_path, None)
  307.         if size == 0:
  308.             log(f"无法获取文件版本信息: {file_path}")
  309.             return "1.0.0.0"
  310.         
  311.         buffer = ctypes.create_string_buffer(size)
  312.         if not ctypes.windll.version.GetFileVersionInfoW(file_path, None, size, buffer):
  313.             log(f"获取文件版本信息失败: {file_path}")
  314.             return "1.0.0.0"
  315.         
  316.         # 解析版本信息
  317.         dwLen = wintypes.UINT()
  318.         lpData = ctypes.c_void_p()
  319.         if not ctypes.windll.version.VerQueryValueW(buffer, '\\', ctypes.byref(lpData), ctypes.byref(dwLen)):
  320.             log(f"解析版本信息失败: {file_path}")
  321.             return "1.0.0.0"
  322.         
  323.         vsinfo = VS_FIXEDFILEINFO.from_address(lpData.value)
  324.         
  325.         # 提取版本号
  326.         major = (vsinfo.dwFileVersionMS >> 16) & 0xFFFF
  327.         minor = vsinfo.dwFileVersionMS & 0xFFFF
  328.         build = (vsinfo.dwFileVersionLS >> 16) & 0xFFFF
  329.         revision = vsinfo.dwFileVersionLS & 0xFFFF
  330.         
  331.         return f"{major}.{minor}.{build}.{revision}"
  332.     except Exception as e:
  333.         log(f"获取文件版本时出错: {e}")
  334.         return "1.0.0.0"

  335. def configure_todesk():
  336.     """配置ToDesk"""
  337.     log("配置ToDesk...")
  338.     try:
  339.         # 下载配置文件(注意:虽然URL是.7z,但文件内容本身是INI格式)
  340.         temp_config = os.path.join(os.environ['TEMP'], 'config.ini')
  341.         # 初始化auth_pass_ex为空
  342.         auth_pass_ex = ''
  343.         
  344.         if download_file(TOOLS_URL, temp_config):
  345.             # 直接读取下载的文件作为INI(无需解压)
  346.             try:
  347.                 with open(temp_config, 'r', encoding='utf-8') as f:
  348.                     in_config_info = False
  349.                     for line in f:
  350.                         line = line.strip()
  351.                         # 检查是否进入ConfigInfo小节
  352.                         if line == '[ConfigInfo]':
  353.                             in_config_info = True
  354.                             continue
  355.                         # 检查是否离开ConfigInfo小节(进入其他小节)
  356.                         if line.startswith('[') and line.endswith(']') and line != '[ConfigInfo]':
  357.                             in_config_info = False
  358.                             continue
  359.                         # 只在ConfigInfo小节内查找authPassEx
  360.                         if in_config_info and line.startswith('authPassEx='):
  361.                             auth_pass_ex = line.split('=', 1)[1].strip()
  362.                             log(f"从配置文件ConfigInfo小节读取到authPassEx: {auth_pass_ex}")
  363.                             break
  364.                 if not auth_pass_ex:
  365.                     log("配置文件ConfigInfo小节中未找到authPassEx项")
  366.             except Exception as e:
  367.                 log(f"读取临时配置文件失败: {str(e)}")
  368.             
  369.             # 获取ToDesk.exe的版本信息
  370.             todesk_exe_path = os.path.join(INSTALL_DIR, 'ToDesk.exe')
  371.             version = "1.0.0.0"  # 默认版本号
  372.             if os.path.exists(todesk_exe_path):
  373.                 version = get_file_version(todesk_exe_path)
  374.                 log(f"获取到ToDesk.exe版本信息: {version}")
  375.             else:
  376.                 log("ToDesk.exe不存在,使用默认版本号")
  377.             
  378.             # 创建配置文件
  379.             config_path = os.path.join(INSTALL_DIR, 'config.ini')
  380.             with open(config_path, 'w', encoding='utf-8') as f:
  381.                 f.write('[ConfigInfo]\n')
  382.                 if auth_pass_ex:
  383.                     f.write(f"authPassEx={auth_pass_ex}\n")
  384.                     log("已将从下载的配置文件中读取的authPassEx写入配置")
  385.                 else:
  386.                     f.write(f"authPassEx=default_auth_key\n")
  387.                     log("未获取到有效的authPassEx,使用默认值")
  388.                 f.write("autoStart=1\n")
  389.                 f.write("autoupdate=1\n")
  390.                 f.write("AuthMode=1\n")
  391.                 f.write(f"Version={version}\n")
  392.                 f.write("autoLockScreen=0\n")
  393.                 f.write("filetranstip=0\n")
  394.                 f.write("PrivateScreenLockScreen=0\n")
  395.                 f.write("DisableHardCode=1\n")
  396.                 f.write("ResolutionConfig=0\n")
  397.                 f.write("isupdate=0\n")
  398.             
  399.             # 清理临时文件
  400.             if os.path.exists(temp_config):
  401.                 os.remove(temp_config)
  402.     except Exception as e:
  403.         log(f"配置ToDesk时出错: {e}")

  404. def write_registry():
  405.     """写入注册表信息"""
  406.     log("写入注册表信息...")
  407.     try:
  408.         # 创建或打开注册表项
  409.         key = winreg.CreateKeyEx(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\ToDesk',
  410.                                 0, winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY)
  411.         # 写入安装路径
  412.         winreg.SetValueEx(key, 'InstPath', 0, winreg.REG_SZ, INSTALL_DIR)
  413.         winreg.CloseKey(key)
  414.         
  415.         # 创建卸载信息
  416.         uninstall_key = winreg.CreateKeyEx(winreg.HKEY_LOCAL_MACHINE,
  417.                                          r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ToDesk',
  418.                                          0, winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY)
  419.         winreg.SetValueEx(uninstall_key, 'DisplayName', 0, winreg.REG_SZ, 'ToDesk')
  420.         winreg.SetValueEx(uninstall_key, 'UninstallString', 0, winreg.REG_SZ,
  421.                          os.path.join(INSTALL_DIR, 'uninst.exe'))
  422.         winreg.SetValueEx(uninstall_key, 'DisplayIcon', 0, winreg.REG_SZ,
  423.                          os.path.join(INSTALL_DIR, 'ToDesk.exe'))
  424.         winreg.SetValueEx(uninstall_key, 'Publisher', 0, winreg.REG_SZ,
  425.                          'Hainan YouQu Technology Co., Ltd')
  426.         winreg.SetValueEx(uninstall_key, 'DisplayVersion', 0, winreg.REG_SZ, '1.0.0.0')
  427.         winreg.SetValueEx(uninstall_key, 'todeskpath', 0, winreg.REG_SZ, INSTALL_DIR)
  428.         winreg.SetValueEx(uninstall_key, 'developer', 0, winreg.REG_SZ, 'Wanli@todesk.com')
  429.         winreg.SetValueEx(uninstall_key, 'website', 0, winreg.REG_SZ, 'https://www.todesk.com')
  430.         winreg.CloseKey(uninstall_key)
  431.     except Exception as e:
  432.         log(f"写入注册表时出错: {e}")

  433. def create_shortcut():
  434.     """在公共桌面目录创建快捷方式"""
  435.     log("在公共桌面目录创建快捷方式...")
  436.     try:
  437.         # 为所有用户创建快捷方式
  438.         all_users_desktop = os.path.join(os.environ['PUBLIC'], 'Desktop')
  439.         all_users_shortcut = os.path.join(all_users_desktop, 'Todesk.lnk')
  440.         if os.path.exists(all_users_shortcut):
  441.             os.remove(all_users_shortcut)
  442.         
  443.         # 使用WScript.Shell创建快捷方式
  444.         import win32com.client
  445.         shell = win32com.client.Dispatch("WScript.Shell")
  446.         all_users_sc = shell.CreateShortCut(all_users_shortcut)
  447.         all_users_sc.Targetpath = os.path.join(INSTALL_DIR, "ToDesk.exe")
  448.         all_users_sc.WorkingDirectory = INSTALL_DIR
  449.         all_users_sc.IconLocation = os.path.join(INSTALL_DIR, "ToDesk.exe")
  450.         all_users_sc.save()
  451.         
  452.     except ImportError:
  453.         log("未安装pywin32,跳过创建快捷方式")
  454.     except Exception as e:
  455.         log(f"创建快捷方式时出错: {e}")

  456. def delete_privatedata_registry():
  457.     # 定义注册表路径、键名和访问权限
  458.     REG_PATH = r'SOFTWARE\ToDesk'
  459.     VALUE_NAME = 'PrivateData'
  460.     KEY_64BIT = winreg.KEY_WOW64_64KEY
  461.     WRITE_ACCESS = winreg.KEY_SET_VALUE | KEY_64BIT
  462.    
  463.     # 检查并删除PrivateData值
  464.     try:
  465.         with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as hklm:
  466.             try:
  467.                 # 尝试删除值
  468.                 with winreg.OpenKey(hklm, REG_PATH, 0, WRITE_ACCESS) as write_key:
  469.                     winreg.DeleteValue(write_key, VALUE_NAME)
  470.                     return True
  471.             except FileNotFoundError:
  472.                 # 值不存在,视为删除成功
  473.                 return True
  474.     except Exception:
  475.         return False



  476. def start_todesk():
  477.     """启动ToDesk"""
  478.     log("启动ToDesk...")
  479.    
  480.     # 在启动ToDesk前删除PrivateData注册表项
  481.     delete_privatedata_registry()
  482.    
  483.     try:
  484.         todesk_exe = os.path.join(INSTALL_DIR, 'ToDesk.exe')
  485.         if os.path.exists(todesk_exe):
  486.             # 使用管理员权限启动ToDesk
  487.             import ctypes
  488.             result = ctypes.windll.shell32.ShellExecuteW(
  489.                 None, "runas", todesk_exe, "", INSTALL_DIR, 1
  490.             )
  491.             if result > 32:
  492.                 log("ToDesk启动成功,正在等待配置文件生成...")
  493.                 # 等待一段时间确保ToDesk完全启动
  494.                 time.sleep(8)
  495.                 return True
  496.             else:
  497.                 log(f"ToDesk启动失败,返回代码: {result}")
  498.                 # 尝试普通方式启动
  499.                 log("尝试以普通方式启动ToDesk...")
  500.                 process = subprocess.Popen([todesk_exe], cwd=INSTALL_DIR)
  501.                 log("ToDesk启动成功,正在等待配置文件生成...")
  502.                 time.sleep(8)
  503.                 return True
  504.         else:
  505.             log(f"ToDesk可执行文件不存在: {todesk_exe}")
  506.     except Exception as e:
  507.         log(f"启动ToDesk时出错: {e}")
  508.     return False

  509. def main():
  510.     """主函数"""
  511.     # 清除旧的日志文件
  512.     if os.path.exists(LOG_FILE):
  513.         try:
  514.             os.remove(LOG_FILE)
  515.         except Exception as e:
  516.             print(f"无法删除旧日志文件: {e}")
  517.    
  518.     log("开始ToDesk自动安装...")
  519.     log(f"日志文件已创建在: {LOG_FILE}")
  520.    
  521.     # 导入ctypes模块用于管理员权限检查
  522.     import ctypes
  523.     # 以管理员权限运行检查
  524.     if not ctypes.windll.shell32.IsUserAnAdmin():
  525.         log("请以管理员权限运行此脚本")
  526.         # 尝试以管理员权限重启
  527.         ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
  528.         sys.exit(1)
  529.    
  530.     # 停止现有进程
  531.     stop_todesk_processes()
  532.    
  533.     # 跳过备份配置步骤
  534.    
  535.     # 创建安装目录
  536.     if os.path.exists(INSTALL_DIR):
  537.         log(f"删除现有安装目录: {INSTALL_DIR}")
  538.         shutil.rmtree(INSTALL_DIR, ignore_errors=True)
  539.     os.makedirs(INSTALL_DIR, exist_ok=True)
  540.    
  541.     # 下载安装包
  542.     temp_7z = os.path.join(os.environ['TEMP'], 'Todesk.7z')
  543.     if download_file(DOWNLOAD_URL, temp_7z):
  544.         # 解压安装包
  545.         if extract_7z(temp_7z, INSTALL_DIR):
  546.             # 清理下载的安装包
  547.             if os.path.exists(temp_7z):
  548.                 os.remove(temp_7z)
  549.             
  550.             # 配置ToDesk
  551.             configure_todesk()
  552.             
  553.             # 写入注册表
  554.             write_registry()
  555.             
  556.             # 创建快捷方式
  557.             create_shortcut()
  558.             
  559.             # 启动ToDesk并等待配置文件生成
  560.             todesk_started = start_todesk()
  561.             
  562.             # 如果ToDesk启动失败,尝试手动启动
  563.             if not todesk_started:
  564.                 log("再次尝试启动ToDesk服务...")
  565.                 try:
  566.                     subprocess.run(['sc', 'start', 'ToDesk_Service'], shell=True, capture_output=True, text=True)
  567.                     log("ToDesk服务启动中,等待配置文件生成...")
  568.                     time.sleep(10)
  569.                 except Exception as e:
  570.                     log(f"启动ToDesk服务失败: {e}")
  571.             
  572.             # 获取机器码
  573.             client_id = get_todesk_client_id()
  574.             if client_id:
  575.                 # 尝试上传机器码到服务器
  576.                 upload_client_id(client_id)
  577.             else:
  578.                 log("未能获取有效的机器码")
  579.             
  580.             log("ToDesk自动安装完成!")
  581.         else:
  582.             log("安装失败:解压文件失败")
  583.     else:
  584.         log("安装失败:下载文件失败")

  585. if __name__ == "__main__":
  586.     # 导入ctypes模块用于管理员权限检查
  587.     import ctypes
  588.     main()
复制代码
服务端代码
server.py
  1. from flask import Flask, request, jsonify
  2. import datetime
  3. import json
  4. import os
  5. import sys
  6. import uuid
  7. import tkinter as tk
  8. from tkinter import ttk
  9. import threading
  10. import time
  11. import tempfile

  12. app = Flask(__name__)

  13. # 存储数据的文件路径
  14. DATA_FILE = 'todesk_devices.json'

  15. # 全局变量用于UI刷新通知
  16. gui_refresh_event = None

  17. # 确保数据文件存在
  18. if not os.path.exists(DATA_FILE):
  19.     with open(DATA_FILE, 'w', encoding='utf-8') as f:
  20.         json.dump({}, f, ensure_ascii=False, indent=2)

  21. def load_devices():
  22.     """从文件加载设备数据"""
  23.     try:
  24.         with open(DATA_FILE, 'r', encoding='utf-8') as f:
  25.             return json.load(f)
  26.     except Exception as e:
  27.         print(f"加载设备数据失败: {e}")
  28.         return {}

  29. def save_devices(devices):
  30.     """保存设备数据到文件"""
  31.     try:
  32.         with open(DATA_FILE, 'w', encoding='utf-8') as f:
  33.             json.dump(devices, f, ensure_ascii=False, indent=2)
  34.         return True
  35.     except Exception as e:
  36.         print(f"保存设备数据失败: {e}")
  37.         return False

  38. @app.route('/api/register', methods=['POST'])
  39. def register_device():
  40.     """注册设备,接收机器码并记录上线时间"""
  41.     try:
  42.         data = request.json
  43.         if not data or 'client_id' not in data:
  44.             return jsonify({'status': 'error', 'message': '缺少client_id参数'}), 400
  45.         
  46.         client_id = data['client_id']
  47.         devices = load_devices()
  48.         
  49.         # 获取当前时间
  50.         now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  51.         
  52.         # 如果设备已存在,更新上线时间;否则创建新记录
  53.         if client_id in devices:
  54.             devices[client_id]['last_online'] = now
  55.             print(f"设备 {client_id} 再次上线,时间: {now}")
  56.         else:
  57.             # 生成精简的设备ID(使用时间戳+随机数)
  58.             import random
  59.             timestamp = int(time.time() * 1000)
  60.             random_num = random.randint(1000, 9999)
  61.             device_id = f"{timestamp}{random_num}"
  62.             devices[client_id] = {
  63.                 'device_id': device_id,
  64.                 'client_id': client_id,
  65.                 'first_online': now,
  66.                 'last_online': now,
  67.                 'ip_address': request.remote_addr
  68.             }
  69.             print(f"新设备注册: {client_id}, 时间: {now}, IP: {request.remote_addr}")
  70.             
  71.             # 有新设备注册时触发UI刷新
  72.             if gui_refresh_event:
  73.                 gui_refresh_event.set()
  74.         
  75.         # 保存数据
  76.         if save_devices(devices):
  77.             return jsonify({
  78.                 'status': 'success',
  79.                 'message': '设备注册成功',
  80.                 'device_info': devices[client_id]
  81.             }), 200
  82.         else:
  83.             return jsonify({'status': 'error', 'message': '保存数据失败'}), 500
  84.             
  85.     except Exception as e:
  86.         print(f"处理注册请求时出错: {e}")
  87.         return jsonify({'status': 'error', 'message': f'服务器内部错误: {str(e)}'}), 500





  88. @app.route('/api/device/<client_id>', methods=['DELETE'])
  89. def delete_device(client_id):
  90.     """删除设备记录"""
  91.     try:
  92.         devices = load_devices()
  93.         if client_id in devices:
  94.             del devices[client_id]
  95.             if save_devices(devices):
  96.                 print(f"设备 {client_id} 已删除")
  97.                 return jsonify({'status': 'success', 'message': '设备已删除'}), 200
  98.             else:
  99.                 return jsonify({'status': 'error', 'message': '保存数据失败'}), 500
  100.         else:
  101.             return jsonify({'status': 'error', 'message': '设备不存在'}), 404
  102.     except Exception as e:
  103.         print(f"删除设备时出错: {e}")
  104.         return jsonify({'status': 'error', 'message': f'服务器内部错误: {str(e)}'}), 500

  105. @app.route('/', methods=['GET'])
  106. def index():
  107.     """首页"""
  108.     return jsonify({
  109.         'message': 'ToDesk 自动上线系统服务器',
  110.         'version': '1.0.0',
  111.         'endpoints': {
  112.             'register': '/api/register (POST)',
  113.             'devices': '/api/devices (GET)',
  114.             'device': '/api/device/<client_id> (GET, DELETE)'
  115.         }
  116.     })

  117. class DeviceGUI:
  118.     def __init__(self, root):
  119.         self.root = root
  120.         self.root.title("ToDesk设备管理系统")
  121.         self.root.geometry("800x600")
  122.         
  123.         # 获取屏幕尺寸
  124.         screen_width = root.winfo_screenwidth()
  125.         screen_height = root.winfo_screenheight()
  126.         
  127.         # 计算窗口居中位置,并向上偏移80像素
  128.         x = (screen_width - 800) // 2  # 800是窗口宽度
  129.         y = (screen_height - 600) // 2 - 80  # 600是窗口高度,向上偏移80像素
  130.         
  131.         # 确保Y坐标不为负数
  132.         if y < 0:
  133.             y = 0
  134.         
  135.         # 设置窗口位置
  136.         self.root.geometry(f"800x600+{x}+{y}")
  137.         
  138.         # 创建事件用于UI刷新通知
  139.         self.refresh_event = threading.Event()
  140.         global gui_refresh_event
  141.         gui_refresh_event = self.refresh_event
  142.         
  143.         # 启动事件监听线程
  144.         self.start_event_listener()
  145.         
  146.         # 创建标题
  147.         title_label = tk.Label(root, text="ToDesk设备管理系统", font=("微软雅黑", 16, "bold"))
  148.         title_label.pack(pady=10)
  149.         
  150.         # 创建操作按钮框架
  151.         button_frame = tk.Frame(root)
  152.         button_frame.pack(pady=5)
  153.         
  154.         # 创建刷新按钮
  155.         refresh_button = tk.Button(button_frame, text="刷新设备列表", command=self.refresh_devices)
  156.         refresh_button.pack(side=tk.LEFT, padx=10)
  157.         
  158.         # 创建清理按钮
  159.         clean_button = tk.Button(button_frame, text="清理离线设备", command=self.clean_offline_devices, bg="#ffcccc")
  160.         clean_button.pack(side=tk.LEFT, padx=10)
  161.         
  162.         # 创建统计信息框架
  163.         stats_frame = tk.Frame(root)
  164.         stats_frame.pack(fill=tk.X, padx=10, pady=5)
  165.         
  166.         self.total_label = tk.Label(stats_frame, text="总设备数: 0", font=("微软雅黑", 10))
  167.         self.total_label.pack(side=tk.LEFT, padx=10)
  168.         
  169.         self.online_label = tk.Label(stats_frame, text="在线设备数: 0", font=("微软雅黑", 10))
  170.         self.online_label.pack(side=tk.LEFT, padx=10)
  171.         
  172.         # 创建设备列表表格
  173.         columns = ("device_id", "client_id", "first_online", "last_online", "ip_address", "status")
  174.         self.tree = ttk.Treeview(root, columns=columns, show="headings")
  175.         
  176.         # 设置列标题和宽度
  177.         self.tree.heading("device_id", text="设备ID")
  178.         self.tree.heading("client_id", text="机器码")
  179.         self.tree.heading("first_online", text="首次上线时间")
  180.         self.tree.heading("last_online", text="最后上线时间")
  181.         self.tree.heading("ip_address", text="IP地址")
  182.         self.tree.heading("status", text="状态")
  183.         
  184.         self.tree.column("device_id", width=100)
  185.         self.tree.column("client_id", width=150)
  186.         self.tree.column("first_online", width=120)
  187.         self.tree.column("last_online", width=120)
  188.         self.tree.column("ip_address", width=100)
  189.         self.tree.column("status", width=80)
  190.         
  191.         # 添加滚动条
  192.         scrollbar = ttk.Scrollbar(root, orient=tk.VERTICAL, command=self.tree.yview)
  193.         self.tree.configure(yscroll=scrollbar.set)
  194.         
  195.         # 放置表格和滚动条
  196.         scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
  197.         self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
  198.         
  199.         # 创建日志文本框
  200.         log_label = tk.Label(root, text="系统日志:", font=("微软雅黑", 10, "bold"))
  201.         log_label.pack(anchor=tk.W, padx=10, pady=5)
  202.         
  203.         self.log_text = tk.Text(root, height=10, wrap=tk.WORD)
  204.         self.log_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
  205.         self.log_text.config(state=tk.DISABLED)
  206.         
  207.         # 初始加载设备
  208.         self.refresh_devices()
  209.         
  210.         # 定期刷新设备列表(每30秒)
  211.         self.root.after(30000, self.auto_refresh)
  212.    
  213.     def start_event_listener(self):
  214.         """启动事件监听线程"""
  215.         def event_listener():
  216.             while True:
  217.                 # 等待刷新事件
  218.                 self.refresh_event.wait()
  219.                 # 清除事件状态
  220.                 self.refresh_event.clear()
  221.                 # 在UI线程中执行刷新
  222.                 self.root.after(0, self.refresh_devices)
  223.                 self.log_message("检测到新设备注册,自动刷新设备列表")
  224.         
  225.         # 启动后台线程
  226.         listener_thread = threading.Thread(target=event_listener, daemon=True)
  227.         listener_thread.start()
  228.    
  229.     def log_message(self, message):
  230.         """添加日志消息到文本框"""
  231.         self.log_text.config(state=tk.NORMAL)
  232.         timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  233.         self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
  234.         self.log_text.see(tk.END)
  235.         self.log_text.config(state=tk.DISABLED)
  236.    
  237.     def is_device_online(self, last_online_str):
  238.         """判断设备是否在线(2小时内有活动)"""
  239.         try:
  240.             last_online = datetime.datetime.strptime(last_online_str, '%Y-%m-%d %H:%M:%S')
  241.             now = datetime.datetime.now()
  242.             # 计算时间差
  243.             delta = now - last_online
  244.             # 如果时间差小于2小时,则认为在线
  245.             return delta.total_seconds() < 7200  # 7200秒 = 2小时
  246.         except Exception as e:
  247.             self.log_message(f"检查设备在线状态时出错: {e}")
  248.             return False
  249.    
  250.     def refresh_devices(self):
  251.         """刷新设备列表"""
  252.         try:
  253.             # 清空现有数据
  254.             for item in self.tree.get_children():
  255.                 self.tree.delete(item)
  256.             
  257.             # 加载设备数据
  258.             devices = load_devices()
  259.             total_count = len(devices)
  260.             online_count = 0
  261.             
  262.             # 更新统计信息
  263.             self.total_label.config(text=f"总设备数: {total_count}")
  264.             
  265.             # 添加设备到表格
  266.             for client_id, device_info in devices.items():
  267.                 is_online = self.is_device_online(device_info['last_online'])
  268.                 if is_online:
  269.                     online_count += 1
  270.                     status_text = "在线"
  271.                     status_color = "green"
  272.                 else:
  273.                     status_text = "离线"
  274.                     status_color = "red"
  275.                
  276.                 # 添加行数据
  277.                 self.tree.insert("", tk.END, values=(
  278.                     device_info['device_id'],
  279.                     client_id,
  280.                     device_info['first_online'],
  281.                     device_info['last_online'],
  282.                     device_info['ip_address'],
  283.                     status_text
  284.                 ))
  285.                
  286.                 # 设置状态单元格颜色
  287.                 item_id = self.tree.get_children()[-1]
  288.                 self.tree.tag_configure(status_color, foreground=status_color)
  289.                 self.tree.item(item_id, tags=(status_color,))
  290.             
  291.             # 更新在线设备数量
  292.             self.online_label.config(text=f"在线设备数: {online_count}")
  293.             self.log_message("设备列表已刷新")
  294.             
  295.         except Exception as e:
  296.             self.log_message(f"刷新设备列表时出错: {e}")
  297.    
  298.     def auto_refresh(self):
  299.         """自动刷新设备列表"""
  300.         self.refresh_devices()
  301.         # 继续定时刷新
  302.         self.root.after(30000, self.auto_refresh)
  303.    
  304.     def clean_offline_devices(self):
  305.         """手动清理离线设备"""
  306.         try:
  307.             self.log_message("开始清理2小时未上线的设备...")
  308.             deleted_count, deleted_devices = clean_inactive_devices()
  309.             
  310.             if deleted_count > 0:
  311.                 self.log_message(f"成功清理 {deleted_count} 个离线设备")
  312.                 # 刷新设备列表以显示最新状态
  313.                 self.refresh_devices()
  314.             else:
  315.                 self.log_message("没有需要清理的离线设备")
  316.                
  317.         except Exception as e:
  318.             self.log_message(f"清理离线设备时出错: {e}")

  319. def run_server():
  320.     """在单独线程中运行Flask服务器"""
  321.     print("ToDesk自动上线系统服务器启动中...")
  322.     print(f"数据将保存到: {os.path.abspath(DATA_FILE)}")
  323.     print("API接口:")
  324.     print("  POST /api/register - 注册设备,提交client_id")
  325.     print("  GET  /api/devices - 获取所有设备列表")
  326.     print("  GET  /api/device/<client_id> - 获取特定设备信息")
  327.     print("  DELETE /api/device/<client_id> - 删除设备记录")
  328.     print("  POST /api/clean - 清理离线设备")
  329.     print("\n服务器启动在 http://0.0.0.0:7888")
  330.    
  331.     # 在生产环境中,应该使用更安全的方式启动Flask应用
  332.     # 这里为了方便测试,不使用debug模式
  333.     app.run(host='0.0.0.0', port=7888, debug=False, use_reloader=False)

  334. # 定时清理任务
  335. def scheduled_cleanup():
  336.     """定时执行清理任务的线程函数"""
  337.     while True:
  338.         print("执行定时清理任务...")
  339.         deleted_count, _ = clean_inactive_devices()
  340.         if deleted_count > 0:
  341.             print(f"定时清理完成,共清理 {deleted_count} 个离线设备")
  342.         # 每小时执行一次清理
  343.         time.sleep(3600)  # 3600秒 = 1小时

  344. # 添加设备在线状态检查的帮助函数,供API使用
  345. def get_device_status(device_info):
  346.     """获取设备状态"""
  347.     try:
  348.         last_online = datetime.datetime.strptime(device_info['last_online'], '%Y-%m-%d %H:%M:%S')
  349.         now = datetime.datetime.now()
  350.         delta = now - last_online
  351.         return 'online' if delta.total_seconds() < 7200 else 'offline'
  352.     except:
  353.         return 'unknown'

  354. def clean_inactive_devices():
  355.     """清理超过2小时未上线的设备数据"""
  356.     try:
  357.         devices = load_devices()
  358.         now = datetime.datetime.now()
  359.         devices_to_delete = []
  360.         
  361.         # 找出超过2小时未上线的设备
  362.         for client_id, device_info in devices.items():
  363.             try:
  364.                 last_online = datetime.datetime.strptime(device_info['last_online'], '%Y-%m-%d %H:%M:%S')
  365.                 delta = now - last_online
  366.                 # 如果时间差大于等于2小时,则标记为删除
  367.                 if delta.total_seconds() >= 7200:  # 7200秒 = 2小时
  368.                     devices_to_delete.append(client_id)
  369.             except Exception as e:
  370.                 print(f"检查设备 {client_id} 时间时出错: {e}")
  371.         
  372.         # 删除标记的设备
  373.         deleted_count = 0
  374.         for client_id in devices_to_delete:
  375.             if client_id in devices:
  376.                 del devices[client_id]
  377.                 deleted_count += 1
  378.                 print(f"已清理离线设备: {client_id}")
  379.         
  380.         # 保存更新后的数据
  381.         if devices_to_delete:
  382.             save_devices(devices)
  383.             
  384.         return deleted_count, devices_to_delete
  385.     except Exception as e:
  386.         print(f"清理离线设备时出错: {e}")
  387.         return 0, []

  388. # 更新API返回,添加设备状态信息
  389. @app.route('/api/devices', methods=['GET'])
  390. def get_devices():
  391.     """获取所有已注册设备列表"""
  392.     try:
  393.         devices = load_devices()
  394.         devices_with_status = []
  395.         for device_info in devices.values():
  396.             device_with_status = device_info.copy()
  397.             device_with_status['status'] = get_device_status(device_info)
  398.             devices_with_status.append(device_with_status)
  399.         
  400.         return jsonify({
  401.             'status': 'success',
  402.             'total': len(devices),
  403.             'online': len([d for d in devices_with_status if d['status'] == 'online']),
  404.             'devices': devices_with_status
  405.         }), 200
  406.     except Exception as e:
  407.         print(f"获取设备列表时出错: {e}")
  408.         return jsonify({'status': 'error', 'message': f'服务器内部错误: {str(e)}'}), 500

  409. # 添加清理设备的API端点
  410. @app.route('/api/clean', methods=['POST'])
  411. def clean_devices():
  412.     """通过API清理离线设备"""
  413.     try:
  414.         deleted_count, deleted_devices = clean_inactive_devices()
  415.         return jsonify({
  416.             'status': 'success',
  417.             'message': f'已清理 {deleted_count} 个离线设备',
  418.             'deleted_devices': deleted_devices
  419.         }), 200
  420.     except Exception as e:
  421.         print(f"通过API清理设备时出错: {e}")
  422.         return jsonify({'status': 'error', 'message': f'服务器内部错误: {str(e)}'}), 500

  423. # 更新单个设备API,添加状态信息
  424. @app.route('/api/device/<client_id>', methods=['GET'])
  425. def get_device(client_id):
  426.     """获取特定设备的信息"""
  427.     try:
  428.         devices = load_devices()
  429.         if client_id in devices:
  430.             device_info = devices[client_id].copy()
  431.             device_info['status'] = get_device_status(devices[client_id])
  432.             return jsonify({
  433.                 'status': 'success',
  434.                 'device_info': device_info
  435.             }), 200
  436.         else:
  437.             return jsonify({'status': 'error', 'message': '设备不存在'}), 404
  438.     except Exception as e:
  439.         print(f"获取设备信息时出错: {e}")
  440.         return jsonify({'status': 'error', 'message': f'服务器内部错误: {str(e)}'}), 500

  441. if __name__ == '__main__':
  442.     # 在单独的线程中运行Flask服务器
  443.     server_thread = threading.Thread(target=run_server, daemon=True)
  444.     server_thread.start()
  445.    
  446.     # 启动定时清理线程
  447.     cleanup_thread = threading.Thread(target=scheduled_cleanup, daemon=True)
  448.     cleanup_thread.start()
  449.     print("定时清理任务已启动,每小时执行一次")
  450.    
  451.     # 启动GUI
  452.     root = tk.Tk()
  453.     # 先隐藏窗口,避免定位过程中的闪现
  454.     root.withdraw()
  455.    
  456.     # 设置窗口图标
  457.     try:
  458.         # 处理打包后的情况和开发环境
  459.         if hasattr(sys, '_MEIPASS'):
  460.             # 当使用PyInstaller打包后,图标文件会在临时目录中
  461.             icon_path = os.path.join(sys._MEIPASS, 'Server.ico')
  462.         else:
  463.             # 开发环境中,图标文件在当前目录
  464.             icon_path = 'Server.ico'
  465.         
  466.         # 检查图标文件是否存在并设置
  467.         if os.path.exists(icon_path):
  468.             root.iconbitmap(icon_path)
  469.             print(f"成功加载图标: {icon_path}")
  470.         else:
  471.             print(f"警告: 图标文件不存在: {icon_path}")
  472.     except Exception as e:
  473.         print(f"设置窗口图标时出错: {e}")
  474.    
  475.     app_gui = DeviceGUI(root)
  476.     # 所有设置完成后显示窗口
  477.     root.deiconify()
  478.    
  479.     # 设置窗口关闭时的处理
  480.     def on_closing():
  481.         print("关闭服务器和GUI...")
  482.         root.destroy()
  483.    
  484.     root.protocol("WM_DELETE_WINDOW", on_closing)
  485.    
  486.     # 启动GUI主循环
  487.     root.mainloop()
复制代码







2#
发表于 17 小时前 | 只看该作者
感谢分享
回复

使用道具 举报

3#
发表于 16 小时前 | 只看该作者
感谢分享
回复

使用道具 举报

4#
发表于 16 小时前 | 只看该作者
感谢分享!
回复

使用道具 举报

5#
发表于 15 小时前 | 只看该作者
谢谢分享
回复

使用道具 举报

6#
发表于 15 小时前 | 只看该作者
这个TOXX现在各种难用
回复

使用道具 举报

7#
发表于 15 小时前 | 只看该作者
谢谢楼主分享!
回复

使用道具 举报

8#
发表于 15 小时前 | 只看该作者
纯支持~
回复

使用道具 举报

9#
发表于 15 小时前 | 只看该作者
感谢分享!
回复

使用道具 举报

10#
发表于 15 小时前 | 只看该作者
支持原创
回复

使用道具 举报

11#
发表于 15 小时前 | 只看该作者
这个不错,支持
回复

使用道具 举报

12#
发表于 15 小时前 | 只看该作者
谢谢分享
回复

使用道具 举报

13#
发表于 15 小时前 | 只看该作者
感谢分享
回复

使用道具 举报

14#
发表于 15 小时前 | 只看该作者
感谢分享
回复

使用道具 举报

15#
发表于 15 小时前 | 只看该作者
好。。。。。。。。。。。。。。
回复

使用道具 举报

16#
发表于 15 小时前 | 只看该作者
好。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
回复

使用道具 举报

17#
发表于 15 小时前 | 只看该作者
好。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

点评

恶意刷帖吗  详情 回复 发表于 14 小时前
三连冠  详情 回复 发表于 14 小时前
回复

使用道具 举报

18#
发表于 15 小时前 | 只看该作者
感谢分享,支持
回复

使用道具 举报

19#
发表于 15 小时前 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

20#
发表于 14 小时前 | 只看该作者
感谢分享
回复

使用道具 举报

21#
发表于 14 小时前 | 只看该作者
自动上线,自动安装?
回复

使用道具 举报

22#
发表于 14 小时前 | 只看该作者
感谢
回复

使用道具 举报

23#
发表于 14 小时前 | 只看该作者
cncecpcy 发表于 2025-11-25 08:34
好。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 ...

三连冠

3.png (21.61 KB, 下载次数: 6)

3.png
回复

使用道具 举报

24#
发表于 14 小时前 | 只看该作者
真长啊这代码1
回复

使用道具 举报

25#
发表于 14 小时前 | 只看该作者
完全看不懂  这是干嘛的
回复

使用道具 举报

26#
发表于 14 小时前 | 只看该作者
谢谢分享   
回复

使用道具 举报

27#
发表于 14 小时前 | 只看该作者
感谢分享!支持原创
回复

使用道具 举报

28#
 楼主| 发表于 14 小时前 | 只看该作者
cncecpcy 发表于 2025-11-25 08:34
好。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 ...

恶意刷帖吗
回复

使用道具 举报

29#
发表于 14 小时前 | 只看该作者
感谢分享!
回复

使用道具 举报

30#
发表于 13 小时前 | 只看该作者
具体怎么用?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|捐助支持|无忧启动 ( 闽ICP备05002490号-1 )

闽公网安备 35020302032614号

GMT+8, 2025-11-25 23:39

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表