2026年1月

目录帖:

PS:看完的佬友可以发条评论,最近几章的阅读量越来越少,是行文出了问题或是难以理解?还是单纯的帖子被淹没了?

============ 以下正文 ============

驱动的安装

在这里只讲 Ubuntu 和 Windows 的。首先说 Windows,大家应该都非常熟悉了,官网下载驱动包一键安装就行。

对于 Ubuntu 来说,大部分情况下建议采用如下方式安装驱动,而非官网下载.run 文件安装驱动:

  • /etc/modprobe.d/blacklist.conf 的末尾追加 blacklist nouveau 并执行 sudo update-initramfs -u && reboot 以禁用 nouveau

  • 执行 ubuntu-drivers devices 以查询推荐驱动。如:

    (base) root@ubuntu:~# ubuntu-drivers devices
    == /sys/devices/pci0000:c0/0000:c0:01.1/0000:c1:00.0 ==
    modalias : pci:v000010DEd00002684sv000010DEsd000016F3bc03sc00i00
    vendor   : NVIDIA Corporation
    model    : AD102 [GeForce RTX 4090]
    driver   : nvidia-driver-535 - distro non-free
    driver   : nvidia-driver-570-server-open - distro non-free
    driver   : nvidia-driver-580-open - distro non-free
    driver   : nvidia-driver-580-server - distro non-free
    driver   : nvidia-driver-535-server - distro non-free
    driver   : nvidia-driver-570 - distro non-free
    driver   : nvidia-driver-580 - distro non-free recommended
    driver   : nvidia-driver-535-open - distro non-free
    driver   : nvidia-driver-580-server-open - distro non-free
    driver   : nvidia-driver-570-open - distro non-free
    driver   : nvidia-driver-535-server-open - distro non-free
    driver   : nvidia-driver-570-server - distro non-free
    driver   : xserver-xorg-video-nouveau - distro free builtin
    

    可以看到目前推荐的驱动版本是 580。那么执行 sudo apt install nvidia-driver-580 -y 即可安装。

注意,驱动程序和 CUDA 版本的关系很简单:显卡驱动的版本必须大于等于 CUDA Toolkit 所要求的最低版本。

nvidia-smi

看懂 nvidia-smi

安装完驱动以后,执行 nvidia-smi 来检查一下驱动是否已经正确安装:

(base) root@ubuntu:~# nvidia-smi
Wed Jan 14 02:22:46 2026       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05              Driver Version: 580.95.05      CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4090        On  |   00000000:81:00.0 Off |                  Off |
| 67%   25C    P8             20W /  300W |       1MiB /  49140MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA GeForce RTX 4090        On  |   00000000:82:00.0 Off |                  Off |
| 30%   25C    P8             22W /  300W |       1MiB /  49140MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   2  NVIDIA GeForce RTX 4090        On  |   00000000:C1:00.0 Off |                  Off |
| 30%   28C    P8             25W /  300W |       1MiB /  49140MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   3  NVIDIA GeForce RTX 4090        On  |   00000000:C2:00.0 Off |                  Off |
| 30%   27C    P8             17W /  300W |       1MiB /  49140MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+

先从回显的第一行看起。这里可以看到我们安装了 580.95.05 版本的驱动。那么这个 CUDA Version 是怎么回事呢?

即使没装过 CUDA ToolKit,这里其实也会显示一个版本号的。这是因为驱动程序里有一个动态链接库 libcuda.so,它决定了当前版本的驱动能够支持的最高 CUDA 版本。

然后看一下每列的内容:

  • GPU / Fan:显卡序号(从 0 开始)、显卡风扇转速(以百分比计)
  • Name / Temp:显卡名、当前核心温度
  • Perf:性能状态(Performance State),P8 = 休眠模式;P2 = 高性能模式;P0 = 最大性能模式(一般 CUDA 计算都是在 P2 上)
  • Persistence-M / Pwr:持久化模式状态、功耗监控。持久化会在下面讲到。功耗监控列的格式为:当前功耗 / 最大功率(可以自己调整)
  • Bus ID:PCI 总线地址。这里可以和 lspci | grep -i vga 的回显对应上
  • Disp.A:Display Activate,显示输出激活状态。由于这些卡都没有连接显示器,所以目前都是 Off
  • Memory Usage:显存占用。喜闻乐见的核心指标,格式为:当前使用显存 / 总显存
  • GPU Util:GPU 核心使用率
  • Volatile Uncorr. ECC:ECC 开启状态。我没开,开了少 3G 显存,太致命了
  • Compute M.:计算模式(Compute Mode),分为 Default(默认)、Exclusive_Process(独占进程)、Prohibited(禁止计算)
  • MIG M.:多实例 GPU 模式(Multi Instance GPU Mode),高贵的数据中心卡才有,可以在硬件层面上把 GPU 切分

最后一行表格是当前活动进程。在这里可以看到使用了 GPU 的进程状态。

持久化

在 Windows 上,系统启动时内核模式驱动就会被加载并一直保持加载状态,天生就带着持久化的特性,所以通常情况下不用管。但在 Linux 中(尤其是无头机),因为没有客户端一直维护 GPU 句柄,所以每次目标 GPU 上有程序启动和停止时,内核模式驱动都会初始化和取消初始化目标 GPU。这无疑是一种资源上的浪费。最要命的是,在实践中,这个现象还会经常导致莫名其妙的 bug。你都准备跑 AI 计算了,还缺那点电费吗?持久化走起!如无意外,每个 Linux 用户都应该开启持久化功能:

nvidia-smi -pm 1

检查 GPU 拓扑

当你连接了 NVLink 后,可以使用如下命令来查看 NVLink 连接状态:

(base) root@gpu-a6000:~# nvidia-smi topo -m

        GPU0    GPU1    GPU2    GPU3    CPU Affinity    NUMA Affinity   GPU NUMA ID
GPU0     X      NV4     SYS     SYS     48-63,112-127   3               N/A
GPU1    NV4      X      SYS     SYS     32-47,96-111    2               N/A
GPU2    SYS     SYS      X      NV4     16-31,80-95     1               N/A
GPU3    SYS     SYS     NV4      X      0-15,64-79      0               N/A

Legend:

  X    = Self
  SYS  = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
  NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
  PHB  = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
  PXB  = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
  PIX  = Connection traversing at most a single PCIe bridge
NV#= Connection traversing a bonded set of # NVLinks 

如上表格所示:这个系统里有 4 张卡,NVLink 两两一组,GPU0/1 一对,GPU2/3 一对。从 0/1 到 2/3 就要走 CPU PCIE 了。

至于 CPU/NUMA 亲和性在此处不再展开讲述,如果你使用多路 Xeon 或 AMD Epyc 则可能需要注意一下调节 NPS 以优化 NCCL 性能。

CUDA

如果你是一个拥有很大固态和很快网络的 Docker 战神,那我的建议是不要在宿主机里装任何 CUDA,一切交给 Docker。

一般地,建议使用:nvidia/cuda - Docker Image

但是,如果你需要编译一些源码或者坚持古法环境配置,那么就需要在官网下载.run 文件去安装 CUDA 了。有几点需要注意:

  • 对于新安装的系统来说,安装 CUDA 前尽可能地补全环境,避免安装失败。CUDA 最好一口气装好,不然清理环境非常头疼:

    sudo apt-get install zlib1g -y
    sudo add-apt-repository ppa:ubuntu-toolchain-r/test
    sudo apt update
    sudo apt install gcc-11 g++-11 -y
    sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 60 --slave /usr/bin/g++ g++ /usr/bin/g++-11
    sudo apt-get install build-essential libgomp1 -y
    
  • 非常重要:安装时记得取消掉驱动安装。CUDA 工具包里会带一个驱动,不要用!如果你已经装好驱动了,再把这玩意带上,装完了必炸

  • 安装结束后,记得检查环境变量(以 CUDA 12.6 为例):

    vi ~/.bashrc
    尾部追加:
    export PATH=/usr/local/cuda-12.6/bin:$PATH
    export LD_LIBRARY_PATH=/usr/local/cuda-12.6/lib64:/usr/local/cuda-12.6/extras/CUPTI/lib64:/usr/local/cuda-12.6/targets/x86_64-linux/lib:/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
    export CUDA_HOME=/usr/local/cuda-12.6
    执行:
    source ~/.bashrc 
    

执行命令以检查 CUDA 环境是否都已正确配置:

(base) root@ubuntu:~# nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2024 NVIDIA Corporation
Built on Tue_Oct_29_23:50:19_PDT_2024
Cuda compilation tools, release 12.6, V12.6.85
Build cuda_12.6.r12.6/compiler.35059454_0

Pytorch

安装 Pytorch 有许多讲究。首先,你应该使用 uvconda 创建一个靠谱的虚拟环境,任何操作都需要在虚拟环境里执行。

访问:https://pytorch.org/get-started/locally/,查看当前 Stable 版本的 Pytorch 安装命令。

Pytorch 依赖许多特定环境:如 CUDA 版本、Python 版本、C++ 版本等,所以通常官网会列出 3 个主力 CUDA 版本对应的 Pytorch 安装命令。那么,假如你在使用特定版本的 CUDA 或需要安装特定版本的 Pytorch,如何确定有可用的 whl 呢?

检查可用 whl

Pytorch 官方构建:

https://download.pytorch.org/whl/cuXXX/torch

XXX 代表 CUDA 版本,以三位数表示。比如 CUDA 11.8 就是 cu118,CUDA 12.6 就是 cu126

截至本文动笔的时间,Pytorch Stable 版本是 2.9.1,官网默认列出的版本是 CUDA 12.6 / 12.8 / 13.0。假如我创建了一个 Python 3.11 的环境并且宿主机的 CUDA 版本为 12.4,应该如何确定 whl 是否存在(可以直接安装)呢?

按照以上信息,我们访问:

https://download.pytorch.org/whl/cu124/torch/

很遗憾!我们看到了,最新的也就只有 2.6 版本:

torch-2.6.0+cu124-cp310-cp310-linux_x86_64.whl
torch-2.6.0+cu124-cp310-cp310-win_amd64.whl
torch-2.6.0+cu124-cp311-cp311-linux_x86_64.whl
torch-2.6.0+cu124-cp311-cp311-win_amd64.whl
torch-2.6.0+cu124-cp312-cp312-linux_x86_64.whl
torch-2.6.0+cu124-cp312-cp312-win_amd64.whl
torch-2.6.0+cu124-cp313-cp313-linux_x86_64.whl
torch-2.6.0+cu124-cp313-cp313-win_amd64.whl
torch-2.6.0+cu124-cp313-cp313t-linux_x86_64.whl
torch-2.6.0+cu124-cp39-cp39-linux_x86_64.whl
torch-2.6.0+cu124-cp39-cp39-win_amd64.whl

正式版的版本太老,看看 nightly 会不会好些,访问:

https://download.pytorch.org/whl/nightly/cu124/torch/

好多了,有 2.7 版本的每夜构建。如果你只是一个小白用户,那在坚守 CUDA 12.4 的情况下也就只有 Pytorch 2.7 可用了:

torch-2.7.0.dev20250310+cu124-cp310-cp310-manylinux_2_28_x86_64.whl
torch-2.7.0.dev20250310+cu124-cp310-cp310-win_amd64.whl
torch-2.7.0.dev20250310+cu124-cp311-cp311-manylinux_2_28_x86_64.whl
torch-2.7.0.dev20250310+cu124-cp311-cp311-win_amd64.whl
torch-2.7.0.dev20250310+cu124-cp312-cp312-manylinux_2_28_x86_64.whl
torch-2.7.0.dev20250310+cu124-cp312-cp312-win_amd64.whl
torch-2.7.0.dev20250310+cu124-cp313-cp313-manylinux_2_28_x86_64.whl
torch-2.7.0.dev20250310+cu124-cp313-cp313-win_amd64.whl
torch-2.7.0.dev20250310+cu124-cp313-cp313t-manylinux_2_28_x86_64.whl
torch-2.7.0.dev20250310+cu124-cp313-cp313t-win_amd64.whl
torch-2.7.0.dev20250310+cu124-cp39-cp39-manylinux_2_28_x86_64.whl
torch-2.7.0.dev20250310+cu124-cp39-cp39-win_amd64.whl

指定镜像及安装特定版本

现在我们确定了最新的 whl 了,但显然直接从官方源下载太慢。比如,我是 CUDA 12.6 用户,虚拟环境里用的 Python 3.11,想要安装 Pytorch 2.9.1(当前的 stable),官方给出的命令是:

pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126

这里推荐使用南京大学的镜像,更新比较及时:

pip3 install torch torchvision --index-url https://mirrors.nju.edu.cn/pytorch/whl/cu126

实际上,这等价于安装了如下 whl(以 torch 包举例):

torch-2.9.1+cu126-cp311-cp311-manylinux_2_28_x86_64.whl

可以看到 whl 的命名规则:包版本+CUDA版本-Python版本-对应系统_系统架构

那么,假如我想要安装 Pytorch 2.8.0 呢?那么就应该注意一下版本对应关系。torchtorchaudio 的版本号是直接对应的,而 torchvision 则可能需要查一下。

当 Pytorch 2.1.0 时,包版本如下:

  • torch==2.1.0
  • torchaudio==2.1.0
  • torchvideo==0.16.0
  • torchtext==0.16.0

从 Pytorch 2.4.0 开始,没有 torchtext 包了。包版本如下:

  • torch==2.4.0
  • torchaudio==2.4.0
  • torchvideo==0.19.0

可以看到,它们都是递增关系。那么很容易推出来 Pytorch 2.8.0 需要的安装命令:

pip3 install torch==2.8.0 torchvision==0.23.0 torchaudio==2.8.0 --index-url https://mirrors.nju.edu.cn/pytorch/whl/cu126

注:Pytorch 2.9.0 的 Conv3d 有问题,如果你部署 / 微调 Qwen3VL 的时候发现明显的性能降级,则应将版本回退到 2.8 或升级至 2.9.1(最好是退到 2.8)


📌 转载信息
原作者:
flymyd
转载时间:
2026/1/14 18:25:02

OmO skills:将 oh-my-opencode 的多 Agent 协同移植到 Claude Code

Oh-my-opencode 最近太火了,我让 Claude Code 学习了一下,然后我就把它的核心移植到了 Claude Code。

之前的 Codeagent 自己选择 backend 的模式总感觉缺少点灵魂,看到 OmO 的设计直接灵光一现,特定场景下的指定模型 + 特调 prompt 才能够发挥最好,于是我开始了 codeagent 的改造和 omo skills 的移植。

OmO 核心设计:Sisyphus 协调器 + 专业 Agent 团队

Agent 层级

OmO 构建了一个 6+1 人专家团队 (我单独加了一个 develop agent):

Agent角色后端模型成本
oracle技术顾问claudeclaude-opus-4.5昂贵
librarian外部研究claudeclaude-sonnet-4.5中等
explore代码库搜索opencodegrok-code免费
develop代码实现codex(default)中等
frontend-ui-ux-engineerUI/UX 专家geminigemini-3-pro昂贵
document-writer文档编写geminigemini-3-flash中等

工作流程

用户请求
    ↓
/omo 调用 Sisyphus
    ↓
Intent Gate 分析任务类型
    ↓
    ├─→ 简单任务:Sisyphus 直接执行
    ├─→ 复杂任务:委派给专业 Agent
    └─→ 探索任务:并行启动多个 Agent

Sisyphus 通过 codeagent-wrapper --agent <agent-name> 来委派任务:

codeagent-wrapper --agent oracle - . <<'EOF'
分析这个项目的认证架构,给出改进建议
EOF

使用方法

基础用法

/omo <你的任务描述>

实际案例

1. 代码重构

/omo 帮我重构这个认证模块,提高可维护性

执行流程

  1. Sisyphus 分析任务:需要代码探索 + 架构设计 + 实现
  2. 委派 explore 搜索认证相关代码 (grok)
  3. 委派 oracle 分析架构问题 (sonnet)
  4. 委派 develop 执行重构 (codex)

2. 全栈功能开发

/omo 我需要添加一个支付功能,包括前端 UI 和后端 API

执行流程

  1. Sisyphus 识别为全栈任务
  2. 并行启动:
    • frontend-ui-ux-engineer 设计支付界面(Gemini Pro)
    • develop 实现后端 API(Codex)
  3. Sisyphus 协调两者的接口对接

3. 代码库研究

/omo 这个项目使用了什么认证方案?

执行流程

  1. Sisyphus 识别为研究任务
  2. 委派 explore 搜索认证相关代码
  3. 委派 librarian 查找外部文档
  4. Sisyphus 汇总结果返回

4. 文档生成

/omo 为这个 API 模块生成完整的技术文档

执行流程

  1. explore 搜索 API 代码
  2. document-writer 生成文档(Gemini Flash,便宜快速)

配置

Agent - 模型映射在 ~/.codeagent/models.json 中配置:

{ "default_backend": "codex", "default_model": "gpt-5.2", "agents": { "oracle": { "backend": "claude", "model": "claude-opus-4-5-20251101", "description": "Technical advisor", "yolo": true }, "librarian": { "backend": "claude", "model": "claude-sonnet-4-5-20250929", "description": "Researcher", "yolo": true }, "explore": { "backend": "opencode", "model": "opencode/grok-code", "description": "Code search" }, "frontend-ui-ux-engineer": { "backend": "gemini", "model": "gemini-3-pro-high", "description": "Frontend engineer" }, "document-writer": { "backend": "gemini", "model": "gemini-3-flash", "description": "Documentation" }, "develop": { "backend": "codex", "model": "gpt-5.2", "description": "codex develop", "yolo": true, "reasoning": "xhigh" } } } 

2026 未来展望

OmO 的多 Agent 协同模式,代表了 AI 编程工具的一个方向:

  1. 异构模型协同:不同模型擅长不同任务
  2. 成本效率平衡:在质量和成本之间找到最优解
  3. 并行执行:充分利用多模型的并行能力

随着更多专业模型的出现(如代码专用模型、UI 专用模型),这种协同模式的优势会更加明显。

我认为这个方向是一个趋势,让不同的模型去干适合的事。

将 oh-my-opencode 的多 Agent 协同理念移植到 Claude Code,通过 Sisyphus 协调器 + 专业 Agent 团队的架构,实现了:

  • 成本优化:按需选择模型,降低 60-80% 成本
  • 效率提升:并行执行,缩短 40-50% 时间
  • 质量保证:专业分工,各司其职

对于复杂的全栈开发、架构重构、代码库探索等任务,/omo 是比 Claude Code 原生能力更优的选择。

今天才开发完成,估计存在一些 bug 欢迎使用有问题直接提 issue (最好是提 pr。


开始使用

访问 GitHub - cexll/myclaude: Claude Code and Codex orchestration workflow 安装,不知道怎么安装? 一切交给 CC

/omo 帮我分析这个项目的架构,并给出改进建议

让 Sisyphus 和他的团队为你工作。


📌 转载信息
原作者:
benchen
转载时间:
2026/1/14 18:24:58

我是山东电信的三折卡 原套餐是 169 元 201910 套餐 三折之后的月租是 39 元一个月
套餐内容有 1100 分钟通话 30g 本地流量 30g 全国流量 免费的 4 张副卡 千兆宽带和电视
然后加了一个 30 元的量子密话功能送了 20g 全国流量 开了一个 20 元的减免包 相当于 10 块钱有 20g 流量
这个卡是我过户来的 所以这就算是基本的套餐吧 相当于月租 49 有以上这些流量和通话

然后又去营业厅开了 ** 橙翼黄金会员(连续包月) 橙翼白金会员(连续包月) 橙翼黑金会员(连续包月)** 这三个会员,这三个会员一个月加起来需要扣掉 33.7 元业务费,但是每个月能领 9 元 18 元 24 元的翼支付券 每个月抵扣掉开通权益的费用后还可以一个号多 9+18+24-33.7=17.3 元话费
备注:有些地区可以开 24 期的话 能够特别便宜 现在山地地区已经下架橙翼黄金会员(24 期)只有橙翼黄金会员(连续包月)所以业务费会贵很多,如果这三个会员能开到 24 期的 那月租只会扣掉 26.7 元 一个月相当于白赚 9+18+24-26.7=24.3 元话费

然后又开通了 9 元 20g 的业务 202307,这个主要是因为流量不够用 9 块钱有 20g 的全国通用流量 主卡副卡可以共享 但是不能结转

这样算下来之后 每个月不仅不用交月租 反而还能剩下一些电话费

纯分享自己的撸话费的办法
各位大佬 还有知道能开哪些权益能撸话费吗,可以一起来交流一下


📌 转载信息
原作者:
chu762278436
转载时间:
2026/1/14 18:24:47

获取 Krak 银行信息

打开 bitget,进入 Fiat24 银行卡 发起转账

薅羊毛入口:薅 13 刀赠金。原帖被删了,新帖还未通过,有意者可私信,进群组有详细说明

总结

  • 币安 C2C 后,链上提币到 bitget,Fiat24 发起欧元转账,损耗要远远小于 Apple/Google pay 或 借记卡 / 信用卡 直接入金,拥有同名外币 iban 都可以参考着发起转账,WISE 等对数字货币比较敏感的平台请谨慎操作,可能封号,避免因小失大。

📌 转载信息
原作者:
Tifae
转载时间:
2026/1/14 18:23:41

librechat 出现漏洞,可以完全控制容器。已中招,自己的 token(在.env 文件中)8 号开始被异常 ip 使用,今天没钱了去看日志才发现。原本以为是之前 dify 漏洞影响,但是感觉时间上不对(dify 爆出漏洞时就立马关了)。

太难了,就部署了四个服务,两个被爆出了漏洞,一个处于仅限内网访问

漏洞内容地址:


📌 转载信息
原作者:
whq2020
转载时间:
2026/1/14 18:23:14

  • 在客厅电视播放《仙逆》最新一集
  • 《凡人修仙传》更新到多少集了?

安装

方式 1: uvx

{
  "mcpServers": {
    "mcp-vods": {
      "command": "uvx",
      "args": ["mcp-vods"],
      "env": {
        "SEARCH_CACHE_TTL": "5"
      }
    }
  }
}

方式 2: Docker

mkdir /opt/mcp-vods
cd /opt/mcp-vods
wget https://raw.githubusercontent.com/aahl/mcp-vods/refs/heads/main/docker-compose.yml
docker-compose up -d
{
  "mcpServers": {
    "mcp-vods": {
      "url": "http://0.0.0.0:8821/mcp" # Streamable HTTP
    }
  }
}

方式 3: Home Assistant OS Add-on

一个帮你追剧 / 追番的 MCP 服务器1

快速开始

  • 添加到 Claude Code, 执行命令:
    • claude mcp add vods -- uvx mcp-vods
    • claude mcp add vods --env MITV_LIST_CFG=客厅电视:192.168.1.11 -- uvx mcp-vods
  • 添加到 OpenAI CodeX, 执行命令: codex mcp add vods -- uvx mcp-vods
  • 添加到 Cursor 一个帮你追剧 / 追番的 MCP 服务器3
  • 添加到 VS Code 一个帮你追剧 / 追番的 MCP 服务器2
  • 添加到 Cherry Studio 一个帮你追剧 / 追番的 MCP 服务器6

环境变量

免配置开箱即用

  • VOD_CONFIG_URL: 远程配置文件 URL,可选 (默认已内置)
  • SEARCH_CACHE_TTL: 搜索缓存 TTL,可选 (默认 5 分钟)
  • MAX_SEARCH_SITES: 搜索次数限制,可选 (默认 10)

使用已部署的 LunaTV/MoonTV

  • MOON_BASE_URL: 已部署的 MoonTV 服务地址,可选,如: http://localhost:3000
  • LUNA_BASE_URL: 已部署的 LunaTV 服务地址,可选
  • LUNA_USERNAME: LunaTV 登录账号,可选
  • LUNA_PASSWORD: LunaTV 登录密码,可选

小米电视 / 投影 / 机顶盒

如需在小米电视上播放视频,要至少配置 MITV_LOCAL_IPMITV_LIST_CFG 之一

  • MITV_LOCAL_IP: 单台小米电视本地 IP,可选
  • MITV_LIST_CFG: 多台小米电视配置,可选,如: 客厅电视:192.168.1.11;主卧电视:192.168.1.12

📌 转载信息
原作者:
alo
转载时间:
2026/1/14 18:21:18


📌 转载信息
原作者:
AVkiss
转载时间:
2026/1/14 18:21:07

你是不是也经历过这种循环:
写完一篇技术文章 → 发到掘金 → 再手动复制到 CSDN → 然后知乎 → 简书 → 公众号……

每个平台的编辑器不一样,格式要调整,图片要重新上传,标签要重新打。一篇文章同步到 5 个平台,可能要花掉半小时甚至更久。

更要命的是:公众号的富文本编辑器会把你的 Markdown 格式全部吃掉,代码高亮没了,数学公式变成乱码……

于是我做了 SyncCaster。

一、SyncCaster 是什么?

SyncCaster 是一个 Chrome 浏览器扩展,帮助内容创作者将文章 一键同步到 17+ 主流博客平台
核心理念很简单:一次编辑,处处发布
你只需要在 SyncCaster 内置的 Markdown 编辑器里写好文章,勾选要发布的平台,点击发布 —— 剩下的事情它来搞定。

完全本地运行,不收集、不存储任何用户信息。代码开源,数据自己掌控。

二、支持哪些平台?

目前已支持 17 个主流平台

平台MarkdownLaTeX 公式备注
掘金
CSDN
博客园需开启数学公式支持
51CTO
腾讯云开发者社区
知乎富文本编辑器
简书
阿里云开发者社区
思否 (SegmentFault)
哔哩哔哩专栏
微信公众号完整保留渲染样式,支持主题
开源中国
今日头条富文本编辑器
InfoQ
百家号富文本编辑器
网易号富文本编辑器
Medium英文平台

覆盖了国内主流的技术社区、自媒体平台,以及海外的 Medium

三、核心功能

3.1 内置 Markdown 编辑器

不需要在外部编辑器写好再复制进来。SyncCaster 内置了功能完整的 Markdown 编辑器:

  • 实时预览,所见即所得
  • 代码高亮(highlight.js)
  • LaTeX 数学公式渲染(KaTeX)
  • Mermaid 图表支持
  • 多种主题样式可选

写技术文章最头疼的公式和代码块,这里都能完美渲染。

3.2 智能内容采集

看到一篇好文章想转载?SyncCaster 可以从任意网页 一键采集文章内容

  • 智能提取标题、正文、图片、公式
  • 基于 AST(抽象语法树)的规范化处理
  • 自动转换为统一的 Markdown 格式
  • 保留原文结构,不丢失格式

技术上,我们用 Mozilla 的 Readability 库提取正文,然后通过自研的 CanonicalAST 管道做清洗和标准化。这意味着无论原文来自哪个平台,采集后的格式都是干净统一的。

3.3 一键多平台发布

这是 SyncCaster 的核心能力:

  1. 选择要发布的文章
  2. 勾选目标平台(可多选)
  3. 点击发布
  4. 自动打开各平台编辑页,填充内容,提交

整个过程通过 DOM 自动化 实现,模拟人工操作。你可以看到每个平台的发布进度,失败了还能重试。

发布完成后,所有打开的标签页会 自动归入一个分组,方便你检查和管理。

3.4 微信公众号特别优化

公众号是最难搞的平台 —— 它的富文本编辑器会吃掉几乎所有 Markdown 格式。

SyncCaster 专门为公众号做了适配:

  • 完整保留渲染样式:标题、代码块、引用、表格都能正常显示
  • CSS 内联处理:把样式直接写进 HTML,绕过公众号的过滤
  • 多种主题可选:不同风格的排版样式
  • 数学公式支持:LaTeX 公式正常渲染

这部分参考了 doocs/md 项目的实现,在此致谢。

3.5 账号状态管理

  • 自动检测各平台的登录状态
  • Cookie 过期预警
  • 一键刷新账号状态
  • 支持重新登录

不用担心发布到一半发现某个平台掉线了。

3.6 任务队列与进度追踪

  • 发布任务队列管理
  • 实时进度条
  • 详细日志记录
  • 失败重试机制

发布 10 个平台也不慌,进度一目了然。

四、技术架构(给感兴趣的开发者)

SyncCaster 采用 monorepo 架构,主要模块:

SyncCaster/
├── apps/
│   └── extension/                  # Chrome 扩展主应用
│       ├── background/             # Service Worker
│       │   ├── scheduler.ts        # 任务调度
│       │   ├── publisher.ts        # 发布引擎
│       │   └── index.ts
│       │
│       ├── content-scripts/        # 内容脚本
│       │   ├── collector.ts        # 页面内容采集
│       │   ├── auth-check.ts       # 登录状态检测
│       │   └── index.ts
│       │
│       └── ui/                     # 扩展前端界面(Vue 3)
│           ├── pages/              # 页面(发布、配置、预览等)
│           ├── components/         # 通用组件
│           ├── store/              # 状态管理
│           └── main.ts
│
├── packages/
│   ├── adapters/                   # 平台适配层
│   │   ├── zhihu/                  # 知乎
│   │   ├── juejin/                 # 掘金
│   │   ├── weibo/                  # 微博
│   │   ├── wechat/                 # 微信公众号
│   │   └── ...                     # 共 17 个平台
│   │
│   ├── core/                       # 核心业务模块
│   │   ├── ast/                    # CanonicalAST
│   │   │   ├── parser.ts           # Markdown / HTML → AST
│   │   │   ├── transformer.ts      # AST 规范化处理
│   │   │   └── serializer.ts       # AST → 平台格式
│   │   │
│   │   ├── collector/              # 通用采集逻辑
│   │   │   ├── extractor.ts
│   │   │   └── normalizer.ts
│   │   │
│   │   ├── wechat/                 # 微信公众号专项逻辑
│   │   │   ├── editor-adapter.ts
│   │   │   └── image-uploader.ts
│   │   │
│   │   └── db/                     # 本地数据存储
│   │       ├── schema.ts           # IndexedDB 表结构
│   │       ├── migrations.ts
│   │       └── index.ts
│   │
│   └── utils/                      # 通用工具函数
│       ├── dom.ts
│       ├── time.ts
│       ├── logger.ts
│       └── types.ts
│
└── md/                             # 内嵌 Markdown 编辑器
    ├── editor/                     # 编辑器核心
    ├── plugins/                    # 扩展插件
    └── themes/                     # 样式主题

技术栈:

  • 前端:Vue 3 + TypeScript + Naive UI + UnoCSS
  • 构建:Vite + pnpm
  • 存储:IndexedDB (Dexie.js)
  • Markdown:marked + highlight.js + KaTeX + Mermaid
  • 扩展:Chrome Extension Manifest V3

适配器设计

每个平台都有一个独立的适配器,继承自 BaseAdapter

abstract class BaseAdapter {
  abstract id: PlatformId;
  abstract name: string;
  abstract capabilities: PlatformCapabilities;
  
  // 认证检查 abstract ensureAuth(ctx): Promise<AuthSession>;
  // 内容转换(Markdown → 平台格式) abstract preEditPost(post, ctx): Promise<PlatformPayload>;
  // 发布 abstract publish(payload, ctx): Promise<PublishResult>;
  
  // DOM 自动化配置 dom?: {
    matchers: string[];  // URL 匹配规则 fillAndPublish(payload): Promise<PublishResult>;
  };
}

想支持新平台?新增一个适配器文件,实现这几个方法就行。

五、SyncCaster 适合谁?

  • 技术博主:写完文章要同步到掘金、CSDN、知乎、公众号……
  • 自媒体作者:需要在多个平台分发内容
  • 内容运营:管理多个平台账号,批量发布
  • 知识管理者:想把散落各处的好文章统一采集归档
    如果你每周至少发布 1 篇文章到 3 个以上平台,SyncCaster 能帮你省下大量重复劳动。

六、快速开始

安装使用

# 克隆仓库
git clone https://github.com/RyanYipeng/SyncCaster.git
cd SyncCaster

# 安装依赖
pnpm install

# 构建扩展
pnpm build

然后在 Chrome 中加载扩展:

  1. 打开 chrome://extensions/
  2. 开启「开发者模式」
  3. 点击「加载已解压的扩展程序」
  4. 选择 apps/extension/dist 目录

开发调试

pnpm dev    # 开发模式(热更新)
pnpm test # 运行测试 

七、为什么开源?

我相信好的工具应该是开放的。

  • 数据自主:你的文章、你的账号信息,都存在本地 IndexedDB,不经过任何服务器
  • 可扩展:平台适配器是插件化设计,社区可以贡献新平台支持
  • 可信任:代码完全公开,你可以审计每一行

八、最后

SyncCaster 解决的是一个很具体的问题:让内容创作者专注于写作,而不是被「同步」这件事消耗。
如果你也被多平台发布折磨过,欢迎试试。
项目地址:

由于谷歌商店审核时间较长,因此现在还未上线谷歌商店(审核中),后续上线会及时更新。
如果觉得有用,欢迎 Star —— 这会让我更有动力持续维护。
有问题或建议,欢迎提 Issue 或 PR。


📌 转载信息
原作者:
Only_Up
转载时间:
2026/1/14 18:21:05

佬们好,vibe 了一个简洁美观的串口调试工具 PortaX


项目地址

Portax:一款简单、美观且便捷的串行调试工具
开箱即用: Portax

亮点

  • ui 简洁美观:深浅主题
  • 关键词高亮:支持自定义关键词高亮及颜色配置
  • 快捷指令:可自定义、持久化保存的快捷指令组,支持快速点击发送
  • 历史回溯:支持通过键盘 / 键回溯发送历史
  • 数据过滤:支持实时日志搜索与过滤
  • RX/TX 监控:底部状态栏集成动态呼吸灯,实时反馈数据收发状态
  • 支持生成折线图:这个功能目前还没优化好,效果如图三,且可以截图与导出图片到 pdf


演示图

图一

图二

图三

图四

开发计划

  • 优化曲线部分
  • windows 客户端
  • 其他协议

欢迎佬们提建议,帮助我完善优化,使 PortaX 成为更好的工具


📌 转载信息
原作者:
HaxIOX
转载时间:
2026/1/14 18:20:45


新增工具

需要使用 Claude Messages Pipe (至少 v0.0.3 版本) 执行,连接到 Claude 官方 API 使用

工具说明备注
Claude 原生网页搜索需要透传参数或直连 Claude API链接
Claude 原生 URL 抓取需要透传参数或直连 Claude API链接
Claude 原生代码执行需要透传参数或直连 Claude API链接

使用预览


📌 转载信息
原作者:
Throttle
转载时间:
2026/1/14 18:20:37

【开源自荐】制作短剧的教程整理

花费一个月时间做了一个开源制作短剧的项目,继上一篇把项目开源后,受到了好多佬友的鼓励,简单做了一个开源项目的教程。

之前的那一篇在这里:【开源短剧】AI 短剧创作工具

首先要配置大模型供应商,模型 API 地址,选择模型,文字,生图,生视频,只配置一遍即可,如果不配置这一步项目没办法往下进行。

配置好大模型供应商信息以后,开始创建项目

将准备好剧本放到章节里面,当然,聪明如佬,自己写一个也不是不可以~

提取角色和场景

提取角色和场景成功后,生成图片

批量生成角色和场景

拆解分镜

分解成功后,进入到剧集编辑页面,这里就是具体的生成效果配置,

每一个镜头配置,镜头属性,镜头图片,视频生成,音效与配乐,视频合成等等。

镜头属性,选择场景,选择角色,选择镜头语言

点击镜头图片,提取提示词,生成图片或者自己上传参考图片(玩命开发中 ing)

选择模型 ,选择参考图,选择时长,可以选择首帧或者其他关键帧配置,根据参考图点击生成视频按钮

视频生成完成后,点击:添加到素材库,再点击添加到时间线,视频的一个片段小结单元就制作好啦

两个视频拼接,可以设置转场效果

合成视频,下载视频

以上便是之前开源项目的简单制作教程


📌 转载信息
原作者:
xiaojunersheng
转载时间:
2026/1/14 18:20:02

昨天发了一个 【开源】CLIProxyAPI 数据监控 使用统计 日志可视化

然后很多佬友希望集成到现在的 CPAMC

所以,说干就干,又把任务丢给 AI,集成进去了。

(匆匆忙忙上线 难免有 bug 欢迎各位大佬 issue 慢慢修复)

1、感谢 L 站各位大佬提供的公益站
2、主要对 OpenAI 兼容提供商 做适配
3、闲时让 AI 码的,难免有 bug,大佬勿喷

使用方法:

方法 1:替换 config.yaml 文件中的 panel-github-repository 参数为:
https://github.com/kongkongyo/Cli-Proxy-API-Management-Center

方法 2:直接 下载 management.html 到本地打开

项目地址:

https://github.com/kongkongyo/Cli-Proxy-API-Management-Center

效果展示:



另附: CLIProxyAPI 的安装以及使用方法


📌 转载信息
原作者:
00001
转载时间:
2026/1/14 18:18:36

一个解决 AI 写代码 "丑" 问题的开源项目

项目地址

为什么做这个?

兄弟们,不知道你们有没有这种感觉:用 AI 写代码很爽,但是让 AI 写出好看的前端界面真的太难了!

每次让 AI 生成 UI,结果都是:

  • 风格不一致,这次生成一个样,下次又变样了
  • 缺乏设计感,一看就是 "AI 味" 很重
  • 想参考 Stripe、Vercel 这些优秀网站的设计?只能手动复制粘贴,累死

所以我就想:能不能把优秀网站的设计 "喂" 给 AI,让它照着写?

于是就有了 Design-Learn 这个项目。

生成的效果

Chrome 插件

VSCode 插件

MCP 截图

点击展开 / 折叠

配置到 rules 也行,然后生成的网页对比



后续可以加一些特效的提示词的增强

核心功能

一键提取页面设计

看到好看的网站?直接点一下插件图标,2-5 秒就能提取:

  • 完整快照:HTML + CSS + 图片 + 字体,一个不落
  • 设计元素:颜色、字体、间距、阴影、圆角,自动识别
  • 组件识别:按钮、卡片、导航、表单,统统给你标出来

比如你看到 Stripe 的配色很好看,点一下,它的颜色系统、字体规范、间距体系就都存下来了。

AI 智能分析

提取完还不够,我还加了 AI 分析功能:

  • 自动生成设计规范文档(Style Guide)
  • 分析色彩系统:主色、辅助色、语义色、渐变
  • 分析字体排版:字体族、字号层级、行高、字重
  • 分析间距系统:基础单位、常用间距值
  • 分析组件规范:按钮样式、卡片阴影、输入框状态

这样 AI 就能理解这套设计语言了。

MCP 集成 Claude Code

这是我觉得最酷的功能!配置好之后,Claude Code 可以直接查询你收集的设计库:

# 在 Claude Code 里直接说:
> 用 stripe.com 的设计风格写一个定价页面

# AI 会自动查询你提取的 Stripe 设计规范,生成风格一致的代码 

支持的 MCP 工具:

  • list_designs - 列出所有已提取的设计
  • search_designs - 按关键词 / URL 搜索
  • get_design - 获取完整设计详情
  • get_rules - 获取设计规则(颜色 / 字体 / 间距)
  • get_styleguide - 获取 AI 生成的设计规范文档

多端协同

整个系统由三个组件组成:

Chrome Extension ──┐
                   ├──> Design-Learn Server (port 3100)
VSCode Extension ──┤    - REST API
                   │    - MCP (SSE/stdio)
Claude Code ───────┘    - SQLite + 文件存储
  • Chrome 插件:提取页面、AI 分析、本地存储
  • VSCode 插件:查看快照、管理设计库、启停服务
  • 本地服务:数据持久化、MCP 工具、多端同步

快速开始

安装 Chrome 插件

chrome://extensions/ → 开发者模式 → 加载已解压的扩展程序 → 选择 chrome-extension/

提取页面

  1. 访问目标网站(比如 https://stripe.com
  2. 点击工具栏插件图标
  3. 点击 "提取页面风格"
  4. 等待 2-5 秒,完成!

配置 MCP 集成(可选)

# 安装服务端依赖 cd design-learn-server && npm install

# 添加到 Claude Code
claude mcp add -s user design-learn -- node /YOUR/PATH/Design-Learn/design-learn-server/src/stdio.js

# 验证
claude mcp list

技术栈

  • Chrome 插件:Manifest V3,纯 JS,零依赖
  • 本地服务:Node.js + SQLite (better-sqlite3) + MCP SSE
  • VSCode 插件:TypeScript + Webview

未来计划

  1. 集成 ui-uxpro

    • 把 ui-uxpro 项目整合进来,增强设计分析能力
    • 支持更智能的组件识别和分类
  2. 设计库管理优化

    • 支持标签、分类、收藏
    • 支持批量导入导出
    • 支持设计库分享
  3. AI 分析增强

    • 支持更多分析维度(无障碍、响应式、性能)
    • 支持自定义分析模板
    • 支持批量分析
  4. 参考 lovable 实现选择主题直接生成可预览的 UI/UX 参考界面

这个项目我一个人在搞,有时候会有点慢,但我会持续更新。你们的支持就是我最大的动力!


📌 转载信息
原作者:
xieyuDaniel
转载时间:
2026/1/14 18:18:22

最近看了这个帖子 https://www.v2ex.com/t/1182067#reply243 链接,让我对 AI 变声有一些思考。

其实,变声的核心在于音色的数据集。换句话说,如果想“模拟”一个人的声音,需要收集到特定条件的数据:
同一个人的语音
说话清晰,口齿分明
时长充足,通常 20–60 分钟以上


可能的采集场景:
视频博主:上传的视频或直播语音被用于训练 AI 模型
恶意行为者:多次语音沟通,甚至利用通讯录信息,模拟你的声音向家人进行诈骗

搭建一个影视站,市面上对于大多数个人站长而言,几乎使用的是:苹果 CMS、海洋 CMS、飞飞 CMS、赞片 CMS、马克斯 CMS 等,本文将详细介绍使用苹果 CMS 搭建一个影视站。

1,前期准备

如果需要搭建一个可以在互联网中访问的影视站,那么需要提前准备服务器(云服务器、VPS)、域名: 1、服务器:本文教程使用的是 VMRack 三网精品服务器(4 核 8G),具体需要多大的配置,则取决后续用途。 2、域名:选择一个与自己影视站品牌相关的的域名,域名也不是必须的,可以直接通过 IP 访问。 接下来,就进行教程安装,需要使用的源码安装包: 1、苹果 CMS 安装包下载:maccms10.zip 2、苹果 CMS10 模板下载:mb-1.zip 3、视频萌芽采集插件下载:萌芽采集插件 v10.7.5.tar.gz 1、安装宝塔 访问宝塔官网,复制一键安装命令:


在服务器详情中,点击登录:

选择 SSH 连接,输入密码,即可登录进服务器:


复制 / 粘贴宝塔一键安装命令,回车执行: if [-f /usr/bin/curl];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ssl251104


安装成功,把登录信息保存好:


访问宝塔面板:https://154.53.75.73:10609/5913b2f2,输入账号、密码即可:

这里点击暂不绑定。

2、安装运行环境 苹果 CMS 使用的是 PHP,所以需要先安装 PHP 的运行环境,这里直接使用 LNMP(推荐),选择 PHP 7.4 版本,点击一键安装即可。

接下来就等待安装成功,可以看到已成功安装,并且正常运行服务:

苹果 CMS 运行需要 fileinfo 扩展,在已安装列表中,点击 PHP 设置操作,找到 fileinfo,点击安装:

3、创建网站 这里先通过 IP 访问网站,后续介绍如何进行域名访问:

4,上传苹果 CMS 源码 下载前面提供的苹果 CMS V10 源码安装包,点击网站的根目录: 点击上传,选择源码压缩包: 解压压缩包: 把解压后的文件夹中的内容放到这个项目的根目录下面 /www/wwwroot/154.53.75.73:

5、安装苹果 CMS 访问 http://154.53.75.73/install.php: 填写数据库信息与管理员的账号密码: 可在数据库菜单中查看数据库信息: 填写完信息后,点击立即执行安装即可: 为了安全因素,需要更改下管理后台的入口文件: 修改完后,访问管理后台:http://154.53.75.73/mac.php: 输入账号、密码、验证码即可登录管理后台: 至此苹果 CMS 就安装成功了,访问网站首页:http://154.53.75.73/ 可以看到,需要自行安装模板。

6、安装模板 下载前面提供的苹果 CMS10 简洁版自适应影视模板,进入 template 目录: 上传模板压缩包 -> 解压 进入管理后台,在模板管理中可以看到刚才上传的模板: 设置模板,【系统】【网站参数配置】设置网站模板 点击保存后,刷新管理后台页面: 访问官网首页: 至此,模板安装成功。模板安装成功后,只需要采集视频即可搭建完一个影视站。

7、安装视频采集插件 下载萌芽采集插件安装包,上传压缩包 -> 解压 进入管理后台,在应用市场中可以看到萌芽采集插件,然后点击启用: 刷新管理后台,在首页左侧菜单中,可以看见萌芽采集资源的菜单入口: 进入萌芽采集资源,可以看见下很有很多视频资源站: 接下来就可以进行视频采集了。

8、手动采集视频 选择一个资源站,点击进入: 先绑定分类: 可以看到上方有很多分类,已经绑定分类的显示红色。未绑定的可以手动点击绑定,如果本地程序分类还未设置,可先添加分类;也可以通过插件,一键添加所需分类。 点击右下角添加 / 复制分类,可以一键绑定: 默认会选中未绑定的分类,勾选需要的分类点击添加分类即可绑定需要的分类。 当分类绑定好后,选中需要的视频,点击采选中: 可以看到,刚才选中的视频就采集成功了: 进入视频 -> 视频数据就能看见视频了:

访问官网首页:

点击影片也能正常播放:

至此,手动采集视频流程就结束了。很明显手动采集效率比较低,日常维护也比较困难,接下来,讲解自动采集视频。

9、自动采集视频

自动采集需要使用萌芽采集的 Python 脚本,一键安装命令: curl -fsSL “https://down.mycj.pro/python/install.sh” -O && chmod +x install.sh && ./install.sh
复制命令,在宝塔终端,粘贴命令执行:

安装成功,复制命令执行,测试是否安装成功:

source /opt/miniconda3/bin/activate /opt/miniconda3/3.12.0/conda_env && cd /www/python/mengya_project && python app.py

可以看到 python 脚本成功执行,接下来配置自动采集。

回到管理后台,萌芽采集资源 -> 设置,打开接口开关:

设置完成后,点击立即保存即可。 回到宝塔面板,在该目录下,打开 config.yaml 文件,配置萌芽自动采集参数:

token、入库密码就是刚才开启接口时设置的:

配置完参数后,回到管理后台,选择一个资源站,点击定时采集:

这里就先选择 1 小时内的资源:

进入定时任务中,可以看见刚才设置的定时采集任务:

回到宝塔面板,在终端中执行采集脚本:

source /opt/miniconda3/bin/activate /opt/miniconda3/3.12.0/conda_env && cd /www/python/mengya_project && python app.py

可以看到采集脚本成功执行,回到管理后台,视频也增多了。

接下来,只需要把该脚本设置成定时任务执行,即可自动采集视频,这里设置每小时自动采集一次:

自此,自动采集流程完成。

到这里一个影视站就基本搭建完成,其中的细节还需要自行摸索。看到这里相信有朋友,还会提出一些疑问,比如:

1、服务器配置到底如何选择

2、模板不好看,如何安装其它模板

3、如何修改当前模板的网站的 Logo

4、网站首页的封面如何设置

5、如何通过域名访问等等

文章来自于站长破壁者投稿!!!

后续将新写一篇文章回答上面的问题,其本质是让大家能够更熟悉的使用,详情查看《苹果 CMS V10 搭建教程二》


📌 转载信息
原作者:
Rosna
转载时间:
2026/1/14 18:14:16

在 vscode 系编辑器中使用 claude code 插件时候,可能会遇到强制登陆的问题:

Claude Code can be used with your Claude subscription or billed
based on API usage through your Console account.

How do you want to log in

这个时候不要慌,你的配置可能没有问题。

打开编辑器设置,找到插件中的 claude code,打开这个选项

再次打开 claude code 就会发现没有登陆提示了


📌 转载信息
转载时间:
2026/1/14 18:14:14

分享下 window10 的包管理工具 winget ,类似 apt、brew

# 关键字搜索
winget find 输入法
# ID 安装,可以多个
winget install Tencent.WeType astral-sh.uv
# 检查更新
winget update
# 更新,可以多个
winget update Tencent.WeType astral-sh.uv
# 本地列表
winget list [可选关键字]
# 查看软件信息
winget show Tencent.WeType

注意默认会安装在系统盘

部分软件的小版本可能不是最新,取决于软件官方更新的速度吧

最好有魔法,部分电脑不知道为什么没有魔法不行,但我有的电脑却可以

最大的好处就是:不用到处找安装包了

我一般的小软件都优先在 winget 搜索安装

如:uv git volta 等工具

不想安装到系统盘的,也可以通过 winget 查找下载连接,手动安装到其他盘


📌 转载信息
原作者:
zhedream
转载时间:
2026/1/14 18:12:47

claude 的 chrome 插件出来有一段时间了,claude code 说可以联动控制 chrome,但我在 wsl 里试了下,直接 “翻车”~(报错:Extension: not detected),无法连接 windows 的 chrome。前两天逛 github 看到个 issue,按大神的法子试了下,终于搞定了~现在 wsl 也能丝滑控制 windows chrome 啦。把踩坑和解决步骤整理出来,供有需要的佬参考~

  1. 先决条件:

    • windows 下 已安装 google chrome 浏览器
    • chrome 已安装 claude in chrome extension 插件,并已登录 claude 账户
    • wsl 下已安装 claude code cli (2.0.73 或更高)
  2. 在 windows, C:\Users\\AppData\Local\Google\Chrome\User Data\NativeMessagingHosts (如果没有 NativeMessagingHosts 目录可自行创建 )下,新建

    • com.anthropic.claude_code_browser_extension.bat:
    @echo off
    wsl.exe -d Ubuntu -- /home/<WSL_USER>/.claude/chrome/chrome-native-host
    
    • com.anthropic.claude_code_browser_extension.json:
    { "name": "com.anthropic.claude_code_browser_extension", "description": "Claude Code Browser Extension Native Host", "path": "C:\\Users\\<USERNAME>\\AppData\\Local\\Google\\Chrome\\User Data\\NativeMessagingHosts\\com.anthropic.claude_code_browser_extension.bat", "type": "stdio", "allowed_origins": [ "chrome-extension://fcoeoabgfenejglbffodgkkbkcdhcgfn/" ] } 

    USERNAME、WSL_USER 分别替换成 windows、wsl 下的对应用户名

  3. 新建 win 注册表文件,install_registry.reg

    Windows Registry Editor Version 5.00
    
    [HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.anthropic.claude_code_browser_extension]
    @="C:\\Users\\<USERNAME>\\AppData\\Local\\Google\\Chrome\\User Data\\NativeMessagingHosts\\com.anthropic.claude_code_browser_extension.json" 

    USERNAME 替换成 windows 用户名,双击导入注册表

  4. 在 wsl 下执行:

    ln -s "/mnt/c/Users/<user>/AppData/Local/Google/Chrome/User Data/Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn" ~/.config/google-chrome/Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn
    

    将 user 替换成 Windows 的用户名。同时需要注意:
    (1) win 下的 claude chrome 插件是否安装在 Users//AppData/Local/Google/Chrome/User Data/Default/Extensions 目录。我最初卡在这儿 (没有找到 Default 目录),后来在本机搜索了下,发现插件安装在了 Users/AppData/Local/Google/Chrome/User Data/Profile1/Extensions 目录;
    (2) wsl 下~/.config/google-chrome 是否存在 Default/Extensions 目录,没有就新建一个。

  5. 登录 claude code 验证(claude --chrome),完结撒花 ~~~

参考链接:[FEATURE] Support Claude in Chrome for WSL environments · Issue #14367 · anthropics/claude-code · GitHub


📌 转载信息
转载时间:
2026/1/14 18:12:37

1. 添加模型提供商 (Model Providers)
要选择 Anthropic 协议!!!!!
要选择 Anthropic 协议!!!!!
要选择 Anthropic 协议!!!!!


2.Augment Proxy 设置,填写可用的 augment 账号信息,测试是否正常,填 url 测试后再填 token (我是这样)


3. 保存配置,启动插件,重新加载窗口。


大佬 @Ank 开发的插件链接
Release augment.vscode-augment@0.746.0 byok-internal · AnkRoot/Augment-BYOK


📌 转载信息
原作者:
lhllinuxdo
转载时间:
2026/1/14 18:06:53

由于下周二 1 月 20 日是 Trae 国际版上线一周年!官方决定在生日前一周开始福利大放送!

今天(1 月 14 日)10:00 起向 Trae 国际版全部用户赠送 Fast Request 权益(不少于 1 个月 Pro 会员的用量)!

面向用户:TRAE 国际版全部用户(Free + Pro)

权益说明:
Free 用户:账号增加 600 次 Fast Request,有效期至 2 月 14 日 10:00
Pro 用户:账号增加 800 次 Fast Request,有效期至 3 月 14 日 10:00

适用范围:TRAE 国际版 IDE + SOLO 模式,权益有效期内所有模型均可使用

领取方式:
访问 Trae 国际版官网 trae.ai,点击主页右上角「Claim Anniversary Gift」按钮,进入活动页面领取


📌 转载信息
转载时间:
2026/1/14 18:06:26

Gemini Business 自动注册 & 2API 上传工具

基于大佬们的开源成果,整合了定时注册自动上传过期剔除等功能。实现全自动化的账号池维护。

核心逻辑优化

  • 自动维护:定时注册并直接传到 2API,自动剔除已过期账号,保留可用账号。
  • 失败重试:修改了注册机逻辑,设定申请 N 个,即使中间失败,也会一直重试直到成功申请到 N 个为止。
  • 丰俭由人:建议每 11 小时注册 2-3 个即可满足个人使用。避免对随机邮箱大佬提供不必要的压力。


感谢各位大佬的无私奉献:


环境准备

请确保已安装 Python 环境,并安装以下依赖库:

pip install undetected-chromedriver selenium beautifulsoup4 requests pystray pillow

配置说明

1. 先去 hf 部署一个 2api, 记住 API 地址admin_key
2. 新建并复制代码生成 py 文件,右键编辑,在顶部的配置区域填入你的 2API 信息:

 # 服务器 API 配置 API_HOST = "请输入你的服务器API地址" ADMIN_KEY = "请输入你的管理员密钥" # 无头模式开关 (True=后台运行无窗口, False=显示浏览器窗口) HEADLESS_MODE = True ##无头注册率会低点,但胜在静默,结合重试其实体验更好。 

运行脚本

在命令行中执行:

py gemini_auto.py

代码如下:

gemini_auto.py
"""
Gemini Business 自动注册上传工具
"""

# 标准库
import sys
import json
import time
import random
from pathlib import Path
from datetime import datetime, timedelta, timezone
from urllib.parse import urlparse, parse_qs
from concurrent.futures import ThreadPoolExecutor

# 第三方库
import requests
from bs4 import BeautifulSoup
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


# ==================== 配置区域 ====================
# 服务器 API 配置
API_HOST = "请输入你的服务器API地址"
ADMIN_KEY = "请输入你的管理员密钥"

# 临时邮箱 API 配置
MAIL_API = "https://mail.chatgpt.org.uk"
MAIL_KEY = "gpt-test"

# Gemini 登录页面
LOGIN_URL = "https://auth.business.gemini.google/login?continueUrl=https:%2F%2Fbusiness.gemini.google%2F&wiffid=CAoSJDIwNTlhYzBjLTVlMmMtNGUxZS1hY2JkLThmOGY2ZDE0ODM1Mg"

# 本地账号文件
ACCOUNTS_FILE = "accounts.json"

# 页面元素定位
XPATH = {
    "email_input": "/html/body/c-wiz/div/div/div[1]/div/div/div/form/div[1]/div[1]/div/span[2]/input",
    "continue_btn": "/html/body/c-wiz/div/div/div[1]/div/div/div/form/div[2]/div/button",
    "verify_btn": "/html/body/c-wiz/div/div/div[1]/div/div/div/form/div[2]/div/div[1]/span/div[1]/button",
}

# 随机姓名池
NAMES = [
    "James Smith", "John Johnson", "Robert Williams", "Michael Brown", "William Jones",
    "David Garcia", "Mary Miller", "Patricia Davis", "Jennifer Rodriguez", "Linda Martinez",
    "Elizabeth Taylor", "Richard Moore", "Susan Wilson", "Joseph Anderson", "Jessica Thomas",
    "Charles Jackson", "Sarah White", "Christopher Harris", "Karen Martin", "Daniel Thompson",
    "Thomas Garcia", "Nancy Martinez", "Matthew Robinson", "Lisa Clark", "Anthony Lewis",
    "Betty Walker", "Mark Young", "Margaret Allen", "Donald King", "Sandra Wright"
]

# 全局停止标志 (用于 GUI 停止任务)
STOP_FLAG = False

# 无头模式开关 (True=后台运行无窗口, False=显示浏览器窗口)
HEADLESS_MODE = True
# ==================================================


# ==================== 工具函数 ====================
def print_log(msg, level="INFO"):
    """统一日志输出格式"""
    icons = {"INFO": "→", "WARN": "⚠", "ERROR": "✗", "OK": "✓"}
    icon = icons.get(level, "•")
    print(f"{icon} {msg}")


def print_separator(char="=", length=80):
    """打印分隔线"""
    print(char * length)


def print_progress(current, total, success, fail, avg_time):
    """打印进度信息"""
    print(f"\n>>> 进度: {current}/{total} | 成功: {success} | 失败: {fail} | 平均耗时: {avg_time:.1f}s")


def log_error(email, error_msg):
    """记录错误到日志文件"""
    error_file = Path("errors.log")
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] 邮箱: {email} | 错误: {error_msg}\n"
    
    try:
        with open(error_file, "a", encoding="utf-8") as f:
            f.write(log_entry)
        print_log(f"错误已记录到 errors.log", "INFO")
    except Exception as e:
        print_log(f"写入错误日志失败: {e}", "WARN")


# ==================== 邮箱管理 ====================
email_queue = []


def create_temp_email():
    """创建临时邮箱地址"""
    try:
        response = requests.get(
            f"{MAIL_API}/api/generate-email",
            headers={"X-API-Key": MAIL_KEY},
            timeout=30
        )
        if response.status_code == 200 and response.json().get('success'):
            email = response.json()['data']['email']
            return email
    except Exception as e:
        print_log(f"邮箱服务异常: {e}", "❌")
    return None


def prefetch_email():
    """预创建邮箱并加入队列"""
    email = create_temp_email()
    if email:
        email_queue.append(email)


def get_email():
    """获取邮箱地址(优先使用队列中的)"""
    if email_queue:
        email = email_queue.pop(0)
        print_log(f"邮箱就绪 → {email}")
        return email
    
    email = create_temp_email()
    if email:
        print_log(f"已生成 → {email}")
    return email


def fetch_verification_code(email, timeout=60):
    """获取邮箱验证码"""
    print_log("等待邮件验证码...")
    start_time = time.time()
    
    while time.time() - start_time < timeout:
        try:
            response = requests.get(
                f"{MAIL_API}/api/emails",
                params={"email": email},
                headers={"X-API-Key": MAIL_KEY},
                timeout=10
            )
            
            if response.status_code == 200:
                emails = response.json().get('data', {}).get('emails', [])
                if emails:
                    html_content = emails[0].get('html_content') or emails[0].get('content', '')
                    soup = BeautifulSoup(html_content, 'html.parser')
                    code_element = soup.find('span', class_='verification-code')
                    
                    if code_element:
                        code = code_element.get_text().strip()
                        if len(code) == 6:
                            print_log(f"验证码 → {code}", "OK")
                            return code
        except:
            pass
        
        elapsed = int(time.time() - start_time)
        print(f"  等待中... ({elapsed}s)", end='\r')
        time.sleep(2)
    
    print_log("验证码超时,请检查网络", "ERROR")
    return None


# ==================== 账号注册 ====================
def save_account_config(email, driver, timeout=10):
    """提取并保存账号配置信息"""
    print_log(f"提取账号配置中(最多 {timeout}s)...")
    start_time = time.time()
    account_data = None

    while time.time() - start_time < timeout:
        cookies = driver.get_cookies()
        current_url = driver.current_url
        parsed_url = urlparse(current_url)

        # 提取 config_id
        url_parts = current_url.split('/')
        config_id = None
        for i, part in enumerate(url_parts):
            if part == 'cid' and i + 1 < len(url_parts):
                config_id = url_parts[i + 1].split('?')[0]
                break

        # 提取关键 cookies
        cookie_map = {c['name']: c for c in cookies}
        session_cookie = cookie_map.get('__Secure-C_SES', {})
        host_cookie = cookie_map.get('__Host-C_OSES', {})

        # 提取 csesidx
        csesidx = parse_qs(parsed_url.query).get('csesidx', [None])[0]

        # 验证所有必需字段
        if all([
            session_cookie.get('value'),
            host_cookie.get('value'),
            csesidx,
            config_id
        ]):
            expiry_timestamp = session_cookie.get('expiry', 0) - 43200
            expires_at = datetime.fromtimestamp(expiry_timestamp).strftime('%Y-%m-%d %H:%M:%S') if expiry_timestamp > 0 else None
            
            account_data = {
                "id": email,
                "csesidx": csesidx,
                "config_id": config_id,
                "secure_c_ses": session_cookie.get('value'),
                "host_c_oses": host_cookie.get('value'),
                "expires_at": expires_at
            }
            
            elapsed = time.time() - start_time
            print_log(f"配置提取完成 ({elapsed:.1f}s)", "OK")
            break

        time.sleep(1)

    if not account_data:
        print_log(f"配置不完整,已跳过 → {email}", "WARN")
        return None

    # 保存到文件
    existing_accounts = []
    if Path(ACCOUNTS_FILE).exists():
        try:
            with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f:
                existing_accounts = json.load(f)
        except:
            pass
    
    existing_accounts.append(account_data)
    
    with open(ACCOUNTS_FILE, 'w', encoding='utf-8') as f:
        json.dump(existing_accounts, f, indent=2, ensure_ascii=False)
    
    print_log(f"已保存 → {ACCOUNTS_FILE}", "OK")
    return account_data


def fast_type(element, text, delay=0.02):
    """快速输入文本"""
    for c in text:
        element.send_keys(c)
        time.sleep(delay)


def register_single_account(driver, executor):
    """注册单个账号 (来自 app.py 的简洁版本)"""
    start_time = time.time()
    email = get_email()
    if not email:
        return None, False, None, 0

    wait = WebDriverWait(driver, 30)

    try:
        # 1. 访问登录页
        driver.get(LOGIN_URL)
        
        # 检测空白页
        time.sleep(2)
        page_source = driver.page_source
        if len(page_source) < 500 or "about:blank" in driver.current_url:
            raise Exception("页面加载空白,需要重启浏览器")

        # 2. 输入邮箱
        print_log("输入邮箱...")
        inp = wait.until(EC.element_to_be_clickable((By.XPATH, XPATH["email_input"])))
        inp.click()
        inp.clear()
        fast_type(inp, email)
        
        # 验证邮箱是否成功输入
        time.sleep(0.3)
        actual_value = inp.get_attribute("value")
        if actual_value != email:
            print_log(f"输入验证失败,清空后重新输入...", "WARN")
            # 清空后用 JS 输入
            driver.execute_script("arguments[0].value = '';", inp)
            time.sleep(0.1)
            driver.execute_script("arguments[0].value = arguments[1];", inp, email)
            # 触发 input 事件
            driver.execute_script("""
                var event = new Event('input', { bubbles: true });
                arguments[0].dispatchEvent(event);
            """, inp)
            time.sleep(0.3)
        
        print_log(f"邮箱 → {email}", "OK")

        # 3. 点击继续
        time.sleep(0.5)
        btn = wait.until(EC.element_to_be_clickable((By.XPATH, XPATH["continue_btn"])))
        driver.execute_script("arguments[0].click();", btn)
        print_log("继续下一步", "OK")

        # 异步预创建下一个邮箱
        executor.submit(prefetch_email)

        # 4. 获取验证码
        time.sleep(2)
        code = fetch_verification_code(email)
        if not code:
            return email, False, None, time.time() - start_time

        # 5. 输入验证码
        time.sleep(1)
        print_log(f"输入验证码 → {code}")
        try:
            pin = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='pinInput']")))
            pin.click()
            time.sleep(0.1)
            fast_type(pin, code, 0.05)
        except:
            try:
                span = driver.find_element(By.CSS_SELECTOR, "span[data-index='0']")
                span.click()
                time.sleep(0.2)
                driver.switch_to.active_element.send_keys(code)
            except Exception as e:
                print_log(f"验证码输入失败: {e}", "ERROR")
                return email, False, None, time.time() - start_time

        # 6. 点击验证
        time.sleep(0.5)
        try:
            vbtn = driver.find_element(By.XPATH, XPATH["verify_btn"])
            driver.execute_script("arguments[0].click();", vbtn)
        except:
            for btn in driver.find_elements(By.TAG_NAME, "button"):
                if '验证' in btn.text:
                    driver.execute_script("arguments[0].click();", btn)
                    break
        print_log("提交验证", "OK")

        # 7. 输入姓名
        print_log("等待姓名输入...")
        selectors = [
            "input[formcontrolname='fullName']",
            "input[placeholder='全名']",
            "input[placeholder='Full name']",
            "input#mat-input-0",
        ]
        name_inp = None

        # 轮询检测姓名输入框
        for _ in range(30):
            for sel in selectors:
                try:
                    name_inp = driver.find_element(By.CSS_SELECTOR, sel)
                    if name_inp.is_displayed():
                        break
                except:
                    continue
            if name_inp and name_inp.is_displayed():
                break
            time.sleep(1)

        if name_inp and name_inp.is_displayed():
            name = random.choice(NAMES)
            name_inp.click()
            time.sleep(0.2)
            name_inp.clear()
            fast_type(name_inp, name)
            print_log(f"姓名 → {name}", "OK")
            time.sleep(0.3)
            name_inp.send_keys(Keys.ENTER)
            time.sleep(1)
        else:
            print_log("未找到姓名输入框", "ERROR")
            return email, False, None, time.time() - start_time

        # 8. 等待进入工作台
        print_log("等待工作台...")
        for _ in range(30):
            time.sleep(1)
            url = driver.current_url
            if 'business.gemini.google' in url and '/cid/' in url:
                print_log("工作台加载完成", "OK")
                break
        else:
            print_log(f"未跳转到工作台 → {driver.current_url}", "WARN")

        # 9. 保存配置
        elapsed = time.time() - start_time
        config = save_account_config(email, driver)
        if config:
            print_log(f"注册成功 → {email} (耗时 {elapsed:.1f}s)", "OK")
            return email, True, config, elapsed
        return email, False, None, elapsed

    except Exception as e:
        print_log(f"注册异常: {e}", "ERROR")
        log_error(email, str(e))
        return email, False, None, time.time() - start_time


# ==================== 账号上传 ====================
class AccountUploader:
    """账号上传管理类"""
    
    def __init__(self, api_host, admin_key):
        self.api_host = api_host.rstrip('/')
        self.admin_key = admin_key
        self.session = requests.Session()
        
    def login(self):
        """登录到服务器"""
        print_log("连接服务器中...")
        login_url = f"{self.api_host}/login"
        
        try:
            response = self.session.post(
                login_url,
                data={"admin_key": self.admin_key},
                allow_redirects=True,
                timeout=30
            )
            
            if len(self.session.cookies) > 0:
                print_log("服务器连接成功", "OK")
                return True
            
            if response.status_code == 200 and '登录' in response.text:
                print_log("密钥验证失败", "ERROR")
                return False
            
            print_log("服务器连接失败", "ERROR")
            return False
                
        except Exception as e:
            print_log(f"连接异常: {e}", "ERROR")
            return False
    
    def upload_and_replace(self, file_path):
        """覆盖上传账号配置"""
        if not Path(file_path).exists():
            print_log(f"文件不存在 → {file_path}", "ERROR")
            return False
        
        print_log(f"读取本地文件 → {file_path}")
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                accounts_data = json.load(f)
        except Exception as e:
            print_log(f"文件读取异常: {e}", "ERROR")
            return False
        
        print_log(f"本地账号 → {len(accounts_data)} 个")
        print_log("开始上传...")
        
        upload_url = f"{self.api_host}/accounts-config"
        
        try:
            response = self.session.put(
                upload_url,
                json=accounts_data,
                timeout=30
            )
            
            if response.status_code == 200:
                result = response.json()
                print_log("上传完成!", "OK")
                print_log(f"{result.get('message', '配置已更新')}")
                print_log(f"服务器账号 → {result.get('account_count', len(accounts_data))} 个")
                
                print()
                print_separator()
                print_log("正在获取服务器账号状态...")
                print_separator()
                self.view_accounts()
                
                return True
            else:
                print_log(f"上传失败,状态码: {response.status_code}", "ERROR")
                return False
                
        except Exception as e:
            print_log(f"上传异常: {e}", "ERROR")
            return False
    
    def upload_and_merge(self, file_path):
        """合并上传账号配置(保留远程正常账号)"""
        print_log("智能合并模式启动...")
        
        # 读取本地账号
        if not Path(file_path).exists():
            print_log(f"本地文件缺失 → {file_path}", "ERROR")
            return False
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                local_accounts = json.load(f)
            print_log(f"本地账号 → {len(local_accounts)} 个")
        except Exception as e:
            print_log(f"读取本地文件失败: {e}", "ERROR")
            return False
        
        # 获取远程账号配置
        print_log("获取远程配置...")
        config_url = f"{self.api_host}/accounts-config"
        
        try:
            response = self.session.get(config_url, timeout=30)
            if response.status_code == 200:
                remote_config = response.json()
                remote_accounts = remote_config.get('accounts', [])
                print_log(f"远程账号 → {len(remote_accounts)} 个")
            else:
                print_log("远程配置获取失败,仅上传本地", "WARN")
                remote_accounts = []
        except Exception as e:
            print_log(f"远程连接异常: {e},仅上传本地", "WARN")
            remote_accounts = []
        
        # 筛选远程正常账号(未过期、未禁用)
        valid_remote_accounts = []
        for account in remote_accounts:
            if account.get('disabled', False):
                continue
            
            expires_at = account.get('expires_at')
            if expires_at and expires_at != '未设置':
                try:
                    beijing_tz = timezone(timedelta(hours=8))
                    expire_time = datetime.strptime(expires_at, "%Y-%m-%d %H:%M:%S")
                    expire_time = expire_time.replace(tzinfo=beijing_tz)
                    current_time = datetime.now(beijing_tz)
                    if expire_time <= current_time:
                        continue
                except:
                    pass
            
            valid_remote_accounts.append(account)
        
        print_log(f"有效远程账号 → {len(valid_remote_accounts)} 个")
        
        # 合并账号(去重)
        merged_accounts = list(valid_remote_accounts)
        remote_ids = {acc.get('id') for acc in valid_remote_accounts}
        
        new_count = 0
        for local_account in local_accounts:
            local_id = local_account.get('id')
            if local_id not in remote_ids:
                merged_accounts.append(local_account)
                new_count += 1
        
        print_log(f"合并结果 → 保留 {len(valid_remote_accounts)} 个,新增 {new_count} 个,共 {len(merged_accounts)} 个")
        
        # 上传合并后的配置
        print_log("上传合并配置...")
        upload_url = f"{self.api_host}/accounts-config"
        
        try:
            response = self.session.put(
                upload_url,
                json=merged_accounts,
                timeout=30
            )
            
            if response.status_code == 200:
                result = response.json()
                print_log("合并上传完成!", "OK")
                print_log(f"{result.get('message', '配置已更新')}")
                print_log(f"服务器账号 → {result.get('account_count', len(merged_accounts))} 个")
                
                print()
                print_separator()
                print_log("正在获取服务器账号状态...")
                print_separator()
                self.view_accounts()
                
                return True
            else:
                print_log(f"上传失败,状态码: {response.status_code}", "ERROR")
                return False
                
        except Exception as e:
            print_log(f"上传异常: {e}", "ERROR")
            return False
    
    def view_accounts(self):
        """查看远程账号状态"""
        print_log("查询远程账号...")
        
        view_url = f"{self.api_host}/accounts"
        
        try:
            response = self.session.get(view_url, timeout=30)
            
            if response.status_code == 200:
                data = response.json()
                accounts = data.get('accounts', [])
                total = data.get('total', len(accounts))
                
                if not accounts:
                    print_log("远程无账号配置", "INFO")
                    return True
                
                print(f"\n共 {total} 个账号")
                print_separator("=", 120)
                
                # 表头
                print(f"{'序号':<6} {'账号ID':<35} {'状态':<12} {'过期时间':<22} {'剩余时长':<15} {'累计对话':<10}")
                print_separator("-", 120)
                
                # 账号列表
                for i, account in enumerate(accounts, 1):
                    acc_id = account.get('id', 'N/A')
                    status = account.get('status', 'N/A')
                    expires_at = account.get('expires_at', '未设置')
                    remaining = account.get('remaining_display', 'N/A')
                    conversations = account.get('conversation_count', 0)
                    
                    if len(acc_id) > 33:
                        acc_id = acc_id[:30] + "..."
                    
                    print(f"{i:<6} {acc_id:<35} {status:<12} {expires_at:<22} {remaining:<15} {conversations:<10}")
                
                print_separator("=", 120)
                return True
            else:
                print_log(f"查询失败 → 状态码 {response.status_code}", "ERROR")
                return False
                
        except Exception as e:
            print_log(f"查询异常: {e}", "ERROR")
            return False


# ==================== 主程序流程 ====================
def run_batch_registration(target_count):
    """批量注册账号 (保底成功数模式)"""
    print()
    print_separator()
    print(f"目标: 成功注册 {target_count} 个账号")
    print_separator()
    print()
    
    # 清空旧文件
    if Path(ACCOUNTS_FILE).exists():
        Path(ACCOUNTS_FILE).unlink()
        print_log(f"已清空 → {ACCOUNTS_FILE}")
    
    driver = None
    executor = ThreadPoolExecutor(max_workers=2)
    success_count = 0
    fail_count = 0
    attempt_count = 0
    total_time = 0
    success_times = []

    # 预创建第一个邮箱
    executor.submit(prefetch_email)
    
    # 连续失败计数器(用于保护机制)
    consecutive_fails = 0
    MAX_CONSECUTIVE_FAILS = 20

    # 循环直到成功数达到目标
    while success_count < target_count:
        # 检查全局停止标志
        global STOP_FLAG
        if STOP_FLAG:
            print_log("收到停止信号,中止任务", "WARN")
            STOP_FLAG = False  # 重置标志
            break
        
        # 连续失败保护
        if consecutive_fails >= MAX_CONSECUTIVE_FAILS:
            print_log(f"连续失败 {MAX_CONSECUTIVE_FAILS} 次,中止本轮任务", "ERROR")
            break
        
        attempt_count += 1
        current_target = target_count + fail_count  # 动态调整显示的总数
        
        print()
        print_separator("#", 60)
        print(f"正在注册第 {attempt_count} 个账号 (成功: {success_count}/{target_count})")
        print_separator("#", 60)
        print()

        # 确保浏览器可用
        if driver is None:
            options = uc.ChromeOptions()
            if HEADLESS_MODE:
                print_log("启动无头浏览器...")
                options.add_argument("--headless=new")
                options.add_argument("--disable-gpu")
                options.add_argument("--no-sandbox")
                options.add_argument("--window-size=1200,800")
            else:
                print_log("启动浏览器...")
            driver = uc.Chrome(options=options, use_subprocess=True)
            if not HEADLESS_MODE:
                driver.set_window_size(100, 200)
                driver.set_window_position(50, 50)
            time.sleep(1)
        else:
            try:
                _ = driver.current_url
            except:
                print_log("浏览器已关闭,重启中...")
                try: 
                    driver.quit()
                except: 
                    pass
                options = uc.ChromeOptions()
                if HEADLESS_MODE:
                    options.add_argument("--headless=new")
                    options.add_argument("--disable-gpu")
                    options.add_argument("--no-sandbox")
                    options.add_argument("--window-size=1200,800")
                driver = uc.Chrome(options=options, use_subprocess=True)
                if not HEADLESS_MODE:
                    driver.set_window_size(100, 200)
                    driver.set_window_position(50, 50)
                time.sleep(1)

        try:
            email, success, config, elapsed = register_single_account(driver, executor)
            total_time += elapsed
            
            if success and config:
                success_count += 1
                success_times.append(elapsed)
                consecutive_fails = 0  # 重置连续失败计数
                print_log(f"进度: {success_count}/{target_count} 完成", "OK")
            else:
                fail_count += 1
                consecutive_fails += 1
                print_log(f"失败 +1 (连续失败: {consecutive_fails}/{MAX_CONSECUTIVE_FAILS})", "WARN")
                
        except Exception as e:
            error_msg = str(e).lower()
            print_log(f"注册异常: {e}", "ERROR")
            fail_count += 1
            consecutive_fails += 1
            
            # 检测空白页或页面加载问题
            if "blank" in error_msg or "timeout" in error_msg or "element" in error_msg:
                print_log("检测到页面异常,重启浏览器...", "WARN")
                if driver:
                    try: 
                        driver.quit()
                    except: 
                        pass
                    driver = None
            elif driver:
                try: 
                    driver.quit()
                except: 
                    pass
                driver = None

        avg_time = total_time / attempt_count if total_time > 0 else 0
        print_progress(success_count, target_count, success_count, fail_count, avg_time)

        if success_count < target_count and driver:
            try:
                driver.delete_all_cookies()
            except:
                pass
            time.sleep(random.randint(2, 3))

    executor.shutdown(wait=False)
    if driver:
        try: 
            driver.quit()
        except: 
            pass
        
        # Monkeypatch: 防止 __del__ 再次调用 quit 导致 WinError 6
        try:
            driver.quit = lambda: None
        except:
            pass
            
        driver = None

    # 统计信息
    avg_time = sum(success_times) / len(success_times) if success_times else 0
    min_time = min(success_times) if success_times else 0
    max_time = max(success_times) if success_times else 0
    
    print()
    print_separator()
    print(f"注册完成! 目标: {target_count}, 成功: {success_count}, 失败: {fail_count}, 总尝试: {attempt_count}")
    print(f"总耗时: {total_time:.1f}s | 平均: {avg_time:.1f}s | 最快: {min_time:.1f}s | 最慢: {max_time:.1f}s")
    print(f"账号已保存至: {ACCOUNTS_FILE}")
    print_separator()
    
    return {
        "success": success_count,
        "fail": fail_count,
        "attempts": attempt_count,
        "avg_time": avg_time,
        "success_times": success_times,
        "is_ok": success_count > 0
    }


def handle_task_execution(count, upload_mode, uploader):
    """执行一次完整的任务(注册+上传)"""
    stats = run_batch_registration(count)
    
    if stats.get('is_ok'):
        print()
        # 先登录再上传
        if not uploader.login():
            print_log("服务器登录失败,无法上传", "ERROR")
            return stats
        
        if upload_mode == 'replace':
            print_log("开始覆盖上传到服务器...")
            uploader.upload_and_replace(ACCOUNTS_FILE)
        elif upload_mode == 'merge':
            print_log("开始合并上传到服务器...")
            uploader.upload_and_merge(ACCOUNTS_FILE)
    else:
        print_log("注册流程未成功,取消上传", "WARN")
        
    return stats


def main():
    """主程序入口"""
    print_separator()
    print("Gemini Business 自动注册上传工具")
    print_separator()
    print()
    
    uploader = AccountUploader(API_HOST, ADMIN_KEY)
    
    # 登录服务器
    if not uploader.login():
        print_log("登录失败,无法继续", "ERROR")
        input("\n按回车键退出...")
        sys.exit(1)
    
    print()
    
    while True:
        print("\n请选择操作:")
        print("  1. 注册上传")
        print("  2. 查看远程账号状态")
        print("  3. 退出")
        print()
        
        choice = input("请输入选项 (1-3): ").strip()
        
        if choice == "1":
            # 确定上传模式
            upload_mode = 'merge'
            
            # 1. 询问数量
            count_str = input("\n请输入注册数量 (默认 5): ").strip()
            count = int(count_str) if count_str else 5
            
            # 2. 询问执行模式
            print("\n请选择执行模式:")
            print("  1. 立即执行一次")
            print("  2. 定时循环执行 (支持自定义间隔)")
            mode_choice = input("请输入选项 (1-2): ").strip()
            
            if mode_choice == "2":
                # 定时模式
                hours_str = input("\n请输入循环间隔小时 (默认 12): ").strip()
                try:
                    interval_hours = float(hours_str) if hours_str else 12.0
                except:
                    print_log("输入无效,使用默认值 12 小时", "WARN")
                    interval_hours = 12.0
                
                print(f"\n已选择: 定时循环模式 (间隔 {interval_hours} 小时)")
                run_now_str = input("是否在开始循环前立即运行一次? (y/n, 默认 y): ").strip().lower()
                run_now = run_now_str != 'n'
                
                print_log("定时任务已启动! 按 Ctrl+C 可随时停止", "INFO")
                
                loop_count = 0
                while True:
                    loop_count += 1
                    
                    if run_now or loop_count > 1:
                        print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] >>> 开始第 {loop_count} 次循环任务")
                        handle_task_execution(count, upload_mode, uploader)
                        print_log(f"第 {loop_count} 次任务完成", "INFO")
                    else:
                        print_log("跳过首次运行,直接进入等待", "INFO")

                    # 计算下次运行时间
                    next_run = datetime.now() + timedelta(hours=interval_hours)
                    print_log(f"下一次任务将在 {next_run.strftime('%Y-%m-%d %H:%M:%S')} 开始", "INFO")
                    
                    # 倒计时等待
                    total_seconds = int(interval_hours * 3600)
                    try:
                        while total_seconds > 0:
                            # 每分钟更新一次状态,显示剩余时间
                            if total_seconds % 60 == 0:
                                pass 
                            time.sleep(1)
                            total_seconds -= 1
                    except KeyboardInterrupt:
                        print("\n")
                        print_log("检测到中断, 停止定时任务", "WARN")
                        break
                        
            else:
                # 立即执行模式 (默认)
                print()
                handle_task_execution(count, upload_mode, uploader)
                
        elif choice == "2":
            print()
            uploader.view_accounts()
            
        elif choice == "3":
            print("\n再见!")
            break
            
        else:
            print_log("无效选项,请重试", "WARN")
        
        print()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n")
        print_log("用户中断程序", "INFO")
    except Exception as e:
        print_log(f"程序异常: {e}", "ERROR")
        input("\n按回车键退出...")


📌 转载信息
原作者:
zding
转载时间:
2026/1/14 18:06:03

一个简单易用的局域网唤醒工具,支持通过 Web 界面管理主机、发送 WOL 唤醒包、检测服务器状态以及测试端口连通性。

功能特性

  • 主机管理(添加、编辑、删除)
  • WOL 唤醒发送 Magic Packet
  • 服务器状态检测(ping/SSH/RDP 并发检测)
  • 端口连通性测试(3 秒超时)
  • 数据导入导出(JSON 格式)
  • 密码保护功能
  • 深色 / 浅色主题切换,兼容移动端

项目地址

https://github.com/dhjz/dwol

功能示例图



📌 转载信息
原作者:
ddjhh
转载时间:
2026/1/14 18:03:56