Full Code of leoxiaoping/pbottleRPA for AI

master 85516c89261a cached
120 files
244.6 KB
97.8k tokens
134 symbols
1 requests
Download .txt
Showing preview only (365K chars total). Download the full file or copy to clipboard to get everything.
Repository: leoxiaoping/pbottleRPA
Branch: master
Commit: 85516c89261a
Files: 120
Total size: 244.6 KB

Directory structure:
gitextract_s9rnej04/

├── .gitignore
├── GPT图像解析示例.js
├── GPT问题答案AI生成演示.js
├── LICENSE
├── README.md
├── WEB增强-数据批量爬取演示.js
├── WEB增强-浏览器元素操作演示.js
├── WEB增强-账号密码登录演示.js
├── [企业版]HID硬件级键盘鼠标演示.js
├── [企业版]外部控制能力.js
├── [企业版]屏幕物体查找Ai演示.js
├── [企业版]接力执行脚本.js
├── [企业版]集群控制中心日志回传.js
├── [企业版]集群控制中心流程升级 .js
├── [企业版]集群控制中心示例.js
├── [第三方 模块卸载].bat
├── [第三方 模块安装].bat
├── [第三方] 读写Excel演示脚本.mjs
├── [第三方] 读写word演示脚本.mjs
├── docs/
│   ├── -图片测试.md
│   ├── .vitepress/
│   │   └── config.mjs
│   ├── AI生成流程脚本.md
│   ├── APIAI图像.md
│   ├── APIAI大模型.md
│   ├── API办公文档.md
│   ├── API压缩解压.md
│   ├── API声音.md
│   ├── API外部控制.md
│   ├── API屏幕.md
│   ├── API浏览器增强.md
│   ├── API用户输入.md
│   ├── API系统相关.md
│   ├── API统一规范.md
│   ├── API网络.md
│   ├── API通用工具.md
│   ├── API键盘操作.md
│   ├── API键鼠硬模拟.md
│   ├── API鼠标操作.md
│   ├── Demo示例.md
│   ├── HTTP静态服务.md
│   ├── SaaS系统自动化任务.md
│   ├── index.md
│   ├── package.json
│   ├── public/
│   │   └── index.html
│   ├── win7操作系统.md
│   ├── ‌Q&A.md
│   ├── 专用自动化独立软件.md
│   ├── 业务管理系统.md
│   ├── 中文调用.md
│   ├── 信创操作系统.md
│   ├── 其他功能模块.md
│   ├── 定时启动.md
│   ├── 开机启动.md
│   ├── 手机应用的自动化.md
│   ├── 无尽模式.md
│   ├── 更多三方功能和拓展.md
│   ├── 桌面快捷方式.md
│   ├── 流程录制.md
│   ├── 流程运行日志.md
│   ├── 流程配置项.md
│   ├── 热键和快捷方式.md
│   ├── 用 js 脚本开发自动化流程.md
│   ├── 用 python 脚本开发自动化流程.md
│   ├── 硬件键鼠模拟.md
│   ├── 老旧低配电脑.md
│   ├── 视频教程.md
│   ├── 集群控制中心.md
│   └── 验证码自动化.md
├── package.json
├── pbottleRPA.js
├── python示例/
│   ├── GPT图像解析示例.py
│   ├── GPT问题答案AI生成演示.py
│   ├── WEB增强-数据批量爬取演示.py
│   ├── WEB增强-浏览器元素操作演示.py
│   ├── WEB增强-账号密码登录演示.py
│   ├── [企业版]接力执行脚本.py
│   ├── [第三方] 读写Excel演示脚本.py
│   ├── [第三方] 读写word演示脚本.py
│   ├── pbottleRPA.py
│   ├── 上传(发送)文件演示.py
│   ├── 下载文件示例演示.py
│   ├── 剪切板演示脚本.py
│   ├── 压缩和解压缩示例.py
│   ├── 发送Email电子邮件.py
│   ├── 发送运维消息手机通知.py
│   ├── 图片相似度检测.py
│   ├── 基础(循环、判断、等待)演示.py
│   ├── 屏幕物体轮廓查找演示.py
│   ├── 常用工具 Utils 演示.py
│   ├── 异步子流程模板.py
│   ├── 微信朋友圈自动点赞.py
│   ├── 快速开始演示(3行代码).py
│   ├── 截屏操作演示脚本.py
│   ├── 文件基础操作演示.py
│   ├── 文字提取查找OCR演示.py
│   ├── 用户手动输入变量示例.py
│   ├── 运维消息手机通知.py
│   ├── 键盘基本操作演示脚本.py
│   └── 鼠标基础操作演示.py
├── word测试文档.docx
├── 上传(发送)文件演示.js
├── 下载文件示例演示.js
├── 企业版 Demo示例说明.txt
├── 剪切板演示脚本.js
├── 压缩和解压缩示例.js
├── 发送Email电子邮件.js
├── 发送运维消息手机通知.js
├── 图片相似度检测.js
├── 基础(循环、判断、等待)演示.js
├── 屏幕物体轮廓查找演示.js
├── 常用工具 Utils 演示.js
├── 异步子流程模板.js
├── 微信朋友圈自动点赞.js
├── 快速开始演示(3行代码).js
├── 截屏操作演示脚本.js
├── 文件基础操作演示.js
├── 文字提取查找OCR演示.js
├── 用户手动输入变量示例.js
├── 键盘基础操作演示脚本.js
└── 鼠标基础操作演示.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/**/node_modules
/**/__pycache__
node_modules
Excel测试表格.xlsx
/**/Excel测试表格.xlsx
单元测试test.js
test.js
test.mjs
配置项.json
package-lock.json
node_modules/
docs/.vitepress/cache
docs/.vitepress/dist
package-lock.json
package-lock.json


================================================
FILE: GPT图像解析示例.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了RPA中的GPT图像解析功能,可以向云端AI提问关于图片内容的问题
 * 通过这个示例,您可以学习如何结合AI能力分析和理解图片内容
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限

// 开始RPA操作

let ask = '描述图片中有什么?'                 // 定义要向AI提出的问题
console.log(ask,`./input/RPAlogo128.png`);    // 在控制台输出问题和要分析的图片路径

const start = Date.now()                      // 记录开始时间,用于计算处理耗时
pbottleRPA.log('云端 AI 生成答案:')            // 将提示信息输出到日志文件中
// 调用云端GPT图像分析API,传入问题和图片路径,并输出结果内容
console.log(pbottleRPA.cloud_GPTV(ask,`./input/RPAlogo128.png`).content) 
console.log('图片解析耗时:(毫秒)',Date.now()-start); // 计算并输出图片解析耗时(毫秒)

================================================
FILE: GPT问题答案AI生成演示.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了RPA中的GPT问题答案AI生成功能
 * 通过这个示例,您可以学习如何向云端AI提问并获取答案,实现智能问答功能
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限


// 定义要向AI提问的问题列表
let asks = [
    '鲁迅为什么要打周树人?',                 // 第一个问题:关于鲁迅的幽默问题
    '给我随便作一首诗吧',                     // 第二个问题:要求AI创作诗歌
    '你是谁?',                              // 第三个问题:询问AI身份
]

const start = Date.now()                      // 记录开始时间,用于计算处理所有问题的总耗时

// 使用循环遍历问题列表,逐个向AI提问
for (let index = 0; index < asks.length; index++) { // 遍历问题数组
    const ask = asks[index];                  // 获取当前问题
    
    pbottleRPA.log(`❓️ 问题 ${index+1}:`,ask);   // 将问题序号和内容输出到日志文件中
    pbottleRPA.tts(ask)                       // 使用文字转语音功能播报当前问题

    // 调用云端GPT API获取问题答案
    let rs = pbottleRPA.cloud_GPT(ask,0)      // 向云端AI提问,获取答案
    pbottleRPA.log('云端 AI 生成答案:')        // 在日志中输出提示信息
    pbottleRPA.log(rs.content)                // 将AI生成的答案内容输出到日志文件中
    pbottleRPA.log('------------')            // 输出分隔线
    pbottleRPA.log()                          // 输出空行,美化日志格式
}

// 输出所有问题答案生成的总耗时
console.log('答案全部生成耗时(毫秒):',Date.now()-start);

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 LEO

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
#  小瓶RPA

专业用户的专业RPA+AI软件。

### 介绍
小瓶RPA,长难业务自动化流程专精。 轻量级简单全能的RPA软件,显著降本增效 & 工作100%准确 & 非侵入式集成。同时支持浏览器web应用和客户端应用的操作流程自动化。同时支持 Js 和 Python 两种脚本制作流程。

 **如果好用或者帮到您,烦劳star一下。** 

产品官网:[https://rpa.pbottle.com/](https://rpa.pbottle.com/)

![小瓶RPA logo](input/RPAlogo128.png)




### 起步

```javascript
pbottleRPA.打开网址('https://www.baidu.com/')
pbottleRPA.粘贴输入('小瓶RPA官网')
pbottleRPA.键盘按键('enter')
```


### 文档入口 【新】

[https://rpa.pbottle.com/docs/](https://rpa.pbottle.com/docs/)


### 小瓶RPA优势

1. 自动化、AI、大模型能力可以快速落实到工作流程,并能精细化调整。
2. 轻量级 + 开放接口能力,无缝整合现有工作系统,而非高成本替代。
3. 纯视觉模拟驱动,可以兼容操作所有应用程序。
4. 技术运维人员友好,主流脚本语言表达,高天花板。


### 产品设计常见疑问

❓︎ 为什么小瓶没有图形拖拽的所谓'设计器',用编写js脚本来替代?

- 图形编程历史已久,上手门槛虽然更低,但同时天花板也更低,目前面对长难流程的开发管理往往不能满足和有更高综合成本。(有商业价值的自动化项目往往都是长难流程)

- js脚本可以融入完整的 Nodejs(Python)生态,无缝引入万亿第三方功能包,同时可以使用 git 等代码工程管理工具和传统IT项目自然对接。

- V2023.3版本 新增加鼠标操作录制自动生成简单的脚本功能。
  
- 🔥 AI 对话大模型 生成小瓶RPA流程脚本   [查看](https://rpa.pbottle.com/docs/AI%E7%94%9F%E6%88%90%E6%B5%81%E7%A8%8B%E8%84%9A%E6%9C%AC.html)


❓︎ 为什么小瓶采用js作为脚本语言,可以用 python、java、c# 等吗?

- 可以,小瓶RPA内核和脚本层采用http通信接口,虽然官方的demo脚本层选用了js(同时兼容性支持Python),但有经验的其他语言开发者,可以随时通过最常规的http接口接入小瓶RPA。

- JS目前是前端的主流脚本,有大量的web经验的群众基础。同时JS的前端开放性,GPT等大模型AI工具更容易输出准确的结果。


❓︎ 可以脱网或者内网使用小瓶RPA吗?

- 企业版支持,个人版启动一次联网免费授权。



### 软件架构

![小瓶RPA架构图](https://images.gitee.com/uploads/images/2021/1126/130823_ef4a3e3b_799608.png "小瓶RPA架构图")


### 公司支持

北京小瓶科技有限公司,对商业版客户提供技术支持和增值服务

官网:[https://www.pbottle.com/](https://www.pbottle.com/)


### 使用须知

1. 允许个人永久免费使用本项目,包括用于个人学习、游戏娱乐、毕业设计、教学案例、公益事业和其他非商业用途;不包含个人的上班时的工作目的;

2. 必须保留版权信息,请自觉遵守;

3. 未经授权禁止将本软件、代码和其他资源以任何形式出售(包含收费项目捆绑的免费部分);


# 安装教程

### 步骤

1.  下载exe运行基座绿色版  pbottleRPA.zip  [基座exe程序绿色版](https://rpa.pbottle.com/)
2.  安装 NodeJS 脚本引擎 .msi,并安装 [下载网站](https://rpa.pbottle.com/a-13943.html)   安装后请重启基座
3.  下载 [测试脚本](https://gitee.com/pbottle/pbottle-rpa/repository/archive/master.zip),运行基座选择demo脚本即可开始运行

### 常见问题

1. 如果系统提示 缺少vcruntime140XX.dll   微软官网下载安装即可:[https://docs.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?view=msvc-140](https://docs.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?view=msvc-140)  (新版本已兼容)

 1. exe启动目录的路径不能含中文,直接复制到其他目录,或者文件夹copy到磁盘根目录即可  (新版本已兼容)

 1. 服务端口(49888)监听不成功问题, 排查请看:[https://rpa.pbottle.com/a-13924.html](https://rpa.pbottle.com/a-13924.html)

 1. 软件不再支持32位老旧版操作系统


### 电脑要求

新!V2023版本进一步提高了AI算法库对电脑的兼容性,请下载最新版本。

win7系统注意事项:https://rpa.pbottle.com/a-13941.html


### 手机应用RPA

1. 手机应用可以采用 Android 模拟器方案 或者 真机投屏交互方案。得益于小瓶RPA采用纯图像识别的驱动方式,完全兼容各种手机应用模拟器 和 手机厂商的镜像投屏
可以使用除了web增强插件外的任意api接口能力

1. 模拟器方案:
经过我们测试的推荐: 蓝叠
已经有问题的:雷电模拟器,剪切板同步延迟,输入有问题

1. 真机投屏交互方案:
vivo办公套件
华为智慧互联


### Web应用浏览器增强

web增强可以使小瓶RPA脚本直接操作浏览器Dom元素,更方便快捷

同时支持使用Dom选择器选择元素并返回结果

 _注意:此功能需要安装小瓶RPA浏览器插件,版本需求:V2023.5以后支持_ 

插件安装下载地址:[https://rpa.pbottle.com/a-13942.html](https://rpa.pbottle.com/a-13942.html)


### Demo示例

自带Demo示例:(中文标题为demo示例脚本 后续会添加更多)


注意:

1. 【web增强】需要先安装小瓶RPA浏览器插件
2. 【第三方】需要先双击  _第三方模块安装.bat_  安装所需模块。


### 全局热键


 **Ctrl + Shift + Q** 

- 停止当前的脚本运行   结束当前的鼠标操作录制

 **Ctrl + Shift + R** 

- 重新启动当前的脚本运行



# AI 模型的使用

### 本地模型
1. OCR文字识别  示例: [文字提取查找OCR演示.js](文字提取查找OCR演示.js)
2. 对象识别模型  示例:[GPT图像解析示例](GPT图像解析示例.js)
   
### 云端大模型
1. 大语言模型  示例: [文字提取查找OCR演示.js](文字提取查找OCR演示.js)
2. 对象识别模型 示例: [GPT图像解析示例.js](GPT图像解析示例.js)



# 开发流程脚本

### RPA脚本开发文档

文档入口:

[https://rpa.pbottle.com/docs/](https://rpa.pbottle.com/docs/)

### 支持脚本语言

1. NodeJS
2. Python(Beta)

### 其它参考

1. 键盘表  [https://rpa.pbottle.com/a-13862.html](https://rpa.pbottle.com/a-13862.html)
2. 挂机定时任务  [https://rpa.pbottle.com/a-13868.html](https://rpa.pbottle.com/a-13868.html)
3. 免费手机通知 [https://rpa.pbottle.com/a-12586.html](https://rpa.pbottle.com/a-12586.html)

### 技术交流微信群

喜欢群聊的可以微信扫码加入(永不过期):

![小瓶RPA技术交流群二维码](input/discuss.jpg)

### 官方RPA增值服务

 [联系我们](https://rpa.pbottle.com/value-added.php) 

### 小瓶RPA招聘

英雄不问出处,各路英才简历投递方式:

打开小瓶RPA官网,浏览器console控制台查看。



================================================
FILE: WEB增强-数据批量爬取演示.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了使用Web增强功能批量爬取网页数据
 * 需要安装小瓶RPA浏览器插件来操作网页元素,实现网页数据的自动化抓取
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限

console.log(pbottleRPA.getTime());            // 在控制台输出当前格式化时间

console.log("=== ※※※※※※※※※ ===");
console.log("=== 需要安装 小瓶RPA 浏览器插件 ==="); // 提示用户需要安装浏览器插件
console.log("=== ※※※※※※※※※ ===");

// 使用文字转语音功能提示用户必须安装浏览器插件
pbottleRPA.tts('必须安装小瓶RPA浏览器增强插件,5秒后开始爬取网页数据')
// 显示系统消息框再次提醒用户
pbottleRPA.showMsg('提示:','必须先安装浏览器增强插件')

// 等待5秒钟给用户准备时间
pbottleRPA.wait(5)

// 打开小瓶RPA官网用于演示数据爬取
pbottleRPA.openURL('https://rpa.pbottle.com/')
pbottleRPA.browserCMD_waitPageReady('https://rpa.pbottle.com/');  // 等待页面加载完成

// 判断页面是否成功打开,通过检查页面中包含"小瓶RPA"的元素数量
let n_rpa = pbottleRPA.browserCMD_count('span:contains(小瓶RPA)')
console.log('包含 小瓶RPA 元素数量:',n_rpa);  // 输出元素数量到控制台
// 模拟滚动页面,加载更多内容
pbottleRPA.keyTap('page down')               // 向下翻页
pbottleRPA.keyTap('page down')               // 再次向下翻页
pbottleRPA.keyTap('page down')               // 第三次向下翻页

// 开始获取网页上的数据
// 使用CSS选择器获取所有class为'list-group-item'的a标签的文本内容
let rs = pbottleRPA.browserCMD_text('a.list-group-item')
// 检查是否超时(说明插件未安装或网络问题)
if (rs == '20s超时') {
    // 显示错误消息并退出脚本
    pbottleRPA.showMsg('出现错误:','必须先安装浏览器增强插件和联网')
    pbottleRPA.exit()
}
// 将获取到的JSON字符串数据解析为JavaScript对象数组
datas = JSON.parse(rs)

console.log('爬取数据数量:',datas.length);     // 输出爬取到的数据条数
// 使用文字转语音功能播报爬取到的数据数量
pbottleRPA.tts('爬取数据'+ datas.length +'条,请查看日志')
pbottleRPA.wait(4)                           // 等待4秒钟

console.log('=====');                        // 输出分隔线
console.log('数据列表:');                    // 输出提示信息
// 遍历数据数组,处理并输出每条数据
datas.forEach(element => {
    // 去除首尾空格并移除换行符
    element = element.trim().replace(/[\r\n]/g, '');
    console.log(element);                    // 输出处理后的数据
});

// 获取网页元素的href属性值(链接地址)
rs = pbottleRPA.browserCMD_attr('a.list-group-item','href')
// 将获取到的JSON字符串解析为链接数组
datas = JSON.parse(rs)
console.log('====');                         // 输出分隔线
console.log('链接列表:');                    // 输出提示信息
// 遍历链接数组并输出每个链接
datas.forEach(element => {
    console.log(element);                    // 输出链接地址
});

// 使用文字转语音功能播报演示结束
pbottleRPA.tts('演示结束')

pbottleRPA.browserCMD.alert('演示结束,数据已经批量输出到日志中,请查看控制台');

================================================
FILE: WEB增强-浏览器元素操作演示.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了使用Web增强功能操作浏览器元素的各种方法
 * 包括网址跳转、文本获取、Cookie操作、CSS样式修改、元素值设置、点击操作等
 * 需要安装小瓶RPA浏览器插件来实现这些功能
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限

console.log(Date());                          // 在控制台输出当前日期时间

console.log("=== ※※※※※※※※※ ===");
console.log("=== 需要安装 小瓶RPA 浏览器插件 ==="); // 提示用户需要安装浏览器插件
console.log("=== ※※※※※※※※※ ===");

// 使用文字转语音功能提示用户必须安装浏览器插件
pbottleRPA.tts('必须安装小瓶RPA浏览器增强插件,手动点击确定继续')
// 显示系统消息框再次提醒用户
pbottleRPA.showMsg('提示:','必须先安装浏览器增强插件')
// 打开百度网站用于演示浏览器操作
pbottleRPA.openURL('https://www.baidu.com/')

// 定义变量用于接收浏览器命令的返回值
let ret = ""
// 使用浏览器命令显示弹窗,等待用户手动确认(20秒超时)
ret = pbottleRPA.browserCMD_alert('来自小瓶RPA的问候,手动点击确定开始,20秒超时')
console.log('返回操作结果 alert',ret);        // 输出操作结果到控制台

// 检查浏览器插件是否正常工作
if (ret !== 'ok') {
    console.log('没有检测到小瓶RPA浏览器插件',ret); // 如果未检测到插件,输出错误信息
    process.exit(1)                           // 退出脚本
}

pbottleRPA.wait(1)                            // 等待1秒钟
pbottleRPA.tts("跳转新网址:")                 // 语音播报即将执行的操作
// 使用浏览器命令跳转到新的网址
pbottleRPA.browserCMD_url('https://www.baidu.com/?from=pbottleRPA')
pbottleRPA.wait(2)                            // 等待2秒钟

// 获取指定元素的文本内容(页面标题)
ret = pbottleRPA.browserCMD_text('span.title-content-title')
console.log('返回操作结果【一次多个】',ret);    // 输出获取到的文本内容

// Cookie操作演示
ret = pbottleRPA.browserCMD_cookie('BAIDUID') // 获取指定名称的Cookie值
console.log('返回操作结果 cookieGet',ret);     // 输出获取到的Cookie值
// 设置Cookie值
ret = pbottleRPA.browserCMD_cookie('pbottleID',"good",3) 
console.log('返回操作结果 cookieSet',ret);     // 输出设置Cookie的结果

// CSS样式操作演示 - 变换背景色
pbottleRPA.tts('变换背景色')                  // 语音播报即将执行的操作
// 设置body元素的背景色为蓝色
ret = pbottleRPA.browserCMD_css('body',"background-color",'blue')
console.log('返回操作结果 cssSet',ret);       // 输出设置结果
// 获取body元素的背景色值
ret = pbottleRPA.browserCMD_css('body',"background-color")
console.log('返回操作结果【颜色值】',ret);      // 输出获取到的颜色值
// 将body元素的背景色重新设置为白色
ret = pbottleRPA.browserCMD_css('body',"background-color",'white')
console.log('返回操作结果 cssSet',ret);       // 输出设置结果

// 文本内容操作演示
ret = pbottleRPA.browserCMD_text('title')     // 获取页面标题文本
console.log('返回操作结果 textGet',ret);      // 输出获取到的标题文本
pbottleRPA.tts('获取标题 ')                   // 语音播报操作内容
pbottleRPA.wait(3)                            // 等待3秒钟

// 设置页面标题演示
pbottleRPA.tts('设置页面标题 ')               // 语音播报即将执行的操作
// 设置新的页面标题,加上前缀"[小瓶RPA]-"
ret = pbottleRPA.browserCMD_text('title','[小瓶RPA]-'+ret)
console.log('返回操作结果 textSet',ret);      // 输出设置结果
ret = pbottleRPA.browserCMD_text('title')     // 重新获取页面标题
console.log('当前页面标题:',ret);             // 输出当前页面标题
pbottleRPA.wait(3)                            // 等待3秒钟

// 搜索操作演示
pbottleRPA.tts('输入搜索词 点击搜索按钮 ')     // 语音播报即将执行的操作
// 在搜索框中输入搜索词"小瓶RPA"
pbottleRPA.paste('小瓶RPA官网')   // 输出输入操作结果

// 点击搜索按钮
ret = pbottleRPA.browserCMD_click('#su')      // 点击百度搜索按钮
console.log('返回点击操作结果 click',ret);     // 输出点击操作结果
pbottleRPA.wait(3)                            // 等待3秒钟

// 获取当前网址演示
pbottleRPA.tts('获取当前网址:')              // 语音播报即将执行的操作
ret = pbottleRPA.browserCMD_url()             // 获取当前页面URL
console.log('获取当前网址:',ret);             // 输出当前网址
pbottleRPA.wait(2)                            // 等待2秒钟

// 去广告操作演示
pbottleRPA.tts('开始去广告')                  // 语音播报即将执行的操作
// 循环执行去广告操作(示例中只执行1次)
for (let index = 0; index < 1; index++) {
    // 移除指定的广告元素
    ret = pbottleRPA.browserCMD_remove('#content_left div:first')
    console.log('返回点击操作结果 remove',ret); // 输出移除操作结果
    pbottleRPA.wait(3)                        // 等待3秒钟
}

// 打开网站链接演示
pbottleRPA.tts('打开网站')                    // 语音播报即将执行的操作
// 点击第一个搜索结果链接
pbottleRPA.browserCMD_click('div#content_left a:contains(小瓶RPA)')
pbottleRPA.wait(3)                             // 等待默认时间

// 获取网站Logo路径演示
pbottleRPA.tts('读取 logo 路径,显示到日志')  // 语音播报即将执行的操作
// 获取第一个img元素的src属性值(即图片地址)
ret = pbottleRPA.browserCMD_attr('img:first','src')
console.log('网站logo图片地址',ret);           // 输出Logo图片地址
pbottleRPA.wait()                             // 等待默认时间

// 获取元素位置信息演示
let ret2 = pbottleRPA.browserCMD_offset('div:contains(小瓶RPA):first')
console.log('搜索结果的位置',ret2);                  // 输出元素位置信息
pbottleRPA.wait()                             // 等待默认时间

// 演示完成提示
pbottleRPA.tts('演示完成准备退出')             // 语音播报演示结束
console.log("准备结束脚本");                  // 在控制台输出结束信息
// 浏览器内显示演示结束的弹窗
ret = pbottleRPA.browserCMD_alert('演示结束')

================================================
FILE: WEB增强-账号密码登录演示.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了使用Web增强功能实现网站自动登录
 * 需要安装小瓶RPA浏览器插件来操作网页元素,实现完整的登录流程自动化
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限

console.log(Date());                          // 在控制台输出当前日期时间

console.log("=== ※※※※※※※※※ ===");
console.log("=== 需要安装 小瓶RPA 浏览器插件 ==="); // 提示用户需要安装浏览器插件
console.log("=== ※※※※※※※※※ ===");

// 使用文字转语音功能提示用户必须安装浏览器插件
pbottleRPA.tts('必须安装小瓶RPA浏览器增强插件,手动点击确定继续')
// 显示系统消息框再次提醒用户
pbottleRPA.showMsg('提示:','必须先安装浏览器增强插件')
// 打开指定网址用于演示登录操作
pbottleRPA.openURL('https://yun.pbottle.com/?from=rpademo')

// 使用浏览器命令显示弹窗,等待用户手动确认(20秒超时)
let ret = pbottleRPA.browserCMD_alert('来自小瓶RPA的问候,手动点击确定开始,20秒超时')
console.log('返回操作结果',ret);              // 输出操作结果到控制台

// 检查浏览器插件是否正常工作
if (ret !== 'ok') {
    console.log('没有检测到小瓶RPA浏览器插件',ret); // 如果未检测到插件,输出错误信息
    process.exit(1)                           // 退出脚本
}

// 点击页面上的"登录或注册"链接
pbottleRPA.browserCMD_click(`a[role='button']:contains(登录或注册)`)
pbottleRPA.wait(2)                            // 等待2秒钟

// 点击"登录"按钮
pbottleRPA.browserCMD_click(`a[role='button']:contains(登录)`)
pbottleRPA.wait()                             // 等待默认时间

// 输入账号密码
// 点击用户名输入框
pbottleRPA.browserCMD_click(`input[name='uname']`)
// 在用户名输入框中输入用户名'test'
pbottleRPA.browserCMD_val(`input[name='uname']`,'test')

// 点击密码输入框
pbottleRPA.browserCMD_click(`input[name='pwd']`)
// 在密码输入框中输入密码'123456'
pbottleRPA.browserCMD_val(`input[name='pwd']`,'123456')
pbottleRPA.wait() 

// 禁用登录按钮测试
pbottleRPA.browserCMD_prop(`button:contains(登录帐号)`,'disabled',true);
pbottleRPA.wait()

pbottleRPA.browserCMD_prop(`button:contains(登录帐号)`,'disabled',false); // 确保登录按钮可用

// 点击登录按钮
pbottleRPA.browserCMD_click(`button:contains(登录帐号)`)
pbottleRPA.wait(3)                            // 等待3秒钟

// 模拟按下回车键确认登录
pbottleRPA.keyTap('enter')
// 使用文字转语音功能播报演示结束
pbottleRPA.tts('演示结束')

================================================
FILE: [企业版]HID硬件级键盘鼠标演示.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了企业版的HID硬件级键盘鼠标操作功能
 * 通过硬件级别的输入模拟,可以绕过一些软件层的限制,实现更稳定的自动化操作
 * 注意:此功能仅在企业版中可用,且需要额外的硬件外设支持
 * 
 * HID 注意:
 * ①此模块不是必须模块 
 * ②此模块功能需要添加电脑硬件外设,购买装配请咨询小瓶RPA客服
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限

// 监测是否开通企业版
let bufferRS = pbottleRPA.bufferSet('pbottle');
// 检查是否为个人版(个人版不支持此功能)
if (bufferRS == '个人版不可用') {
    // 显示系统消息框提示用户
    pbottleRPA.showMsg('个人版不可用','请先开通企业版')
    // 使用文字转语音功能播报提示信息
    pbottleRPA.tts('个人版不可用,请先开通企业版')
    // 退出脚本并输出错误信息
    pbottleRPA.exit('⚠ 个人版不可用,请先开通企业版')
}

console.log("=== HID 键盘模拟测试 ===");                 // 在控制台输出测试开始信息
console.log(Date());                         // 在控制台输出当前日期时间
// 显示系统消息框提示用户需要硬件外设
pbottleRPA.showMsg('需要硬件外设','测试需要硬件外设')
// 使用文字转语音功能播报提示信息
pbottleRPA.tts('正在打开键盘测试网站,需要开启RPA硬件模拟功能')
// 打开键盘测试网站用于演示硬件级输入
pbottleRPA.openURL('https://www.keyboardtest.cn/')
pbottleRPA.wait(3)                           // 等待3秒钟让网页加载完成
// 使用HID接口模拟按下F11键,进入全屏模式
pbottleRPA.hid.keyTap('f11')

// 获取当前屏幕分辨率信息
let resolution = pbottleRPA.getResolution()
console.log('当前分辨率:',resolution);        // 输出分辨率信息到控制台
// 使用HID接口移动鼠标到屏幕中心位置
pbottleRPA.hid.moveMouse(resolution.w/2,resolution.h/2)
// 使用HID接口执行鼠标点击操作
pbottleRPA.hid.mouseClick();

// 使用HID接口控制鼠标滚轮操作
pbottleRPA.hid.mouseWheel(-2)                // 向下滚动2个单位
pbottleRPA.wait()                            // 等待默认时间
pbottleRPA.hid.mouseWheel(1)                 // 向上滚动1个单位
pbottleRPA.wait()                            // 等待默认时间
pbottleRPA.hid.mouseWheel()                  // 默认向下滚动

// 使用HID接口执行不同按键的鼠标点击操作
pbottleRPA.hid.mouseClick('middle');         // 中键点击
pbottleRPA.hid.mouseClick('left',3000);      // 左键长按3秒
pbottleRPA.hid.mouseClick('right');          // 右键点击
pbottleRPA.wait()                            // 等待默认时间
// 移动鼠标到屏幕指定位置
pbottleRPA.hid.moveMouse(resolution.w/3,resolution.h/2)
pbottleRPA.hid.mouseDoubleClick()            // 执行鼠标双击操作
pbottleRPA.hid.mouseClick();                 // 执行鼠标单击操作

// 内容按键演示 - 逐个按下英文字母和符号键
let str = "abcdefghijklmnopqrstuvwxyz`1234567890-=[]\\;',./";  
// 遍历字符串中的每个字符并模拟按键
for (let char of str) {
    console.log(' 按键:',char);              // 输出当前按键字符到控制台
    pbottleRPA.hid.keyTap(char)              // 使用HID接口模拟按键
}

// 控制输入按键演示
pbottleRPA.hid.keyTap('up')                  // 按下方向键上
pbottleRPA.hid.keyTap('down')                // 按下方向键下
pbottleRPA.hid.keyTap('left')                // 按下方向键左
pbottleRPA.hid.keyTap('right')               // 按下方向键右
pbottleRPA.hid.keyTap('space')               // 按下空格键
pbottleRPA.hid.keyTap('page up')             // 按下Page Up键
pbottleRPA.hid.keyTap('page down')           // 按下Page Down键
pbottleRPA.hid.keyTap('end')                 // 按下End键
pbottleRPA.hid.keyTap('home')                // 按下Home键
pbottleRPA.hid.keyTap('tab')                 // 按下Tab键
pbottleRPA.hid.keyTap('shift')               // 按下Shift键
pbottleRPA.hid.keyTap('backspace')           // 按下退格键
pbottleRPA.hid.keyTap('enter')               // 按下回车键

// 使用HID接口再次按下F11键,退出全屏模式
pbottleRPA.hid.keyTap('f11')

pbottleRPA.wait(1)                           // 等待1秒钟
// 使用HID接口模拟按下Ctrl+Alt+Del组合键(系统级操作)
pbottleRPA.hid.keyTap('ctrl + alt + del')

pbottleRPA.wait(1)                           // 等待1秒钟
// 使用HID接口模拟按下Esc键
pbottleRPA.hid.keyTap('esc')
pbottleRPA.showMsg('测试完成', 'HID 键盘模拟测试完成');

================================================
FILE: [企业版]外部控制能力.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

const pbottleRPA = require('./pbottleRPA')


console.log("=== 外部控制能力测试 ===");
console.log(Date());


//监测是否开通企业版
let bufferRS = pbottleRPA.bufferSet('pbottle');
if (bufferRS == '个人版不可用') {
    pbottleRPA.showMsg('个人版不可用','请先开通企业版')
    pbottleRPA.tts('个人版不可用,请先开通企业版')
    pbottleRPA.exit('⚠ 个人版不可用,请先开通企业版')
}


/**
 * buffers
 */
pbottleRPA.wait()
for (let index = 0; index < 10; index++) {
    let content = "我是 +=<& buffer"+index
    pbottleRPA.bufferSet(content,index)
}

pbottleRPA.wait()
for (let index = 0; index < 10; index++) {
    console.log('buffer'+ index + ' 内容:', pbottleRPA.bufferGet(index));
}

let other = '其他内容'
let myJson = {
    name:'小瓶RPA',
    webSite:'https://rpa.pbottle.com/',
    version:2,
    app:['pc','web','mobile'],
    other,
}

pbottleRPA.bufferSet(myJson) //默认buffer0
console.log('获取buffer: ',pbottleRPA.bufferGet());


/**
 * 启停
 */
let path=__filename;
console.log(path);
let url = 'http://127.0.0.1:49888/?action=pbottleRPA_run&path='+encodeURIComponent(path)
console.log(url);
fetch(url).then((rs)=>{
    console.log(rs.status);
    return rs.text()
}).then((txt)=>{
    console.log('启动结果:',txt);
}).catch((error)=>{
    console.log(error);
})



//当前运行状态
let urlState = 'http://127.0.0.1:49888/?action=pbottleRPA_state'
let rs = pbottleRPA.getHtml(urlState)
console.log("当前运行状态(isRunning | ready):",rs);



setTimeout(()=>{
    // pbottleRPA.openURL('http://127.0.0.1:49888/?action=pbottleRPA_lastLog')  //获取日志
    //外部停止
    let urlStop = 'http://127.0.0.1:49888/?action=pbottleRPA_stop'
    pbottleRPA.getHtml(urlStop)
},3000)

================================================
FILE: [企业版]屏幕物体查找Ai演示.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 * 
 * 功能说明:此脚本演示了企业版的AI物体识别功能
 * 可以识别屏幕指定区域内的物体类型和位置,依赖于当前的训练模型
 * 注意:此功能仅在企业版中可用
 */

const pbottleRPA = require('./pbottleRPA')     // 引入小瓶RPA的核心库,获得对RPA功能的访问权限

console.log("=== 屏幕物体识别测试开始 ===");              // 在控制台输出测试开始信息
console.log(Date());                          // 在控制台输出当前日期时间

// 获取设备ID以检查是否为企业版
let deviceID = pbottleRPA.deviceID();
console.log('设备号:',deviceID);              // 输出设备ID到控制台

// 检查是否为个人版(个人版不支持此功能)
if (deviceID == '个人版不可用') {
    console.log('个人版不可用,请先开通企业版');   // 输出提示信息到控制台
    // 显示系统消息框提示用户
    pbottleRPA.showMsg('个人版不可用','请先开通企业版')
    // 使用文字转语音功能播报提示信息
    pbottleRPA.tts('个人版不可用,请先开通企业版')
    // 退出脚本并输出错误信息
    pbottleRPA.exit('个人版不可用,请先开通企业版')
}

// 提示用户物体识别种类依赖于当前的训练模型
console.log('物体识别种类依赖于当前的训练模型');
// 使用文字转语音功能播报提示信息
pbottleRPA.tts('物体识别种类依赖于当前的训练模型')

console.log("将要识别屏幕线框内物体,5秒后开始");
pbottleRPA.wait(5)                            // 等待5秒钟

// 使用AI物体识别功能检测屏幕指定区域内的物体
// 参数说明:置信度阈值0.5,检测区域坐标(745,291,424,565)
let rs = pbottleRPA.aiObject(0.5,745,291,424,565)
console.log("检测到物体结果:",rs);            // 输出检测到的物体结果到控制台

================================================
FILE: [企业版]接力执行脚本.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

const pbottleRPA = require('./pbottleRPA')


//监测是否开通企业版
let bufferRS = pbottleRPA.bufferSet('pbottle');
if (bufferRS == '个人版不可用') {
    pbottleRPA.showMsg('个人版不可用','请先开通企业版')
    pbottleRPA.tts('个人版不可用,请先开通企业版')
    pbottleRPA.exit('⚠ 个人版不可用,请先开通企业版')
}



pbottleRPA.delaySet(__filename)  //自己接力自己

pbottleRPA.log('等待3秒')
pbottleRPA.wait(3)
pbottleRPA.log('完成')


================================================
FILE: [企业版]集群控制中心日志回传.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

const pbottleRPA = require('./pbottleRPA')



const serverURL = pbottleRPA.clusterCenter();
console.log('集群控制中心地址:',serverURL);


const localLogURL = 'http://127.0.0.1:49888/?action=pbottleRPA_lastLog2'

const deviceId = pbottleRPA.deviceID()
const buffer0 = pbottleRPA.bufferGet(0)

// 获取taskId
let taskId;
try {
   taskId = JSON.parse(buffer0)._taskInfo.id
} catch (error) {
    pbottleRPA.exit('⚠ 未获取到taskId,日志回传终止')
}


// 获取本地运行日志
const content = pbottleRPA.getHtml(localLogURL)

console.log('本地日志长度:',content.length)
console.log('deviceId:',deviceId);
console.log("taskId:",taskId);


// 远程传输日志
fetch(serverURL+'?action=postLog', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        deviceId,
        taskId,
        content
    })
})
.then(res => res.text()).then(text => console.log('日志回传结果:',text))



================================================
FILE: [企业版]集群控制中心流程升级 .js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

const pbottleRPA = require('./pbottleRPA')

//日志回传
pbottleRPA.delaySet('./[企业版]集群控制中心日志回传.js'); 

const serverURL = pbottleRPA.clusterCenter();

let rs = pbottleRPA.getHtml(serverURL+'?action=update');
rsJson = JSON.parse(rs);
console.log('远程服务器返回:',rsJson);

const fileURL = serverURL.replace('/api/query','')+ rsJson.fileID
pbottleRPA.downloadFile(fileURL,'./newVersion.zip')
pbottleRPA.wait()

let destPath = pbottleRPA.path.resolve(__dirname + `/../${rsJson.version}`)
pbottleRPA.log('新工作空间:',destPath)
pbottleRPA.fs.mkdirSync(destPath, { recursive: true })
pbottleRPA.wait()
pbottleRPA.unZip('./newVersion.zip', destPath);
pbottleRPA.wait()
pbottleRPA.log('新版本下载并解压完成');

// 回传更新完成状态
rs = pbottleRPA.postJson(serverURL+'?action=updateFinish',{
    deviceId: pbottleRPA.deviceID(),
    version: rsJson.version,
    workDir: destPath.replaceAll('\\','/')+'/',
})
pbottleRPA.log('远程服务器返回:',rs);



================================================
FILE: [企业版]集群控制中心示例.js
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

const pbottleRPA = require('./pbottleRPA')

//日志回传
pbottleRPA.delaySet('./[企业版]集群控制中心日志回传.js'); 


//监测是否开通企业版
let bufferRS = pbottleRPA.bufferSet('pbottle',5);
if (bufferRS == '个人版不可用') {
    pbottleRPA.showMsg('个人版不可用','请先开通企业版')
    pbottleRPA.tts('个人版不可用,请先开通企业版')
    pbottleRPA.exit('⚠ 个人版不可用,请先开通企业版')
}


//控制中心配置参数从本地 buffer0 中直接读取
let buffer = pbottleRPA.bufferGet(0) 
console.log('下达任务参数(json格式):',buffer);

//流程开始
let resolution = pbottleRPA.getResolution()
console.log('当前电脑屏幕分辨率',resolution)

console.log('集群任务执行完成 🎉🎉🎉');




================================================
FILE: [第三方 模块卸载].bat
================================================
chcp 65001
@echo off
cd .

echo 正在卸载模块中
call npm uninstall  exceljs mammoth docx
echo.

echo.
echo 卸载完成!~    按任意键退出

pause >nul





================================================
FILE: [第三方 模块安装].bat
================================================
chcp 65001
@echo off
cd .

echo 正在切换国内安装源 
call npm config set registry https://registry.npmmirror.com/
echo.

echo 正在安装模块中
call npm install  exceljs mammoth docx
echo.

echo.
echo 恭喜,安装完成!~    按任意键退出

pause >nul

================================================
FILE: [第三方] 读写Excel演示脚本.mjs
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

import pbottleRPA from "./pbottleRPA.js";  //必须含 .js 后缀
import os from 'node:os'
let ExcelJS   // import ExcelJS  from "exceljs"
try {
	const {default:myExcelJS} = await import('exceljs') // 中文文档: https://gitee.com/mirrors/exceljs ;所有序号从1开始和excel序号保持一致 👍
    ExcelJS=myExcelJS
} catch {
    pbottleRPA.showMsg('请先安装第三方模块','双击【第三方 模块安装.bat】')
    pbottleRPA.tts('请先安装第三方模块' + '双击【第三方 模块安装.bat】')
	pbottleRPA.exit('请先安装第三方模块' + '双击【第三方 模块安装.bat】')
}


console.log("=== Excel 读写测试 ===");
console.log(Date());
pbottleRPA.tts('Excel 读写测试')
pbottleRPA.wait(3)
pbottleRPA.tts(`将当前电脑配置信息生成EXCEL文件`)
pbottleRPA.wait(5)

//生成excel文档
const workbook = new ExcelJS.Workbook()
const sheet = workbook.addWorksheet('pbottleRPA')
sheet.addRow(['项', '值'])
sheet.getRow(1).font = {bold:true}  //第一行粗体
sheet.getColumn(1).width = 30;  //列宽
sheet.getColumn(2).width = 60;
sheet.addRows([
    [
        '时间',
        pbottleRPA.getTime(),
    ],
    [
        '显示器分辨率',
        JSON.stringify(pbottleRPA.getResolution()),
    ],
    [
        'CPU',
        os.cpus()[0].model,
    ],
])
await workbook.xlsx.writeFile(`./Excel测试表格.xlsx`)
pbottleRPA.tts(`已经生成EXCEL测试表格...请查看 `)
pbottleRPA.openDir(pbottleRPA.__dirname)





//追加一条数据
await excelAppend(pbottleRPA.__dirname + `/Excel测试表格.xlsx`, ['项名','重新追加值'])   //所有异步方法(async 返回 promise),都用 await 以形成顺序执行的流程
pbottleRPA.wait(5)




//读取excel
const workbook2 = new ExcelJS.Workbook();
await workbook2.xlsx.readFile(`./Excel测试表格.xlsx`);
let sheet2 = workbook2.getWorksheet(1)
console.log(sheet2.getSheetValues());
pbottleRPA.tts(`已经读取EXCEL测试表格到日志 `)




/**
 * Excel 文件追加一行数据
 * @param {string} filename 文件绝对路径
 * @param {[]} line 行数据
 */
async function excelAppend(filename,line=[]) {
    console.log('excel追加行',filename,line);
    //读取excel
    const workbook = new ExcelJS.Workbook();
    await workbook.xlsx.readFile(filename);
    let sheet2 = workbook.getWorksheet(1)
    //excel 重新追加一行记录到已有的excel文件末尾
    sheet2.addRow(line)
    await workbook.xlsx.writeFile(filename) //保存
}

================================================
FILE: [第三方] 读写word演示脚本.mjs
================================================
/**
 * 小瓶RPA演示demo,具体api请查看*流程开发文档*
 * 官网:https://rpa.pbottle.com/
 * 流程开发文档:https://rpa.pbottle.com/docs/
 */

import pbottleRPA from "./pbottleRPA.js";  //必须含 .js 后缀
import fs from 'node:fs'
let mammoth
let docx
try {
	mammoth = await import('mammoth')  // import mammoth  from "mammoth"
    docx = await import('docx')   // import docx  from "docx"
} catch {
    pbottleRPA.showMsg('请先安装第三方模块','双击【第三方 模块安装.bat】')
    pbottleRPA.tts('请先安装第三方模块' + '双击【第三方 模块安装.bat】')
	pbottleRPA.exit('请先安装第三方模块' + '双击【第三方 模块安装.bat】')
}



console.log("=== word 后台读写测试 ===");
console.log(Date());
pbottleRPA.tts('word 后台读写测试')
pbottleRPA.wait(3)
pbottleRPA.tts(`将后台生成 word 文件`)
pbottleRPA.wait(5)

//生成 word 文档  更多例子:https://gitee.com/mirrors_dolanmiu/docx/tree/master/demo
const doc = new docx.Document({
    sections: [
        {
            properties: {},
            children: [
                new docx.Paragraph({
                    children: [
                        new docx.TextRun({
                            text: "标题文字",
                            bold: true,
                            size: 40,
                        }),
                    ],
                }),
                new docx.Paragraph({
                    children: [
                        new docx.TextRun({
                            text: "小瓶RPA官网:",
                        }),
                        new docx.ExternalHyperlink({
                            children: [
                                new docx.TextRun({
                                    text: "https://rpa.pbottle.com",
                                    style: "Hyperlink",
                                }),
                            ],
                            link: "https://rpa.pbottle.com",
                        }),
                    ],
                }),
            ],
        },
    ],
});

let buffer = await docx.Packer.toBuffer(doc);
fs.writeFileSync("word测试文档.docx", buffer);
pbottleRPA.openDir(pbottleRPA.__dirname)



//读取word文档
pbottleRPA.tts(`将后台读取 word 文件 显示到日志`)
pbottleRPA.wait(3)
let rs = await mammoth.extractRawText({path:"./word测试文档.docx"})
console.log('读取word文档内容:',rs.value);





================================================
FILE: docs/-图片测试.md
================================================
![image.png](https://raw.gitcode.com/qq_17154415/pbottleRPAdoc/attachment/uploads/b83ca6b3-5bd9-47c8-9fe2-50602f7bd9bb/image.png 'image.png')

================================================
FILE: docs/.vitepress/config.mjs
================================================
import { defineConfig } from 'vitepress'


// https://vitepress.dev/reference/site-config
export default defineConfig({
    title: "小瓶 RPA 官方文档",
    description: "小瓶RPA,专业用户的专业RPA软件。轻量级简单全能的RPA软件,浏览器自动化增强,显著降本增效 & 工作100%准确 & 非侵入式集成。同时支持浏览器web应用和客户端应用的操作流程自动化。同时支持 Js 和 Python 两种脚本制作流程。",
    lang: 'zh-Hans',
    lastUpdated: true,
    base: '/docs/',
    // outDir: './html',
    locales: {
        root: {
            label: '中文',
            lang: 'zh-Hans'
        },
        en: {
            label: 'English',
            lang: 'en', // 可选,将作为 `lang` 属性添加到 `html` 标签中
            link: 'https://officetool.online/pbottle-rpa/docs' // 默认 /en/ -- 显示在导航栏翻译菜单上,可以是外部的
        }
    },

    sitemap: {
        hostname: 'https://rpa.pbottle.com/docs/'
    },
    head: [
        ['link', { rel: 'shortcut icon', href: '/rpa.ico', type: 'image/x-icon' }],
        ['meta', { name: 'keywords', content: '小瓶RPA, 专业RPA, 文档, 流程文档, 官方文档, RPA文档 ,机器人流程自动化文档' }],
        ['script', { type: 'text/javascript' }, `
        var _hmt = _hmt || [];
          (function() {
              var hm = document.createElement("script");
              hm.src = "https://hm.baidu.com/hm.js?4f28c271b56a4be165f2b8200a58c47c";
              var s = document.getElementsByTagName("script")[0];
              s.parentNode.insertBefore(hm, s);
          })();
      `
        ],
        // ['script', {
        //     async:'async',
        //     crossorigin:"anonymous",
        //     src:"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8054907953581959",
        //   }
        // ],
    ],

    themeConfig: {
        // https://vitepress.dev/reference/default-theme-config
        docFooter: {// 文章翻页
            prev: '上一篇',
            next: '下一篇'
        },
        darkModeSwitchLabel: '外观',    // 移动端 - 外观
        returnToTopLabel: '返回顶部',    // 移动端 - 返回顶部
        sidebarMenuLabel: '菜单',    // 移动端 - menu
        outline: { label: '本页大纲', level: [2, 6] },

        nav: [
            { text: '使用指南', link: '/' },
            { text: '功能模块API', link: '/API鼠标操作' },
            { text: '官网下载', link: 'https://rpa.pbottle.com/' },
            { text: '在线客服', link: 'https://yun.pbottle.com/kefu.php' },
            { text: '技术交流群', link: 'https://gitee.com/pbottle/pbottle-rpa/raw/master/input/discuss.jpg' },
        ],


        sidebar: [
            {
                text: '使用指南',
                items: [
                    { text: '开始', link: '/index' },
                    { text: 'Demo示例', link: '/Demo示例' },
                    { text: '视频教程', link: '/视频教程' },
                    { text: '用 js 脚本开发自动化流程', link: '/用 js 脚本开发自动化流程' },
                    { text: '用 python 脚本开发自动化流程', link: '/用 python 脚本开发自动化流程' },
                    { text: '中文调用', link: '/中文调用' },
                    { text: 'AI生成流程脚本 🔥', link: '/AI生成流程脚本' },
                    { text: '流程运行日志', link: '/流程运行日志' },
                    { text: '定时启动', link: '/定时启动' },
                    { text: '开机启动', link: '/开机启动' },
                    { text: '流程录制', link: '/流程录制' },
                    { text: '热键和快捷方式', link: '/热键和快捷方式' },
                    { text: '手机应用的自动化', link: '/手机应用的自动化' },
                    { text: 'win7 操作系统', link: '/win7操作系统' },
                    { text: '信创操作系统 🔥', link: '/信创操作系统' },
                    { text: '‌Q&A 常见问答', link: '/‌Q&A' },
                ]
            },

            {
                text: '功能模块API',
                items: [
                    { text: '统一规范', link: '/API统一规范' },
                    { text: '鼠标操作模拟', link: '/API鼠标操作' },
                    { text: '键盘操作模拟', link: '/API键盘操作' },
                    { text: '系统 & 文件', link: '/API系统相关' },
                    { text: '屏幕画面', link: '/API屏幕' },
                    { text: '声音', link: '/API声音' },
                    { text: '网络', link: '/API网络' },
                    { text: '压缩解压', link: '/API压缩解压' },
                    { text: '用户输入', link: '/API用户输入' },
                    { text: '通用工具 Utils', link: '/API通用工具' },
                    { text: 'AI图像(本地)', link: '/APIAI图像' },
                    { text: 'AI大模型(云模块)', link: '/APIAI大模型' },
                    { text: '浏览器增强 🔥', link: '/API浏览器增强' },
                    { text: '办公文档', link: '/API办公文档' },
                    { text: '外部控制', link: '/API外部控制' },
                    { text: '键鼠硬模拟 hid', link: '/API键鼠硬模拟' },
                    { text: '其他(数据库、Excel等)', link: '/其他功能模块' },

                ]
            },

            {
                text: '高级指南',
                items: [
                    { text: '流程配置项', link: '/流程配置项' },
                    { text: '验证码自动化', link: '/验证码自动化' },
                    { text: '老旧低配电脑', link: '/老旧低配电脑' },
                    { text: '桌面快捷方式', link: '/桌面快捷方式' },
                    { text: 'HTTP静态服务', link: '/HTTP静态服务' },
                    { text: '无尽模式', link: '/无尽模式' },
                    { text: '硬件级键鼠模拟', link: '/硬件键鼠模拟' },
                    { text: '集群控制中心 🔥', link: '/集群控制中心' },
                    { text: 'SaaS系统自动化能力', link: '/SaaS系统自动化任务' },

                ]
            },

            {
                text: '其他开发服务',
                items: [
                    { text: '业务管理系统(ERP)', link: '/业务管理系统' },
                    { text: '独立软件定制', link: '/专用自动化独立软件' },
                ]
            }
        ],

        socialLinks: [
            { icon: 'gitee', link: 'https://gitee.com/pbottle/pbottle-rpa' },
            { icon: 'github', link: 'https://github.com/leoxiaoping/pbottleRPA' }
        ],


        editLink: {
            pattern: 'https://gitee.com/pbottle/pbottle-rpa/tree/master/docs/:path',
            text: '编辑本页内容'
        },


        footer: {
            message: `⭐⭐⭐⭐⭐`,
            copyright: 'Copyright © 2019-present 小瓶科技'
        }

    }
})


================================================
FILE: docs/AI生成流程脚本.md
================================================
# AI 对话大模型 生成小瓶RPA流程脚本

得益于小瓶RPA脚本层开源开放政策,小瓶RPA能够轻松用主流AI编程助手快速生成高质量流程脚本。

此功能目前只做参考和测试用途。

## 小瓶RPA助手智能体


直接访问小瓶RPA助手智能体agent:(官方知识库已经更新)

https://yuanqi.tencent.com/webim/#/chat/yoxhoN?appid=2021155473084382336

```
https://yuanqi.tencent.com/webim/#/chat/yoxhoN?appid=2021155473084382336
```


## AI平台(beta)

#### 打开AI平台

以豆包编程助手为例

https://www.doubao.com/



#### 引入小瓶RPA开源库

- 点击底部编程-》引入开源仓库

![引入小瓶RPA仓库](public/Snipaste_2025-03-19_15-42-35.png)

- 复制下面地址,输入并确认
  
```
https://github.com/leoxiaoping/pbottleRPA
```


#### 开始对话

输入对话提问例子测试

```
生成js流程脚本:打开多个网址并按 ctrl+d 收藏这些网址
```



================================================
FILE: docs/APIAI图像.md
================================================
# 本地 AI 图像

首先在软件设置中开启本地AI识别功能模块,然后重启软件生效。

![本地AI识别](public/Snipaste_2025-11-21_01-17-47.png)

## aiOcr 文字识别

@param {string} imagePath 空或者screen 为电脑屏幕; 或者本地图片的绝对路径;

@param {number} x 可选 剪裁起始点 左上角开始

@param {number} y 可选 剪裁起始点

@param {number} width 可选 剪裁宽度

@param {number} height 可选 剪裁高度

@returns {array} AI OCR识别的json结果 包含准确率的评分和中点位置 格式: `[{text:'A',score:'0.319415',x:100,y:200},...]`

![效果图](https://foruda.gitee.com/images/1721807171969468756/847e92b6_799608.png)


##  findText 寻找文字

findText 屏幕查找定位文字

查找文字,注:此功能受电脑性能影响,低配电脑可能速度较慢。 需要小瓶RPA客户端版本 > V2024.5

@param {string} inputTxt

@param {number} fromX=0 可选,查找开始的开始横坐标

@param {number} fromY=0 可选,查找开始的开始纵坐标

@param {number} width=-1 可选,搜索宽度

@param {number} height=-1 可选,搜索高度

@returns {JSON} 返回json结果:{x,y,text} x,y坐标相对于fromX,fromY。


## aiObject 物体识别

AI 物体识别 已经从经典算法升级为AI模型预测,企业版可脱网使用 V2024.8 以上版本有效

调试:软件根目录会生成 debug/Ai_ObjectDetect.png 文件

@param {number} imagePath 空或者screen 为电脑屏幕; 或者本地图片的绝对路径;

@param {number} x 可选 剪裁起始点 左上角开始

@param {number} y 可选 剪裁起始点

@param {number} width 可选 剪裁宽度

@param {number} height 可选 剪裁高度

@returns {array} AI 物体识别的 json 结果 包含准确率的评分 格式: `[{x:100,y:100,width:150,height:150,score:0.86,class:'分类名'},...]`


![效果图](https://foruda.gitee.com/images/1721807100510375459/29adc836_799608.png)

================================================
FILE: docs/APIAI大模型.md
================================================
# AI 大模型(云模块)

小瓶RPA 云端模块,AI在线大模型

![本地AI识别](public/Snipaste_2025-11-21_01-21-00.png)

1. 此模块不是必须模块 ,云端模块不影响本地模块的独立运行
2. 此模块功能需要登录并激活云端模块。碍于成本因素,部分功能需要充值计费才能使用

调用方式:pbottle.cloud.XXX()

token定价:https://rpa.pbottle.com/a-14065.html


## 大语言模型GPT 

小瓶RPA整合的云端大语言答案生成模型

@param {string} question 提问问题,如:'今天是xx日,你能给我写首诗吗?'

@param {number} modelLevel 模型等级,不同参数大小不同定价,默认 0 为标准模型。0为低价模型;1为性价比模型;2为旗舰高智能模型;

@param {string} response_format 云端模型输出格式,默认:"text",可选 "json_object" JSON格式

@returns {Answerinfo} JSON内容格式 `{content:'结果',tokens:消耗token的数量}`


示例:GPT问题答案AI生成演示.js


## 图像大模型 cloud_GPTV

小瓶RPA整合的云端图像分析大模型

@param {string} question 提问问题,如:'分析这个图片的内容'

@param {string} imagePath 上传图片的路径,如:'c:/test.jpg'

@param {number} modelLevel 模型等级,不同参数大小不同定价,默认 0 为标准模型。

@returns {Answerinfo} JSON内容格式 `{content:'结果',tokens:消耗token的数量}`

示例:GPT图像解析示例.js

================================================
FILE: docs/API办公文档.md
================================================
# office 办公文档

## 常规模拟操作

办公文档的操作一般依赖常用的办公软件:微软office、金山wps 等,
小瓶 RPA 可以通过模拟操作鼠标键盘操作这些软件。

## 后台读写快捷模式

1. Excel 文档:参考示例 [第三方] 读写Excel演示脚本.mjs
2. Word 文档:参考示例 [第三方] 读写word演示脚本.mjs

注意:请先双击 [第三方 模块安装].bat 安装模块。

================================================
FILE: docs/API压缩解压.md
================================================
# 压缩解压缩

新版压缩解压接口支持 4GB 以上超大文件

## 压缩 zipDir

压缩文件夹内容成一个zip文件包 v2025.0 以后版本生效

@param {string} directory 文件夹路径,输入绝对路径

@param {string} zipFilePath zip文件包


##  解压缩 unZip

解压缩zip文件内容到指定文件夹内 v2025.0 以后版本生效

@param {string} zipFilePath zip文件包

@param {string} directory 文件夹路径,输入绝对路径 默认解压到zip文件当前目录



================================================
FILE: docs/API声音.md
================================================
# 声音

## beep 蜂鸣声

发出系统默认提示音


## tts 文字转语音

从文本到语音(TextToSpeech) 语音播报
非阻塞

@param {*} text 朗读内容

================================================
FILE: docs/API外部控制.md
================================================
# 外部控制

除了实现软件操作自动化外,外部控制功能够实现小瓶RPA自身操作的自动化。

外部控制提供一系列控制小瓶RPA本身的 **http 接口** ,可以由 第三方系统 或者 RPA集群控制器 发出。

外部控制功能只对企业版开放 ,详细查看小瓶RPA软件授权:https://rpa.pbottle.com/License.php

## 外部启动流程

从外部控制启动小瓶RPA开始某项自定义的任务

用法:
http://ip:49888/?action=pbottleRPA_run&path=流程脚本路径

- 成功启动任务返回:ok
- 上一个任务还未结束:isRunning


## 外部停止流程

从外部控制停止小瓶RPA正在运行的任务

用法:

http://ip:49888/?action=pbottleRPA_stop


## 当前运行状态

从外部控制查询小瓶RPA正在运行的状态

用法:

http://ip:49888/?action=pbottleRPA_state

- 未运行状态返回:ready
- 正在运行状态返回:isRunning


## 外部控制buffer存取

buffer 是小瓶RPA的跨脚本的全局状态变量的存取管理机制。

#### bufferSet 命令

设置buffer存储内容

此buffer可以跨脚本存取,RPA重启时才重置,存取多线程下安全

@param {*} n buffer编号,从0-9共10个 默认:0 第一个buffer

@returns ok 表示成功

(POST方法):http://ip:49888/action=bufferSet&n=0 ,content设置到Post的body中,通常为json的字符串

#### bufferGet 命令

获取buffer存储内容

此buffer可以跨脚本存取,RPA重启时才重置,存取多线程下安全

http外部获取方式:http://ip:49888/action=bufferGet&n=0

@param {*} n buffer编号,从0-9共10个 默认:0 第一个buffer

@returns 字符串

http://ip:49888/action=bufferGet&n=0


## 外部设置定时任务

从外部控制修改小瓶RPA的计划任务(定时器)

用法:

http://ip:49888/?action=pbottleRPA_plan&plan=* *

定时器规则参考: https://www.pbottle.com/a-13868.html


## 外部获取设备唯一号

外部获取当前设备RPA唯一号

用法:
http://ip:49888/?action=pbottleRPA_deviceID

多用于商业加密脚本的分发控制和验证


## 外部获取运行日志

外部获取最后一个任务的运行日志

用法:
http://ip:49888/?action=pbottleRPA_lastLog

通常用来检查详细的运行过程

---

获取倒数第二个运行日志
`http://ip:49888/?action=pbottleRPA_lastLog2`


## delaySet 设置接力任务


`pbottle.delaySet(scriptPath)`

设置接力执行的脚本

当前脚本结束后(无论正常结束还是错误退出),立刻启动的自动脚本。

http外部设置方式(GET方法):http://ip:49888/action=pbottleRPA_delay&path=MyPATH

@param {string} scriptPath 接力脚本的路径 如:'D:/test.mjs' 如果路径为空,默认清除当前已经设置的接力任务。

@returns {string} ok 表示成功


- 监督任务执行
A 任务是一个复杂的长流程,在执行过程中各种问题和错误,导致一定概率不能顺利玩成到任务最后。
这时候在A任务开头设置接力任务B,B 作为 A 的监督员,可以在 A 任务退出时候,验证 A 的关键指标完成。根据验证结果做出通知人工,甚至重启A任务的流程。


================================================
FILE: docs/API屏幕.md
================================================
# 屏幕画面


## getResolution 获取屏幕分辨率

获取当前屏幕分辨率, ratio 为桌面缩放比例

@returns JSON 内容格式  `{ w:1920,h:1080,ratio:1.5 }`

## screenShot 屏幕截图

@param {*} savePath 保存路径默认 我的图片,图片格式为PNG;如果使用自定义路径请以 '.png' 结尾;

@param {*} x 截图开始位置

@param {*} y

@param {*} w 截图宽度

@param {*} h 截图长度


##  getScreenColor 获取屏幕颜色

屏幕一个点取色

@param {*} x

@param {*} y

@returns 返回颜色值

## findScreen 寻找图像

屏幕查找图象定位

@param {string} tpPath 搜索的小图片,建议png格式 相对路径

@param {number} miniSimilarity 可选,指定最低相似度,默认0.9。取值0-1,1为找到完全相同的。

@param {number} fromX=0 可选,查找开始的开始横坐标

@param {number} fromY=0 可选,查找开始的开始纵坐标

@param {number} width=-1 可选,搜索宽度

@param {number} height=-1 可选,搜索高度

@returns 返回找到的结果json 格式:`{x,y}`

##  waitImage 等待图像出现

等待屏幕上的图片出现

@param {string} tpPath 图片模板路径 相对路径:./image/123.png

@param {Function} intervalFun 检测间隔的操作,function格式

@param {number} timeOut 等待超时时间 单位秒

@returns {position|boolean} 结果的位置信息,json格式:`{x,y}`

#### 调试

等待图片超时情况,小瓶RPA会立刻全屏截图并保存到  `电脑-》我的图片`  供后续判断排查。    


## findContours 寻找轮廓

@param {number} minimumArea 轮廓最小面积 默认过滤掉 10x10 以下的元素

@param {number} fromX 开始坐标

@param {number} fromY

@param {number} width 作用范围

@param {number} height

@returns {array} 所有查找到的轮廓信息,包含闭合区域的起始坐标,中点坐标,面积,id。 格式:`[{ x: 250, y: 10, cx: 265.5, cy: 30.5, area: 2401, id: 42 },...]`

屏幕查找物体或者窗口轮廓

## imgSimilar 图片相似度对比

图片相似度对比  需要小瓶RPA客户端版本 > V2025.3
  
@param {string} path1  图片1路径

@param {string} path2  图片2路径

@param 'SIFT' | 'ORB' | 'SSIM' checkType 对比算法  默认 'ORB'

@returns {score:number, time:number}  score相似度分数 0-1 ; time耗时秒

#### 调试

软件 home 目录会生成 debug/findContours.png



================================================
FILE: docs/API浏览器增强.md
================================================
# 浏览器增强 - web应用

**⚠ 浏览器插件只是一种web浏览器页面操作的快捷操作方式,不是必须。按照小瓶RPA桌面应用的操作规则一样可以操作web浏览器应用。**

需要先安装小瓶RPA浏览器插件,安装方法查看:

https://rpa.pbottle.com/a-13942.html


基础调用方式:
`pbottleRPA.browserCMD.xxx()`

可以参考 “web增强“ 开头的demo示例。

## 元素选择器


小瓶RPA web增强基本遵循已经被广泛使用 **jQuery选择器** 的设计规则,可以参考jquery的选择器文档。

https://www.runoob.com/jquery/jquery-ref-selectors.html

相比 xpath 等方案优势:

- 学习成本低,规则和浏览器的 document.querySelector 原生方法一致
- 兼容性强,支持虚拟DOM的前端框架,如:Vue React 等
- 支持功能丰富且强大的伪类方案,可以高级选择定位

```javascript
pbottleRPA.browserCMD_click('button:contains(登录帐号)')
```




## 元素选择器测试工具

新版小瓶RPA增加了元素选择器测试功能,本功能旨在帮助用户在复杂网页中快速定位和选择html的元素。

![web增强插件](https://www.pbottle.com/static/upload/20250416/17447934102282.png)

**V2025.2 新增选中元素背景闪烁功能,方便用户查看元素位置。**



快速复制元素选择器步骤:

1. 鼠标放到浏览器目标元素上,F12 或者 右键菜单“检查元素”
2. 目标元素代码块上,右键弹出复制菜单
3. 选择 复制selector 【复制选择器】

![小瓶RPA如何精准选择网页的元素](https://www.pbottle.com/static/upload/20250807/17545509544351.png)



##  alert 警告框 


browserCMD_alert()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

警告框

@param {*} msg 显示文本内容

@returns 无


## url 获取|设置当前url 

browserCMD_url()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

@param {string} urlStr 当前网页转向新网址,默认为空获取当前网址 【小瓶RPA浏览器增强插件V2023.8以上生效】

@returns {string} 返回当前浏览器的url网址 或者 ok


## click 点击

browserCMD_click()

  浏览器增强命令  需要安装小瓶RPA的浏览器拓展

  模拟点击   参考 jQuery click() 方法,改为浏览器 native 的 click() 并自动获取焦点

  @param {string} selector   元素选择器。如果选择多个元素,只触发第一个元素的click事件

  @param {object} options 点击选项  可选  如:{ bubbles: false,  ctrlKey: true} https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/MouseEvent

  @returns {string}


## dblclick 双击

  浏览器增强命令  需要安装小瓶RPA的浏览器拓展  V2026.2 以上版本支持

  模拟双击   参考 jQuery dblclick() 方法,改为浏览器 native 的 click() 并自动获取焦点

  @param {string} selector   元素选择器。如果选择多个元素,只触发第一个元素的click事件

  @param {object} options 点击选项  可选  如:{ bubbles: false,  ctrlKey: true} https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/MouseEvent

  @returns {string}


## closeTab 关闭标签页

browserCMD_closeTab('current'|'other')  V2025.4 以上版本支持。

  浏览器增强命令  需要安装小瓶RPA的浏览器拓展

  关闭浏览器标签页。打开新标签页用 pbottleRPA.openURL()

  @param {string} 关闭类型  'current':默认关闭当前标签页; 'other':关闭其他标签页

  @returns {string} 正常返回 'ok'


## count 元素计数 

browserCMD_count()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

元素数量 参考 jQuery 选择器

@param {string} selector 元素选择器

@returns {number} 返回选择元素的数量,最优的选择结果是1

## hide 隐藏元素

browserCMD_hide()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

隐藏 参考 jQuery hide() 方法

@param {*} selector 元素选择器

@returns


##  show 显示元素

browserCMD_show()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

显示 参考 jQuery show() 方法

@param {*} selector 元素选择器

@returns


## offset 获取元素位置

浏览器增强命令 需要安装小瓶RPA的浏览器拓展 2024.0 以上版本生效

获取元素定位,相对浏览器文档左上角 参考 jQuery offset() 方法

@param {string} selector 元素选择器

@returns {} 返回 json:`{"top":100,"left":100}`  像素值为软件像素,非显示器硬件像素


## remove 移除元素

browserCMD_remove()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

移除元素 参考 jQuery remove() 方法

@param {*} selector 元素选择器

## text 获取|设置文本

browserCMD_text

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或者设置文本 参考 jQuery text() 方法

@param {*} selector 元素选择器

@param {*} content

@returns

## html 设置|获取代码

browserCMD_html()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或者设置html 参考 jQuery html() 方法

@param {*} selector 元素选择器

@param {*} content

@returns

## val 获取|设置值

browserCMD_val()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或设置值 input select等 参考 jQuery val() 方法

@param {*} selector 元素选择器

@param {*} content

@returns

## cookie 获取|设置 小存储

browserCMD_cookie()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或设置当前站点的 cookie

@param {*} cName cookie 名称

@param {*} cValue cookie 值 留空为获取cookie的值

@param {*} expDays cookie 过期时间,单位:天, 留空为会话cookie

@returns 返回 cookie的值


## css 获取|设置样式

browserCMD_css()

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或设置css样式 参考 jQuery css() 方法

@param {*} selector 元素选择器

@param {*} propertyname

@param {*} value

@returns


## attr 获取|设置属性

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或设置attr样式 参考 jQuery attr() 方法

@param {*} selector 元素选择器

@param {*} 属性名

@param {*} value


## prop 获取|设置prop

浏览器增强命令 需要安装小瓶RPA的浏览器拓展

获取或设置prop样式 参考 jQuery prop() 方法

@param {*} selector 元素选择器

@param {*} 属性名

@param {*} value

## fetch 网络请求

  浏览器增强命令  需要安装小瓶RPA的浏览器拓展  V2026.2 以上版本支持

  fetch请求网址,从当前页面发起ajax请求并返回响应结果  https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

  默认 20 秒超时

  @param {string} fetch_url 网址

  @param {object} options 请求参数

  @returns {string} 响应结果

## waitPageReady 监听页面加载完成

  浏览器增强命令  需要安装小瓶RPA的浏览器拓展 V2026.2 以上版本支持

  等待页面加载完成,返回页面网址

  默认 20 秒超时

  @param {string} readyURL  页面加载完成后的网址

  @param {number} timeout 超时时间,单位秒

  @returns {string}  返回当前浏览器的url网址 或者错误退出


## 批量采集获取网页内容

当选择器结果为多个元素时候,会一次性返回所有内容,内容格式为:JSON数组。  注意:V2025.3 以上版本支持。

批量采集获取网页内容目前支持方法:

- text()
- html()
- val()
- attr()
  
  参考示例:WEB增强-数据批量爬取演示.js


================================================
FILE: docs/API用户输入.md
================================================
# 用户输入

小瓶RPA流程执行中,流程可以暂停等待用户可以手动输入数值或者选项。

输入后流程继续执行。

⚠ V2026.0 以上版本基座支持用户输入。



## 等待输入 waitInput


 * 等待输入 V2026.0.0 新增
 * @param {string} inputPrompt 输入提示词
 * @param {number} timeOut 可选,等待超时时间 单位秒 默认600秒
 * @returns {string}  输入内容  默认返回空字符串



### 图片预览

![用户输入图片](public/ScreenShot_2025-10-20_143246_398.png)


### demo示例

用户手动输入变量示例.js



================================================
FILE: docs/API系统相关.md
================================================
# 系统相关

## wait 等待

  脚本暂停等待操作响应 (秒)
  注意:一次等待超过100s, 会有日志提示
  @param {number} seconds  秒,  缺省值为 1 秒。支持小数。

## setDefaultDelay 设置默认操作延时

设置RPA模拟操作的延时 包含鼠标、键盘、粘贴、打开网页操作
设置为 0 可以用 sleep() 手动管理操作延时

@param {*} millisecond 毫秒单位的数字


## jsPath 目录路径

变量名称:
当前脚本的文件夹路径,不带最后斜杠

别名:__dirname 方便在 .mjs 中使用

## getBasePath 基座路径 

获取并设置基座平台的根目录路径 | V2025.0 以上版本启用

@returns {string}

## showMsg 显示系统消息

@param {*} title 标题
@param {*} content 内容

本信息提示框为操作系统原生提示框

![提示框](https://foruda.gitee.com/images/1697684140653917645/45448f2b_799608.png)

系统设置

![alt text](https://foruda.gitee.com/images/1717396769929524137/aa3a2e03_799608.png)


## showRect 显示标记框

有效屏幕内显示一个彩色方框,直观提示流程操作范围和目标的当前的定位
V2024.6以上版本有效

@param {number} fromX 起始位置xy坐标,屏幕左上角为零点

@param {number} fromY

@param {number} width 宽度

@param {number} height 高度

@param {string} color 颜色 红绿蓝黄4色可选:red|green|blue|yellow

@param {number} msec 显示持续时间 单位毫秒

示例脚本:朋友圈点赞,截屏,文字识别

##  openFile 打开文件

用默认应用打开文件 如word、excel、pdf等

@param {*} path 文件路径

## openDir 打开目录

用资源管理器打开展示文件夹

@param {*} path 文件夹路径

## kill 关闭软件

(强行)关闭指定软件

@param {string} processName 进程名称,如:'WINWORD.EXE' 任务管理器 ‘进程名称’ 栏目 。注意不是 名称,如不显示,右键勾选显示这一栏目即可

@param {boolean} force 是否强制,相当于模拟任务管理器的结束任务操作。默认普通关闭,可能跟随保存确认框

```javascript
pbottleRPA.kill('WINWORD.EXE')  //关闭word
pbottleRPA.kill('EXCEL.EXE')  //关闭word
pbottleRPA.kill('msedge.exe')  //关闭edge浏览器
```


## copyText 复制文字 

模拟复制文字,相当于选择并复制文本内容

@param {string} txt 复制的文本内容

## copyFile 复制文件

模拟复制文件操作,支持文件路径和文件夹路径,复制后在目标文件夹ctrl+V 即可粘贴 V2024.7开始生效

复制文件后,在微信发送窗口粘贴,即可发送文件

@param {string} filepath 绝对路径

## exit 退出流程

强制退出当前脚本

@param {string} msg 退出时候输出的信息

## log 日志输出

@param {string} text 输出日志


## waitFile 等待文件

等待文件下载成功或者生成

@param {string} dirPath 监控文件夹目录 如:'c:/User/pbottle/download'

@param {string} keyWords 过滤关键词 如:'.zip'

@param {function} intervalFun 检测间隔的操作,function格式

@param {number} timeOut 等待超时时间 单位秒


================================================
FILE: docs/API统一规范.md
================================================
# API统一规范定义

## 屏幕坐标

以屏幕左上角为原点(坐标为 (0, 0)),水平向右为 x 轴正方向,垂直向下为 y 轴正方向。在这种坐标系中,屏幕上任意一点都可以通过一个 (x, y) 的坐标值来表示,其中 x 表示该点距离原点在水平方向的距离,y 表示在垂直方向的距离,单位通常是像素。

注意:在有缩放的屏幕上时,其中的像素单位是**物理像素**,不是软件像素。

![小瓶RPA屏幕坐标图片示意图](public/eb678332-8356-478e-b00d-600e6afdbc8c.jpg)


#### 推荐截图和测量工具

snipaste 截图软件自带坐标测量。

下载地址:
https://zh.snipaste.com/download.html


#### 分辨率测量工具

[https://www.pbottle.com/a-13812.html](https://www.pbottle.com/a-13812.html)




## 统一路径格式

小瓶RPA脚本默认路径格式分隔符为 `/`,包含 windows 系统。

windows 系统路径规则统一到 Linux 系统路径规则。

举例:

1. 绝对路径  `D:/myPath/test.png`
2. 相对路径  `./test.png`

================================================
FILE: docs/API网络.md
================================================
# 网络

流程同步网络方法

## openURL 打开网址

用电脑默认浏览器打开网址

@param {string} myurl 网址

示例:快速开始脚本

## getHtml 请求网址 (同步方法)

  普通请求网址,获取返回的html文本

  @param {string} url 网络地址 get方法

  @param {object} headersJson  请求头 Json对象 

  @returns {string} 返回的文本


## postJson 提交json

  向指定API网址post一个json,最常用网络接口方式
  
  @param {string} url API网络地址 
  
  @param {object} msgJson Json对象 
  
  @param {object} headersJson 请求头 Json对象 

  @param {string} method e.g. GET, POST, PUT, DELETE or HEAD

  @returns {string}

示例:运维消息手机通知.js

## postJsonFile 提交json文件

  向指定API网址post一个json文件,适合大型json内容

  @param {string} url API网络地址 

  @param {string} msgJsonFile Json文件路径 

  @param {object} headersJson 请求头Json对象 

  @param {string} method e.g. GET, POST, PUT, DELETE or HEAD

  @returns {string}

示例:GPT图像解析示例.js



## downloadFile 下载文件

  从网络下载一个文件到本地路径

  @param {string} fileUrl 网址

  @param {string} filename 本地路径文件名
  
  @param {object} headersJson  请求头 Json对象 


## wxMessage 微信消息发送(过期废止)

通知到手机

通过小瓶云发送微信通知 (微信到达率高,并且免费)

@param {string} name 消息标题

@param {string} content 消息详细内容

@param {string} key 获取key详情方法:https://www.pbottle.com/a-12586.html

## sendMail 发送邮件

 * 发送邮件;注意这个方法是个异步方法,请参考示例;
 * @param {string} to  收件人地址
 * @param {string} subject 邮件主题
 * @param {string} content 邮件内容;文本文件,换行用 '\n'
 * @param {string} host 服务器地址(如:smtp.qq.com)
 * @param {number} port 服务器端口 默认是465
 * @param {string} user 认证信息(用户名)一般也是发送邮件地址
 * @param {string} pass 认证信息(密码)
 * @returns 

示例:发送Email电子邮件.js

================================================
FILE: docs/API通用工具.md
================================================
# pbottleRPA.utils 工具箱

提供基础常用便利工具

调用方式:
pbottleRPA.utils.xxx()

demo示例:
常用工具 Utils 演示.js

## 获取格式化时间 getTime

utils.getTime()


格式化的时间 getTime('Y-m-d H:i:s') 输出类似 "2023-09-17 14:30:45" 的日期时间字符串

@param {string} format 格式参考 https://www.runoob.com/php/php-date.html 仅支持 Y|y|m|d|H|i|s|n|j

@param {number} timestamp 时间戳秒

@returns {string}

##  唯一数 uniqid

utils.uniqid()

生成唯一符串 注意:默认只是毫秒级的

@param {string} prefix 前缀

@param {boolean} moreEntropy 是否开启更精细的随机,如果还不能满足请使用uuid

@returns {string}


## 是否数字 isNumeric

utils.isNumeric()


判断是否为数字化变量(包含数字化的字符串)

@param {*} value 任意类型变量

@returns {boolean}

## 是否有内容 hasData

utils.hasData()

判断变量中是否有数据,直接if()可用。
非零数字 或 非空字符串、数组、对象 返回 true,其他都返回 false

@param {*} value 任意类型变量

@returns {boolean}

##  搜索文件 searchFile

utils.searchFile()

根据关键字搜索定位具体文件

@param {string} directory 绝对路径

@param {string} words 文件名包含的关键字,过滤词,默认忽略大小写

@returns {string[]} 文件路径 || [] 未找到


================================================
FILE: docs/API键盘操作.md
================================================
# 键盘模拟操作

## keyToggle 键盘基础触发

模拟按键触发事件

@param {*} key 按键名称参考:https://www.pbottle.com/a-13862.html

@param {*} upDown 默认按下down,up松开按键


## keyTap 键盘按键

按一下键盘 支持组合按键 加号连接 如: keyTap('ctrl + a')

@param {*} key 按键名称参考:https://www.pbottle.com/a-13862.html


## paste 粘贴输入

当前位置 粘贴(输入)文字

@param {*} text


##  getClipboard 获取剪切板内容

获取当前电脑的剪切板内容,系统剪切板支持多种格式 版本 V2024.2 开始生效
- 纯文本格式:普通复制 如'小瓶RPA'
- 图片格式 base64形式:浏览器复制图片 'data:image/png;base64,' 开头
- html格式:浏览器或者钉钉复制富文本综合内容 ''开头

@returns 结果文本

================================================
FILE: docs/API键鼠硬模拟.md
================================================
# 键鼠硬模拟 API

小瓶RPA硬件增强不是必选项,需要购买额外的硬件,建议只有需要系统级模拟操作时候才启用。

开启硬件键盘鼠标模拟,不影响默认的软件键盘鼠标模拟。

功能起始版本:V2024.3

本功能只对企业版开放 ,详细查看小瓶RPA软件授权:https://rpa.pbottle.com/License.php


## pbottle.hid.XXX 接口集

调用方式:pbottle.hid.XXX()


## hid.keyToggle

* 模拟按键触发事件 (硬件级)
 * @param {string} key  按键名称参考:https://www.pbottle.com/a-13862.html
 * @param {string} upDown  默认按下down,up松开按键

## hid.keyTap 

* 按一下键盘(硬件级)   支持组合按键 加号连接 如:  keyTap('ctrl + alt + del')
* @param {string} key  按键名称参考:https://www.pbottle.com/a-13862.html

## hid.mouseCMD

* 基础鼠标命令  全部为零释放
 * @param {number} button 按键  1,2,4 代表鼠标的 左键,右键,中键。
 * @param {number} x 按键时候移动的位置,绝对位置  x=100:向右移动 100像素,负数向左
 * @param {number} y 按键时候移动的位置,拖拽相对位置  y=100:向下移动 100像素,负数向上
 * @param {number} mouseWheel 滚动齿轮数  正数向下,负数向下
 * @param {number} time 按下到释放时间

## hid.moveMouse 

 * 移动鼠标到指定位置  起点为屏幕左上角  屏幕绝对位置(硬件分辨率)
 * @param {number} x   横坐标
 * @param {number} y   纵坐标
  
## hid.mouseClick

 * 当前位置点击鼠标 默认左键  
 * @param {string} 鼠标的按键选择 left right middle 可选  ,默认左键
 * @param {number} 点按时间 单位毫秒 可选
  
## hid.moveAndClick

 * 移动鼠标到指定位置并点击
 * @param {number} x 横坐标
 * @param {number} y 纵坐标

## hid.mouseDoubleClick 

双击鼠标  左键


## hid.mouseLeftDragTo 

* 鼠标左键拖到一段位置
 * @param {number} x  位置
 * @param {number} y  位置

## hid.mouseRightDragTo 

 * 鼠标左键拖到一段位置
 * @param {number} x  位置
 * @param {number} y  位置


##  hid_mouseWheel 

* 鼠标滚轮
* @param {number} data 滚动的量  默认为-1   向下滚动一个齿轮;  正数向上滚动;



## 参考示例

HID硬件级键盘鼠标演示.js

================================================
FILE: docs/API鼠标操作.md
================================================
# 鼠标操作模拟


## moveMouse 鼠标移动

移动鼠标到指定位置并点击 起点为屏幕左上角

@param {number} x 横坐标

@param {number} y 纵坐标

@param {number} interval 像素间隔时间,越大移动越慢 毫秒单位,默认:0

## moveAndClick 鼠标移动并点击

移动鼠标到指定位置并点击 起点为屏幕左上角

@param {number} x 横坐标

@param {number} y 纵坐标



## mouseClick 鼠标点击

当前位置点击鼠标 默认左键 可选 'right'

@param {*} leftRight 可选

@param {*} 点按时间 单位毫秒 可选


## mouseDoubleClick 鼠标双击

双击鼠标 默认左键

## mouseWheel 鼠标滚轮

鼠标滚轮

@param {*} data 滚动的量 默认为-720 向下滚动720度


## mouseLeftDragTo 鼠标左键拖动

鼠标左键拖到指定位置

@param {*} x

@param {*} y


## mouseRightDragTo 鼠标右键拖动

鼠标右键拖到指定位置

@param {*} x

@param {*} y

================================================
FILE: docs/Demo示例.md
================================================
# Demo示例

小瓶RPA提供多个基础功能的demo示例,方面大家参考。具体API的使用可以参考demo,所有api接口都有demo示例。

Demo示例为 JavaScript 和 python 脚本语言,符合完整 Nodejs NPM  和 python pip 项目规则。

**官方推荐 JavaScript 版本**


## Demo运行条件
1. 安装运行小瓶RPA基座程序
2. 安装脚本引擎
3. 下载运行示例脚本

## Demo示例完整教程
[https://rpa.pbottle.com/a-14019.html](https://rpa.pbottle.com/a-14019.html)


================================================
FILE: docs/HTTP静态服务.md
================================================
# 集成HTTP静态服务

小瓶RPA 集成了HTTP静态服务,可以快速集成静态页面,实现自动化测试、下载文件、H5页面展示等功能。

⚠ 本功能V2026.1以上版本有效。

## 静态文件本地目录

小瓶RPA主菜单 设置-》打开数据目录

静态文件存放目录:
`[数据目录]/static`

例如:`[数据目录]/static/test.html`


## 访问网址

`http://127.0.0.1:49888/static/`

例如:`http://127.0.0.1:49888/static/test.html`




================================================
FILE: docs/SaaS系统自动化任务.md
================================================
# SaaS系统自动化任务

小瓶RPA可以为现有SaaS系统服务,升级提供自动化任务服务中心。

## 优势

- SaaS 系统无缝平台升级改造
- SaaS 品牌用户端保持
- 本地批量集中执行,低成本
- 扩充现有 SaaS 现有自动化任务能力

## 架构示意图

![SaaS系统自动化任务](https://www.pbottle.com/static/upload/20250716/1752649922436.png)




================================================
FILE: docs/index.md
================================================
---
next:
  text: 'Demo示例'
  link: 'Demo示例.html'
---


# 开始使用小瓶RPA

**小瓶RPA,专业用户的专业RPA+AI软件。**

长难业务自动化流程专精,轻量级简单全能的RPA软件,显著降本增效 & 工作100%准确 & 非侵入式集成。同时支持浏览器web应用和客户端应用的操作流程自动化。同时支持 Js 和 Python 两种脚本制作流程。


下载官网:https://rpa.pbottle.com/


![小瓶RPA logo](https://rpa.pbottle.com/TP/img/logo_rpa.png)



## 小瓶RPA专业版的优势

1. RPA自动化优势、AI优势和传统开发生态优势三位一体。🤖
2. 超轻量级 + 开放接口能力,无缝整合现有工作系统,而非高成本替代。📗
3. 纯视觉模拟驱动,可以兼容操作所有应用程序和所有操作系统。🖥️
4. 系统硬件级操作模拟, 可以完成所有系统级高权限模拟操作。⌨️


## 自动流程入门示例

```javascript
const pbottleRPA = require('./pbottleRPA')  //引入小瓶RPA nodejs模块

pbottleRPA.openURL('https://www.baidu.com/') // 用浏览器打开百度
pbottleRPA.paste('小瓶RPA官网')  //输入搜索词
pbottleRPA.keyTap('enter')  //确认搜索
```


```python
import pbottleRPA  #引入小瓶RPA python模块

pbottleRPA.openURL('https://www.baidu.com/') #用浏览器打开百度
pbottleRPA.paste('小瓶RPA官网')  #输入搜索词
pbottleRPA.keyTap('enter')  #确认搜索

```


## 系统架构图

![小瓶RPA架构图](https://www.pbottle.com/TP/img/rpa.png)

## 小瓶RPA基座界面 和 浏览器插件界面

![小瓶RPA软件截图](https://www.pbottle.com/static/upload/20250619/17503069137119.png)
![小瓶RPA浏览器插件截图](https://www.pbottle.com/static/upload/20250416/17447934102282.png)

## 软件授权温馨提示

未经授权禁止出售小瓶RPA软件和其附加资源,详细参考:

- [《小瓶RPA 用户协议》](https://rpa.pbottle.com/a-13944.html)  
- [《小瓶RPA 软件授权对比》](https://rpa.pbottle.com/License.php)

================================================
FILE: docs/package.json
================================================
{
  "devDependencies": {
    "rimraf": "^6.1.2",
    "vitepress": "^1.6.4"
  },
  "scripts": {
    "docs:dev": "vitepress dev",
    "docs:clean": "rimraf .vitepress/dist",
    "docs:build": "vitepress build",
    "docs:preview": "vitepress preview"
  }
}


================================================
FILE: docs/public/index.html
================================================
文档静态资源目录

================================================
FILE: docs/win7操作系统.md
================================================
# win7 操作系统上使用小瓶RPA

国内政企单位仍有大量 win7 电脑设备

小瓶 RPA 仍然花费开发资源保持对 win7 系统的全功能模块兼容。



## win7 小瓶RPA平台基座

Win7 系统存在大量盗版精简系统,尽可能保持系统自动更新到最新版。

常见 win7 精简版因为缺乏 TTS 引擎 会导致小瓶RPA的 TTS 模块不可用。


## win7 脚本引擎特殊安装

win7已经被很多新版本放弃,win7只能下载最新版本zip版本,才能使用mjs、fetch 等最新特性

https://mirrors.cloud.tencent.com/nodejs-release/v18.20.4/node-v18.20.4-win-x64.zip



1. 下载zip版本node并解压后添加文件夹路径 到 环境变量 path 中,参考:https://www.pbottle.com/a-14057.html

2. 如果提示:“无法定位程序输入点EventSetInformation 于动态链接库ADVAPI32.dll上

保证win7最新更新,补丁编号KB3080149, 下载地址是 https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=48636

3. node 运行的提示操作系统不适配,设置环境变量 NODE_SKIP_PLATFORM_CHECK=1 即可


================================================
FILE: docs/‌Q&A.md
================================================
# Q & A 常见问答

- 收集和探讨关于小瓶RPA的常见问题和官方答案。
- 更多问题可以联系在线客服。

## 小瓶RPA 可以实现哪些流程的自动化,可以破解某个系统或软件吗?

小瓶RPA不具有破解和越权操作能力,RPA可以代替并优化人类操作电脑并有高速处理数据能力。

一个能力边界的判断方式是:

凡是你能手动操作的工作流程,小瓶RPA都能够把这个流程实现自动化。

如果你手动操作(包含使用工具软件)都无法做到的事情,大概率 小瓶RPA 也做不到。


## 小瓶RPA的怎么使用?适用于哪个行业?

小瓶RPA是通用行业的自动化平台软件,所有现有工作流程都可以快速实现自动化。

小瓶RPA软件更像办公的ppt软件,你只安装了ppt是没有ppt文档的,没有实用价值的。流程脚本就好比制作ppt文档,好的ppt文档也不是每个人都会做的,根据用户的制作水平而定。

小瓶RPA附带了一些供参考的基础demo示例脚本,用户可以从官网下载运行。



## 可以多个流程同时运行吗?

- RPA的流程中可以大致分为 模拟操作 和 数据处理 两种指令。数据处理可以并发同时执行,模拟操作部分由于电脑只能一套鼠标键盘可以操作,所以不能同时执行。
- 一台电脑可以同时运行多个虚拟机,每个虚拟机可以同时运行一个自己的流程。



## 为什么小瓶没有支持拖拉拽的所谓 '设计器',只能用脚本语言表达流程?

- 小瓶RPA的主要定位是,专业用户的专业RPA软件。对流程实施者有基础脚本能力要求,不适合没有任何脚本基础的人员直接使用。应用场景多为自动化项目、工作站RPA应用等。

- 图形化编程(设计器)曾经风靡一时,但在脚本语言越来越傻瓜化的今天,图形的语法表现力不足这一劣势越来越明显。目前面对长难流程的开发管理往往不能满足和有更高综合成本。(有商业价值的自动化项目往往都是长难流程)
  
- js脚本可以融入完整的 Nodejs(Python)生态,无缝引入万亿第三方功能包,可以传统IT项目自然对接,对技术运维人员更友好。
  
- V2023.3版本 新增加鼠标操作录制自动生成简单的脚本功能。

- 小瓶RPA可以用AI编程助手,实现流程脚本的自动化生成和编写。查看章节:`使用指南-》AI生成流程脚本`。声明:此方式目前正处于测试阶段。


## 小瓶RPA 是否收费?

- 小瓶RPA个人版本:永久免费!

- 小瓶RPA企业版本:一次性软件永久授权 + 流程实施(可选)。其中流程实施包含:流程制作 + 测试 + 稳定性优化。
  
- 小瓶RPA云模块:各种 AI 大模型按照消耗云端 token 的量计费,自动从充值账号扣除,不使用不产生费用。

具体内容可咨询我们的销售顾问。


## 可以脱网或者内网使用小瓶RPA吗?

可以,需要企业版授权,个人用户需要联网获取免费授权。

详见:https://rpa.pbottle.com/License.php


## 小瓶RPA可以将我制作的流程脚本打包成一个exe程序,直接在客户电脑运行吗?

  小瓶RPA软件目前是个平台模式运行的软件,不支持其中一个流程导出为 exe 独立程序。

- **支持 OEM 定制化服务**,软件可以贴牌服务商公司的品牌logo,需要采购达量或者定制费。
- **支持 流程脚本加密**,适合服务商不想扩散自己开发的流程脚本情况。
- **支持 任务管理中心web版本**,可以让终端用户只通过浏览器批量管理自己的任务结果,无需接触具体任务流程。

##  有了AI大模型,还需要小瓶RPA吗?

  需要,目前AI从实用角度替代不了小瓶RPA。

- 小瓶RPA是准确优先的,AI是智能优先。目前工作流程如果全部AI,你要配双倍人力去给它纠错,最后经济上得不偿失。
- 目前小瓶RPA的自动化流程中,已经集成了多个AI本地模型,还有在线大模型AI模块,以便智能处理流程中的必要的环节。

##  技术专员和流程实施的区别?

- 流程实施服务是全包的服务,所有相关的开发、测试、优化和部署 都由我们来完成。
- 技术专员服务属于技术支持,只提供项目中具体的技术点咨询顾问服务,整体的实施工作还是以甲方为主来完成。

##  小瓶RPA应用和传统软件应用有什么区别?

传统应用的所有功能模块都要自己开发,Rpa应用可以引用所有成品软件应用当做自己的功能模块。所以RPA应用也可以被叫做**软件的软件**。

比如传统应用要增加一个通信功能模块,你就得自己开发,顶多可以找library库来提升开发质量和速度。RPA应用可以直接连接企业微信,不但有了发消息的功能,甚至把企业微信的用户生态都串联了过来。

================================================
FILE: docs/专用自动化独立软件.md
================================================
# 专用自动化独立软件(上位机软件定制开发)

独立自动化产品定制,硬件软件结合的独立可执行程序。

商业化友好,高性能 C/C++,AI能力集成等特点。

浏览业务官网:https://soft.pbottle.com/


## 硬件设备支持

小瓶上位机软件可以通过:

1. 串口、CAN总线、USB、以太网、蓝牙、WIFI、MQTT等和硬件外设(下位机)进行通信。
2. PLC 设备厂商专用通信协议:欧姆龙Fins、三菱MC、西门子S7、modbus、OPC 等。
3. 定制版或者专属硬件SDK开发模式完成外设数据通信和控制。
   
其他设备类型:

- 工业相机驱动
- 运动控制器驱动
- PLC下位机驱动
- 传感器 驱动
- 数据采集卡
- 定制外设硬件驱动SDK

## 自动化能力模块

装配 **小瓶RPA** 最新自动化操作能力模块。


## 操作系统支持

- Windows
- 信创Linux(麒麟+统信)
- Android
- 鸿蒙

## 开发案例

https://soft.pbottle.com/a-13956.html

*注:部分项目受合同保密条约限制不予展示,请联系客服索要跟多资料和视频演示*

================================================
FILE: docs/业务管理系统.md
================================================
# 业务管理系统(小瓶ERP业务管理系统)

市场瞬息万变,业务系统必须实时响应,小瓶ERP业务管理系统,业务敏捷开发高于一切的ERP系统,能够快速低成本实现贴合业务的量身定制。

摒弃传统 ERP 系统冗长的开发周期与高昂的定制成本,依托模块化架构,构建一套高效灵活的业务响应机制。

小瓶 RPA+AI 赋能的业务管理系统。

浏览业务官网:https://erp.pbottle.com/


## 小瓶ERP业务管理系统优势


1. 敏捷高于一切的业务管理系统。
2. 灵活自主的私有云部署方式,同时支持小程序和APP移动运营。
3. 小瓶 RPA 提供AI数据处理能力。
   
## 小瓶 RPA+AI 赋能

1. 流程自动化与效率提升
2. AI智能决策支持
3. 跨系统集成与数据互通

## ATI 非接口数据能力

解决场景痛点:

1. 多业务数据,数据孤岛。
2. 数字化升级数据阻碍。
3. 软件数据保护,无导出途径。
4. 数据格式不通用。
5. 基础数据生涩难懂。


================================================
FILE: docs/中文调用.md
================================================
# 中文调用

- 增加中文调用方式,更直观,更易读,更低入门
- 免去注释,快速开发流程
- 中文+英文 双重调用方式

## API 中英文对照示意图

![中文API脚本示意图](./public/chinese-coding.png)

## 快速开始的中文示例

```javascript
pbottleRPA.打开网址('https://www.baidu.com/')
pbottleRPA.粘贴输入('小瓶RPA官网')
pbottleRPA.键盘按键('enter')
```


================================================
FILE: docs/信创操作系统.md
================================================
# 信创操作系统上使用小瓶RPA

小瓶RPA从 V2024.8 版本支持信创操作系统,已经完成信创操作系统的全功能模块原生支持。


![小瓶RPA信创版本](https://www.pbottle.com/static/upload/20241020/17294114803702.png)

## 支持操作系统

1. 银河麒麟操作系统  KylinOS
2. 统信操作系统 UOS
3. 乌班图 Ubuntu OS
   
   ![麒麟适配证书](https://www.pbottle.com/static/upload/20241204/17332947879554.jpg)
   ![UOS适配证书](https://www.pbottle.com/static/upload/20241022/17295659104480.jpg)

## 安装 小瓶RPA

官方应用商店搜索 '小瓶RPA'

(银河麒麟+统信)


## 信创操作系统平台差异

- 小瓶RPA信创版本具有和 windows 版本**完全一致的功能模块**
- 可以用 windows 版本测试流程脚本
- 个人免费下载版支持 x86_64 的 CPU 架构
- 企业版支持 ARM、LoongArch、RISC-V 架构

================================================
FILE: docs/其他功能模块.md
================================================
# 自由引入其他功能模块

JavaScript 是互联网上最流行的脚本语言之一,所有网页前端和客户端中都有它的身影,由于是一种类C语言,有着广泛的用户基础并且 JavaScript 很容易学习。

Node.js使用 `npm` 作为其默认的包管理器,该工具能够自动化处理项目依赖的安装、更新和删除。

npm 第三方功能库:https://www.npmjs.com/

任何传统编程能够实现的功能模块,都能整合进入我们的小瓶RPA自动化流程。

## 数据库模块

引入  mysql 数据库示例,其他数据库模块同理:

```javascript
//引入小瓶RPA功能
const pbottleRPA = require('./pbottleRPA')
//引入第三方mysql连接功能   事先执行命令:npm install mysql
const mysql      = require('mysql'); // 事先执行命令:npm install mysql

var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'root',
  password : '123456',
  database : 'test'
});
connection.connect();
 
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
  //使用小瓶RPA api接口
  pbottleRPA.tts("mysql 获取数量为:" + results[0].solution)
});
```


## 图片处理模块

引入  sharp 图片处理模块示例:

```javascript

const pbottleRPA = require('./pbottleRPA')  //引入小瓶RPA功能
const sharp = require('sharp');  // 事先执行命令:npm install sharp

// 缩放到指定宽度,高度自适应
sharp('input.jpg')
  .resize(300, null)
  .toFile('output.jpg');

// 保持长宽比并限制在指定尺寸内
sharp('input.jpg')
  .resize(300, 300, {
    fit: 'inside',
    withoutEnlargement: true  // 防止图片被放大
  })
  .toFile('output.jpg');

```

## Excel 和 Word 模块

先执行:`npm install exceljs mammoth docx`

- 参考demo示例:[第三方] 读写Excel演示脚本.mjs
- 参考demo示例:[第三方] 读写word演示脚本.mjs

## 生成 PDF 模块

生成一个简单的 PDF 文件的示例:

```javascript
const pbottleRPA = require('./pbottleRPA')  //引入小瓶RPA功能
const puppeteer = require('puppeteer'); // 事先执行命令:npm install sharp

async function htmlToPdf(htmlContent, outputPath) {
  // 启动浏览器
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 设置页面内容
  await page.setContent(htmlContent);

  // 等待页面加载(可选)
  await page.waitForTimeout(1000); // 等待1秒,确保内容完全加载(如果有需要的话)

  // 生成 PDF
  await page.pdf({ path: outputPath, format: 'A4' });

  // 关闭浏览器
  await browser.close();
}

// HTML 内容示例
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
  <title>测试页面</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <p>这是一个使用 Node.js 和 Puppeteer 生成的 PDF。</p>
</body>
</html>
`;

// 输出路径
const outputPath = 'output.pdf';

// 调用函数生成 PDF
htmlToPdf(htmlContent, outputPath).catch(err => console.error('生成 PDF 失败:', err));
```javascript

================================================
FILE: docs/定时启动.md
================================================
# 定时启动计划任务

## 小瓶RPA自带定时任务规则

计划任务设置规则

1. 小时字段和分钟字段用空格隔开
2. 多个值用逗号隔开
3. 小时的取值范围 0-23,分钟取值范围0-59 
4. 支持  -   表示范围,支持  *  表示任意


例子:

```
* *           //表示每分钟一次
0-9  0        //表示每天0点 ...  到9点  9个小时每小时的零分钟都运营一次
3,6,9  10,40  //表示每天3点,6点,9点, 每小时运行两次,分别在10分钟和40分钟
```


## windows 高级多任务定时任务

操作系统原生定时器,稳定高效,并且有睡眠唤醒等高级功能

- Windows :**任务计划程序**

1. 打开windows自带的计划任务程序,并设置触发时间

![任务计划程序](https://www.pbottle.com/static/upload/20230822/16926961733913.png)

2. 设置小瓶RPA启动流程

![设置小瓶RPA启动流程](public/Snipaste_2025-03-14_22-03-54.png)


详细:
https://rpa.pbottle.com/a-13985.html


## Linux  多任务定时任务

Linux **crontab** 是 Linux 系统中用于设置周期性被执行的指令的命令。

当安装完成操作系统之后,默认便会启动此任务调度命令。

crond 命令每分钟会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。

Cron 表达式在线工具:https://www.jyshare.com/front-end/9444/


================================================
FILE: docs/开机启动.md
================================================
# 开机启动

软件和流程均支持零操作,开机即可自动启动。

## 开机启动小瓶RPA基座

设置-》基础设置-》开机自动启动

![小瓶RPA设置开机启动](public/imageautostart.png)

###  信创系统

信创系统根据系统启动方式不同,设置方式不同


## 开机启动流程任务

设置-》基础设置-》开机自动启动

![小瓶RPA设置开机启动](public/imageautostart2.png)



================================================
FILE: docs/手机应用的自动化.md
================================================
# 手机应用的自动化

手机应用可以采用 Android 模拟器方案 或者 真机投屏交互方案。得益于小瓶RPA采用纯图像识别的驱动方式,完全兼容各种手机应用模拟器 和 手机厂商的镜像投屏
 **可以使用除了web增强插件外的任意api接口能力** 

## 手机模拟器方案

经过我们测试的推荐: 蓝叠

已经有问题的:雷电模拟器,剪切板同步延迟,输入有问题


## 真机投屏交互方案

手机厂商都提供可以息屏操作的真机电脑方案

- vivo办公套件
- 华为智慧互联
  
![真机投屏交互方案](https://foruda.gitee.com/images/1730729973222516589/8f50e7d2_799608.jpeg)

## 手机验证码自动登录

现在很多软件权限的验证,都是通过手机短信验证码的。

小瓶RPA可以做到**自动验证码登录**,全程无需人工手动输入。

- 连接手机到电脑,开启手机调试。
- 小瓶RPA开发流程脚本,通过 ADB 读取手机中短息。
- 清洗短信数据,得到最新验证码,输入工作流程。

================================================
FILE: docs/无尽模式.md
================================================
# 流程执行的无尽模式

依赖  delaySet()  ,   小瓶RPA可以稳定高效地连续执行流程任务.

A 任务是一个时间长短不定的流程,如果想让A任务不停的运行,这就不能用定时器准确启动了。我们可以 A 任务中开头设置接力任务A自己。这样 7X24 小时,A任务都可以满时效的运行。


## 对比定时器优势

- 定时器不能知道流程的具体结束时间,下次启动前会留有空隙,时效性差
- 定时器只能设置一个任务流程,无尽模式可以让多个任务流程交替接力执行


## demo 示例

[企业版]接力执行脚本.js

================================================
FILE: docs/更多三方功能和拓展.md
================================================

--小瓶RPA兼容所有 nodejs生态

nodejs 文档:https://nodejs.org/docs/latest-v16.x/api/

npm 第三方功能库:https://www.npmjs.com/

 **任何传统编程能够实现的功能模块,都能整合进入我们的小瓶RPA自动化流程。** 
比如:文件处理、数据库操作、邮件发送等


```
//引入小瓶RPA功能
const pbottleRPA = require('./pbottleRPA')
//引入第三方mysql连接功能   事先执行命令:npm install mysql
const mysql      = require('mysql');

var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'root',
  password : '123456',
  database : 'test'
});
connection.connect();
 
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
  //使用小瓶RPA api接口
  pbottleRPA.tts("mysql 获取数量为:" + results[0].solution)
});

```





--联系小瓶RPA客服

![输入图片说明](https://foruda.gitee.com/images/1673232317114810920/31188334_799608.png "小瓶RPA客服")



================================================
FILE: docs/桌面快捷方式.md
================================================
# 桌面快捷方式

每个流程应用都可以在桌面创建一个快捷方式。
注:本功能依赖外部控制方式,企业版可用


## windows 桌面启动

新建文本文件,改名为: 一键启动.bat

发送到桌面快捷方式

```bat
chcp 65001
@echo off
set "BAT_DIR=%cd%"
set "BAT_DIR=%BAT_DIR:\=/%"

@REM  bat和脚本放到一个目录,然后替换下面/后面文件名即可。 一键启动脚本文件名不能有空格。

set "TASK_PATH=%BAT_DIR%/主流程示例.js"
curl  "http://127.0.0.1:49888/?action=pbottleRPA_run&path=%TASK_PATH%"

exit
@REM pause

```


## Linux 桌面启动

新建文本文件,改名为: 一键启动.sh

```bash
#!/bin/bash

# 定义要访问的 URL
url="http://127.0.0.1:49888/?action=pbottleRPA_run&path=TASK_PATH"

# 使用 curl 访问指定路径,并将结果输出
curl -s "$url"

# 检查 curl 命令的退出状态码
if [ $? -eq 0 ]; then
    echo "访问成功"
else
    echo "访问失败"
fi    

```

================================================
FILE: docs/流程录制.md
================================================
# 小瓶RPA流程录制

对于**简单鼠标操作**流程,小瓶RPA可以直接录制成js脚本,然后直接开始执行。

流程录制功能模块条件:
1. windows版本的小瓶RPA
2. 小瓶RPA V2024 以上版本


![小瓶RPA流程录制功能](https://www.pbottle.com/static/upload/20250121/17374423412798.png)

## 录制注意事项

- 选择保存目录为demo目录(目录中需有 pbottleRPA.js 文件)
- 录制目前只支持鼠标事件录制,不支持键盘事件的录制
- 录制结束快捷键:Ctrl+Shift+Q



## 重复执行录制的流程

操作系统原生定时器,稳定高效

- 参考定时器,设置重复执行当前任务。   [进入查看](定时启动.html)
- 修改录制出的js脚本,加入循环逻辑。参考示例:基础(循环、判断、等待)演示.js




================================================
FILE: docs/流程运行日志.md
================================================
# 流程运行日志

小瓶RPA既有当前日志 和 永久硬盘日志

**新版支持:每个流程独立生成一个日志文件,形成一个清晰的运行记录。**


## 实时运行日志

小瓶RPA的流程日志系统会直接承接脚本引擎的标准输出,并会自动附加当前的详细时间:

1. javascript 用户
   
`console.log('流程日志输出了')`

2. python 用户
   
`print('流程日志输出了')`


### 图例

![小瓶RPA日志](https://foruda.gitee.com/images/1713334029181578701/6aefd417_799608.png "Snipaste_2024-04-17_14-06-30.png")



##  永久硬盘日志

查看方式

- 菜单:工具-查看运行日志



## 流程运行完整历史记录

- 每个流程独立生成一个日志文件,形成一个清晰的运行记录。
- 每天形成一个文件目录,方便按事件查找历史记录。


================================================
FILE: docs/流程配置项.md
================================================
# 流程配置项


- 为每个流程设立一个配置项
- 日常用户只修改 excel 或者 json 即可,不需要直接修改流程脚本代码


## 配置项为 excel 格式

建立一个excel文件,如:配置项.xlsx

参考示例: Excel读写


## 配置项为 Json 格式

建立一个 Json 文件,如:配置项.json

```javascript
global.global_config = require('./配置项.json')
```


## 动态流程配置

流程执行时,可手动输入流程配置项目,输入后流程继续执行。

API:用户输入 waitInput

参考示例: 用户手动输入变量示例.js

================================================
FILE: docs/热键和快捷方式.md
================================================
# 热键和快捷方式

方便用户快速启动已有的任务流程,手动和自动充分结合,增效更多应用场景。

比如自动登录,自动数据处理等场景,让小瓶RPA真正成为你的办公小助手。

## 全局流程任务热键

#### Ctrl + Shift + Q

停止当前的脚本运行 & 结束当前的鼠标操作录制

#### Ctrl + Shift + R

重新启动当前的脚本运行



## 任务快捷方式

#### 设置

![设置小瓶RPA快捷方式](https://www.pbottle.com/static/upload/20250113/17366997418579.png)


#### Ctrl + Shift + 数字(1-5)

快捷任务的全局热键



## 任务栏菜单 快捷启动


任务栏菜单显示,右键展开快捷项目,点击快捷执行

![小瓶RPA任务栏的右键菜单](https://www.pbottle.com/static/upload/20250115/17368738582960.png)

================================================
FILE: docs/用 js 脚本开发自动化流程.md
================================================
# 用 js 脚本开发自动化流程 

JavaScript 是互联网上最流行的脚本语言之一,所有网页中都有它的身影,由于是一种类C语言,有着广泛的用户基础并且 JavaScript 很容易学习。

小瓶RPA自动流程脚本的开发只用到了最基础的js部分,建议有任何编程基础的用户直接开始开发您的流程,而不必要先深入学习javascript。


## JavaScript 起步教程

如果您还没有任何脚本编写经验,可以参考网上的基础教程和demo来开发自己的流程,比如:https://www.runoob.com/js/js-intro.html

参考 Demo 示例:基础(循环、判断、等待)演示.js


## 推荐编辑器

VSCode 

下载:https://code.visualstudio.com/

## 推荐版本

nodeJS v18 以上

下载: https://rpa.pbottle.com/a-13943.html



## VSCode 断点调试脚本

VSCode 提供原生对js脚本的断点调试功能动态实时观察变量数值,可设置暂停断点调试。

1. 选择左侧工具栏-》运行调试  
2. 选择 nodejs
3. 点击行号设置红色断点 并运行
   
   ![小瓶脚本调试](./public/Snipaste_2025-03-30_19-57-57.png)


## 引入 NodeJS 三方功能模块

npm 第三方功能库:https://www.npmjs.com/

任何传统编程能够实现的功能模块,都能整合进入我们的小瓶RPA自动化流程。

比如:文件处理、数据库操作、邮件发送等等

引入 nodejs mysql 数据库示例:

```javascript
//引入小瓶RPA功能
const pbottleRPA = require('./pbottleRPA')
//引入第三方mysql连接功能   事先执行命令:npm install mysql
const mysql      = require('mysql');

var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'root',
  password : '123456',
  database : 'test'
});
connection.connect();
 
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
  //使用小瓶RPA api接口
  pbottleRPA.tts("mysql 获取数量为:" + results[0].solution)
});
```

================================================
FILE: docs/用 python 脚本开发自动化流程.md
================================================
# 用 python 脚本开发自动化流程(beta)

Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。

Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构。

小瓶RPA自动流程脚本的开发只用到了最基础的python部分,建议有任何编程基础的用户直接开始开发您的流程,而不必要先深入学习 Python。


## Beta 版本状态说明

- Python 和 JavaScript 脚本层都是开源的, 流程实施方可以自由修改。

- Beta 版本相对于 JavaScript 版本,部分接口和 demo 示例存在延迟更新,用到的时候需要实施团队有自主补齐的 Python 开发能力。

- Beta 仅限于 Python 脚本层,小瓶 RPA 基础软件模块(基座平台) 和 JavaScript 一致 ,保持最新版本状态,所以有 Python 能力的实施团队完全可以正常使用。



## python 起步教程

如果您还没有任何脚本编写经验,可以参考网上的基础教程和demo来开发自己的流程,比如:https://www.runoob.com/python3/python3-tutorial.html


## 推荐编辑器

推荐 VSCode 

下载: https://code.visualstudio.com/


## 推荐版本

推荐 python3.8 以上版本

下载: https://rpa.pbottle.com/a-14009.html


================================================
FILE: docs/硬件键鼠模拟.md
================================================
# 硬件键盘鼠标自动化模拟

⚠ 只有商业用户获取支持

多数 RPA 采用**软件模拟**方式操作系统的键盘和鼠标。
部分网银软件、U盾软件、财务软件等,为了安全禁止其他软件对其操作。
小瓶 RPA **硬件键盘鼠标自动化模拟** 可以解决此问题

## 硬件获取

- 邮寄获取小瓶RPA模拟器
- 电脑有空闲 USB 插口


## 软件配置

- 菜单-》设置-》高级 -》键鼠硬件模拟
- 输入设备后,切换到当前模拟器。
- 修改当前流程脚本到 hid 作用域

![小瓶RPA硬件键盘鼠标自动化模拟器](https://www.pbottle.com/static/upload/20250228/17407282692965.png)

  

参考示例: HID硬件级键盘鼠标演示.js




## 非标设备自动化

借助小瓶RPA硬件级键盘输入等可以实现非标设备自动化

![小瓶RPA](https://foruda.gitee.com/images/1710733715150239316/8b226ffd_799608.png)




================================================
FILE: docs/老旧低配电脑.md
================================================
# 低配置老旧电脑运行自动化


小瓶RPA基座平台采用 c++ 高性能开发方案,已经最大限度的减低电脑配。

## 平台基座系统资源占用优化

设置里取消本地 AI 引擎的启动加载。

取消全部AI引擎后,小瓶RPA主内存可以降低到 40 M 级别。

![小瓶 RPA](https://foruda.gitee.com/images/1721897504278031607/df9c494e_799608.png)


## 流程脚本优化

- 通过 setDefaultDelay  增大默认操作时间间隔
- 通过 wait 手动增加特定的时间间隔
- waitImage 自动等待 取代 wait 固定等待

================================================
FILE: docs/视频教程.md
================================================
# 小瓶RPA系列视频教程

已经陆续推出了免费的 《小瓶RPA系列视频教程》

请网络搜索 :小瓶RPA系列视频教程

## B站入口

https://www.bilibili.com/video/BV1Th4y1C7np/

================================================
FILE: docs/集群控制中心.md
================================================
# 集群控制中心

⚠ 集群控制只有商业版本可以获取支持


**小瓶RPA 集群管理系统可以让终端用户只通过浏览器批量管理自己的任务结果,无需接触具体任务流程。**

小瓶RPA(机器人流程自动化)集群控制中心是整个 RPA 批量任务的核心管理和调度枢纽。 

在一个大型自动自动任务中,RPA 集群控制中心可以根据各个部门的业务需求,将自动化任务分配给不同的机器人,同时实时监控机器人的工作状态。

如果某个机器人出现故障或异常,控制中心可以及时调整任务分配,确保业务的连续性。

通过对运行数据的分析,企业可以发现流程中的瓶颈和优化点,进一步提升 RPA 的效率和效果。它就像是一个指挥中心,确保 RPA 机器人集群高效、有序地运行,为企业的数字化转型提供有力支持。

## 系统基础架构

支持 linux & windows 系统搭建。 一次性设备授权绑定。 

**web 管理后台**,方便局域网、网络用户登录和使用。

![小瓶RPA集群控制中心](https://www.pbottle.com/static/upload/20250115/17369275974197.png)

#### 用户管理

允许新用户进行注册,填写必要的信息,如用户名、密码等。提供登录功能,验证用户身份
以授予相应权限。支持用户修改密码操作。 

用户操作日志可以记录员工何时登录系统、修改了哪些业务数据等。如果发现某些数据被误
修改或存在违规操作,就可以通过操作日志快速找到责任

#### 设备终端管理

终端信息管理,详细记录每一个设备终端的基本信息,如设备信息、名称、添加时间等。 
方便对大量设备进行清晰的识别和管理。 

连接管理,监控和管理设备终端与控制中心的连接状态,确保稳定通信。时处理连接异常情
况,保障任务分配和数据传输的顺畅。 

RPA 终端状态监测,持续监测设备终端的性能指标,如离线、连接、正在运行等。根据性能
状况进行资源调配和优化。


#### 任务管理

当有大量任务需要执行时,控制中心可以根据各节点的实时情况将任务高效地分配出去。通
过对任务状态和进度的持续监控,确保每个worker rpa 机器人都能发挥最有效的能效。 
任务创建与定义,允许用户方便地创建新的 RPA 任务,明确任务的目标参数、流程开始时
间等要求。 

任务分配,根据集群中各节点的状态和能力,合理地将任务分配给合适的节点执行。 
可以考虑负载均衡等因素,以提高整体效率。分发模式分为设备所有终端 + 指定终端 
任务参数设定,设定每个任务的具体参数,确保终端RPA客户端可以接收,并按照参数配
置来执行自动流程任务。 

任务历史记录,保留任务的历史信息,包括执行时间、结果等,便于追溯和复盘。


#### 定制任务类型

基于 BS 架构的 web 管理系统,可以快速为客户定制专有流程的任务管理系统。

定制符合业务场景的,专有输入输出(input|output)文件格式的任务类型。


## 小瓶RPA集群系统使用文档

支持 linux & windows 系统私有云搭建,终极数据安全保障。

一次性设备授权绑定,无限终端数量。

系统预览:

![小瓶RPA集群服务中心登录](./public/Snipaste_2025-09-21_15-07-56.png)

#### 终端配置

配置步骤:

1. 打开小瓶RPA终端
2. 设置(系统设置)
3. 高级
4. 集群控制中心服务地址,如:`http://pbottleRPA_cluster_test.com:39088/api/query`
5. 集群控制中心通信频率 如:`5`,0:关闭;

结果:软件集群模式 显示绿灯

#### 流程日志回传

日志回传需要等待业务脚本执行完成后,终端主动回传,可能会有延迟。

流程开始的地方添加回传脚本

```
pbottleRPA.delaySet('./[企业版]集群控制中心日志回传.js'); 
```


================================================
FILE: docs/验证码自动化.md
================================================
# 验证码自动化

**温馨提示:小瓶RPA本身以模拟操作和数据处理为主,它不具有任何破解功能**

优先选择记住登录状态、U盾、定时刷新等方法保持用户权限的会话。

## 简单图形验证码
   
小瓶RPA文字OCR模块可以解决简单字母数字验证码

![验证码示例](public/c36742f9fd8d17614f293302eab29d79.jpeg)

## 人工辅助半自动化

流程如下:

1. 小瓶RPA获取验证码截图
2. 发送系统管理员手机,人工识别 
3. 识别结果发送回小瓶RPA自动输入
4. 完成验证!!

*提示:一般系统都有记住登录状态功能,请优先勾选,一台电脑只需登录一次。*

## 用第三方网络API

百度搜索 “验证码识别API”,一般都是些小厂商的api云服务, 注册付费获取api权限并待用

- http://www.ttshitu.com/test.html
- https://www.jfbym.com/price.html

流程:

1. 小瓶RPA获取验证码截图
2. 上传截图到三方API
3. 小瓶RPA根据返回结果生成键鼠逻辑
4. RPA 操作完成验证

## 自有系统私有化部署

小瓶RPA增值服务之一,基于AI图像技术为你独立训练适配当前验证码的本地服务。

================================================
FILE: package.json
================================================
{
  "name": "pbottle-rpa-demo",
  "version": "2025.4.0",
  "description": "小瓶RPA基础演示流程,使用教程:https://rpa.pbottle.com/a-14019.html,流程开发文档:https://rpa.pbottle.com/docs/",
  "main": "",
  "scripts": {
    "install-dep": "npm install  exceljs mammoth docx"
  },
  "author": "leo@pbottle.com",
  "license": "MIT"
}


================================================
FILE: pbottleRPA.js
================================================
/**
 *  小瓶 RPA 标准库API  NodeJS版本
 *  官网:https://rpa.pbottle.com/
 *  作者:leo@pbottle.com
 *  
 *  欢迎各路高手将本代码转换成 python、lua、C# 等其他语言封装
 * 
 */

const path = require("node:path");
const fs = require("node:fs");
const os = require('os');
const tls = require('node:tls');
const childProcess = require('node:child_process');

/**
 * 当前脚本的路径,结尾无/  如 'D:/pbottleRPAdemo'
 */
const jsPath = path.resolve('./');
const CppUrl = `http://127.0.0.1:49888/`
let basePath = process.env.RPAbaseDir; //基座路径
let homePath = process.env.RPAhomeDir;
let curlCommand = 'curl';  //优先使用系统的,如果系统不存在curl命令,使用小瓶RPA自带的

console.log("基座服务地址:(NodeJS)", CppUrl);
exports.jsPath = jsPath
exports.basePath = basePath
exports.__dirname = jsPath
exports.目录路径 = jsPath

//node:fs
exports.fs = fs
//node:path
exports.path = path

let defaultDelay = 1000;  //默认值一秒
/**
 * 设置RPA模拟操作的延时  包含鼠标、键盘、粘贴、打开网页操作
 * 设置为 0  可以用 sleep() 手动管理操作延时
 * @param {number} millisecond   毫秒单位的数字,系统默认 1000 毫秒 即1秒
 */
let setDefaultDelay = (millisecond) => {
    defaultDelay = millisecond
}
exports.setDefaultDelay = setDefaultDelay
exports.设置默认操作延时 = setDefaultDelay




/**
 * 发出系统警告声音
 * @returns 
 */
let beep = () => {
    let url = `${CppUrl}?action=beep`
    // console.log(url)
    let res = getHtml(url)
}
exports.beep = beep
exports.蜂鸣声 = beep


/**
 * 日志输出,同时生成文件日志
 */
exports.log = console.log
exports.日志输出 = console.log


/**
 * 系统原生消息提示
 * @param {string} title  标题
 * @param {string} content  内容
 * @returns 
 */
let showMsg = (title, content) => {
    title = encodeURIComponent(title)
    content = encodeURIComponent(content)
    let url = `${CppUrl}?action=showMsg&title=${title}&content=${content}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.showMsg = showMsg
exports.显示系统消息 = showMsg


/**
 * (强行)关闭指定软件
 * @param {string} processName 进程名称,如:'WINWORD.EXE' 任务管理器 ‘进程名称’ 栏目 。注意不是 名称,如不显示,右键勾选显示这一栏目即可
 * @param {boolean} force 是否强制,相当于模拟任务管理器的结束任务操作。默认普通关闭,可能跟随保存确认框
 */
let kill = (processName, force = false) => {
    let forceCMD = ''
    if (force) {
        forceCMD = '/F'
    }
    try {
        childProcess.execSync(`taskkill ${forceCMD} /IM ${processName}`, { stdio: 'ignore', encoding: 'utf8' })
    } catch (error) {
        console.error(`关闭进程(${processName})失败,可能软件未运行`);
        return;
    }
    console.log('关闭进程成功:' + processName);
}
exports.kill = kill
exports.关闭软件 = kill


/**
 * 有效屏幕内显示一个彩色方框,直观提示流程操作范围和目标的当前的定位
 * V2024.6以上版本有效
 * @param {number} fromX  起始位置xy坐标,屏幕左上角为零点
 * @param {number} fromY 
 * @param {number} width  宽度
 * @param {number} height 高度
 * @param {string} color  颜色 红绿蓝黄4色可选:red|green|blue|yellow 
 * @param {number} msec  显示持续时间 单位毫秒
 * @returns 
 */
let showRect = (fromX = 0, fromY = 0, width = 500, height = 500, color = 'red', msec = 500) => {
    fromX = Math.round(fromX)
    fromY = Math.round(fromY)
    width = Math.round(width)
    height = Math.round(height)

    color = encodeURIComponent(color)
    let url = `${CppUrl}?action=showRect&fromX=${fromX}&fromY=${fromY}&width=${width}&height=${height}&color=${color}&msec=${msec}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.showRect = showRect
exports.显示标记框 = showRect


/**
 * 强制退出当前脚本
 * @param {...any} msg 退出时候输出的信息
 */
let exit = (...args) => {
    console.log(...args)
    beep()
    process.exit(1)
}
exports.exit = exit
exports.退出流程 = exit


/**
 * 脚本暂停等待操作响应 (毫秒)
 * 注意:一次等待上限时长两分钟内
 * @param {number} milliseconds  毫秒
 * @returns 
 */
let sleep = (milliseconds) => {
    // childProcess.execSync(` node -e "setTimeout(() => console.log('sleep ${milliseconds} 结束'), ${milliseconds})" `, { stdio: ['ignore', 'ignore', 'ignore'], encoding: 'utf8' })
    if (milliseconds < 1) {
        // console.log('milliseconds input error');
        return;
    }
    milliseconds = Math.floor(milliseconds) //毫秒取整
    if (milliseconds >= 120000) {
        console.log('警告:一次等待上限时长两分钟内');
    }

    milliseconds -= 20  //减小毫秒误差,接口请求导致,大小受电脑运行速度影响
    if (milliseconds < 1) {
        milliseconds = 1
    }
    let url = `${CppUrl}?action=httpSleep&milliseconds=${milliseconds}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.sleep = sleep
exports.睡眠毫秒 = sleep


/**
 * 脚本暂停等待操作响应 (秒)
 * 注意:一次等待超过100s, 会有日志提示
 * @param {number} seconds  秒,  缺省值为 1 秒。支持小数。
 */
let wait = (seconds = 1) => {
    if (seconds <= 0 || !isNumeric(seconds)) {
        console.log('pbottleRPA.wait:seconds input error');
        return;
    }
    if (seconds > 100) {  //100秒
        let quotient = Math.floor(seconds / 100)
        for (let i = 0; i < quotient; i++) { //每100秒
            sleep(100 * 1000)
            console.log(`提示:已等待100s...`);
        }
        seconds = seconds % 100;
        sleep(seconds * 1000)
    } else {
        sleep(seconds * 1000)
    }
}
exports.wait = wait
exports.等待 = wait

/**
 * 移动鼠标到指定位置并点击  起点为屏幕左上角
 * @param {number} x   横坐标
 * @param {number} y   纵坐标
 * @param {number} interval  像素间隔时间,越大移动越慢  毫秒单位,默认:0
 * @returns 
 */
let moveMouseSmooth = (x, y, interval = 0) => {
    x = Math.round(x)
    y = Math.round(y)
    let url = `${CppUrl}?action=moveMouse&x=${x}&y=${y}&interval=${interval}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay);
    return res;
}
exports.moveMouseSmooth = moveMouseSmooth
exports.moveMouse = moveMouseSmooth  //增加别名
exports.鼠标移动 = moveMouseSmooth


/**
 * 移动鼠标到指定位置并点击
 * @param {number} x 横坐标
 * @param {number} y 纵坐标
 */
let moveAndClick = (x, y) => {
    // call local functions directly instead of using `this` which may not refer to module exports
    moveMouseSmooth(x, y)
    mouseClick()
}
exports.moveAndClick = moveAndClick
exports.鼠标移动并点击 = moveAndClick


/**
 * 当前位置点击鼠标 默认左键  可选 'right'
 * @param {string} leftRight    可选
 * @param {number} time 点按时间 单位毫秒  可选
 * @returns 
 */
let mouseClick = (leftRight = 'left', time = 30) => {

    let url = `${CppUrl}?action=mouseLeftClick&time=${time}`
    if (leftRight == 'right') {
        url = `${CppUrl}?action=mouseRightClick&time=${time}`
    }
    // console.log(url)
    let res = getHtml(url)

    sleep(defaultDelay);
    return res;
}
exports.mouseClick = mouseClick
exports.鼠标点击 = mouseClick


/**
 * 双击鼠标  默认左键
 * @returns 
 */
let mouseDoubleClick = () => {

    let url = `${CppUrl}?action=mouseDoubleClick`

    // console.log(url)
    let res = getHtml(url)

    sleep(defaultDelay);
    return res;
}
exports.mouseDoubleClick = mouseDoubleClick
exports.鼠标双击 = mouseDoubleClick


/**
 * 鼠标滚轮
 * @param {number} data 滚动的量  默认为-720   向下滚动720度
 * @returns 
 */
let mouseWheel = (data = -720) => {
    let url = `${CppUrl}?action=mouseWheel&data=${data}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay);
    return res;
}
exports.mouseWheel = mouseWheel
exports.鼠标滚轮 = mouseWheel


/**
 * 鼠标左键拖到指定位置
 * @param {number} x 
 * @param {number} y 
 * @returns 
 */
let mouseLeftDragTo = (x, y) => {
    x = Math.round(x)
    y = Math.round(y)
    let url = `${CppUrl}?action=mouseLeftDragTo&x=${x}&y=${y}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay);
    return res;
}
exports.mouseLeftDragTo = mouseLeftDragTo
exports.鼠标左键拖动 = mouseLeftDragTo


/**
 * 鼠标右键拖到指定位置
 * @param {number} x 
 * @param {number} y 
 * @returns 
 */
let mouseRightDragTo = (x, y) => {
    x = Math.round(x)
    y = Math.round(y)
    let url = `${CppUrl}?action=mouseRightDragTo&x=${x}&y=${y}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay);
    return res;
}
exports.mouseRightDragTo = mouseRightDragTo
exports.鼠标右键拖动 = mouseRightDragTo



/**
 * 屏幕一个点取色
 * @param {number} x 
 * @param {number} y 
 * @returns {string} 返回颜色值 如:'#000000'
 */
let getScreenColor = (x, y) => {
    let url = `${CppUrl}?action=getScreenColor&x=${x}&y=${y}`
    // console.log(url)
    let res = getHtml(url)
    let jsonRes = JSON.parse(res)
    return jsonRes.rs;
}
exports.getScreenColor = getScreenColor
exports.获取屏幕颜色 = getScreenColor


/**
 * 屏幕截图
 * @param {string} savePath  保存路径默认 我的图片,图片格式为PNG;如果使用自定义路径请以 '.png' 结尾; 
 * @param {number} x  截图开始位置
 * @param {number} y 
 * @param {number} w  可选 截图宽度
 * @param {number} h  可选 截图长度
 * @returns 
 */
let screenShot = (savePath = '', x = 0, y = 0, w = -1, h = -1) => {

    if (savePath) { //整理路径
        savePath = path.resolve(savePath)
        savePath = encodeURIComponent(savePath)
    }

    x = parseInt(x)
    y = parseInt(y)
    w = parseInt(w)
    h = parseInt(h)

    if (x != 0 || y != 0 || w != -1 || h != -1) {
        showRect(x, y, w, h);
    }

    let url = `${CppUrl}?action=screenShot&savePath=${savePath}&x=${x}&y=${y}&w=${w}&h=${h}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.screenShot = screenShot
exports.屏幕截图 = screenShot

/**
 * 按键名称 转成 键值
 * @param {string} name 
 * @returns {number}
 */
function keycode(name) {
    name = name.trim().toLowerCase();
    const replacement_dict = {
        'backspace': 8,
        'tab': 9,
        'enter': 13,
        'shift': 16,
        'ctrl': 17,
        'alt': 18,
        'pause/break': 19,
        'caps lock': 20,
        'esc': 27,
        'space': 32,
        'page up': 33,
        'page down': 34,
        'end': 35,
        'home': 36,
        'left': 37,
        'up': 38,
        'right': 39,
        'down': 40,
        'insert': 45,
        'delete': 46,
        'command': 91,
        'left command': 91,
        'right command': 93,
        'numpad *': 106,
        'numpad +': 107,
        'numpad -': 109,
        'numpad .': 110,
        'numpad /': 111,
        'num lock': 144,
        'scroll lock': 145,
        'my computer': 182,
        'my calculator': 183,
        'windows': 91,
        '⇧': 16,
        '⌥': 18,
        '⌃': 17,
        '⌘': 91,
        'ctl': 17,
        'control': 17,
        'option': 18,
        'pause': 19,
        'break': 19,
        'caps': 20,
        'return': 13,
        'escape': 27,
        'spc': 32,
        'spacebar': 32,
        'pgup': 33,
        'pgdn': 34,
        'ins': 45,
        'del': 46,
        'cmd': 91,
        'f1': 112,
        'f2': 113,
        'f3': 114,
        'f4': 115,
        'f5': 116,
        'f6': 117,
        'f7': 118,
        'f8': 119,
        'f9': 120,
        'f10': 121,
        'f11': 122,
        'f12': 123,

        ';': 186,
        '=': 187,
        ',': 188,
        '-': 189,
        '.': 190,
        '/': 191,
        '`': 192,
        '[': 219,
        '\\': 220,
        ']': 221,
        "'": 222,

        "0": 48,
        "1": 49,
        "2": 50,
        "3": 51,
        "4": 52,
        "5": 53,
        "6": 54,
        "7": 55,
        "8": 56,
        "9": 57,
        "a": 65,
        "b": 66,
        "c": 67,
        "d": 68,
        "e": 69,
        "f": 70,
        "g": 71,
        "h": 72,
        "i": 73,
        "j": 74,
        "k": 75,
        "l": 76,
        "m": 77,
        "n": 78,
        "o": 79,
        "p": 80,
        "q": 81,
        "r": 82,
        "s": 83,
        "t": 84,
        "u": 85,
        "v": 86,
        "w": 87,
        "x": 88,
        "y": 89,
        "z": 90,
    }
    return replacement_dict[name]
}

/**
 * 模拟键盘按键触发基础事件
 * @param {string} key  按键名称参考:https://www.pbottle.com/a-13862.html
 * @param {string} "up" 或 "down"  默认按下down。up松开按键
 * @returns 
 */
let keyToggle = (key, upDown = 'down') => {
    let upDown_n = 0;
    if (upDown == 'up') {
        upDown_n = 2;
    }
    let key_n = keycode(key)
    if (key_n === undefined) {
        console.log(`⚠ 按键 ${key} 不存在!~`);
        return
    }
    let url = `${CppUrl}?action=keyToggle&key_n=${key_n}&upDown_n=${upDown_n}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.keyToggle = keyToggle
exports.键盘基础触发 = keyToggle


/**
 * 模拟鼠标按键触发基础事件
 * @param {string} key   鼠标 left | right | middle  
 * @param {string} "up" 或 "down"  默认按下down。up松开按键
 * @returns 
 */
let mouseKeyToggle = (key = 'left', upDown = 'down') => {
    let upDown_n = 0;
    if (upDown == 'up') {
        upDown_n = 2;
    }
    let key_n = 0
    switch (key) {
        case 'right':
            key_n = 1
            break;
        case 'middle':
            key_n = 2
            break;
        default:
            key_n = 0
            break;
    }
    let url = `${CppUrl}?action=mouseKeyToggle&key_n=${key_n}&upDown_n=${upDown_n}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.mouseKeyToggle = mouseKeyToggle
exports.鼠标基础触发 = mouseKeyToggle


/**
 * 按一下键盘   支持组合按键 加号连接 如:  keyTap('ctrl + a')
 * @param {string} key  按键名称参考:https://www.pbottle.com/a-13862.html
 */
let keyTap = (key) => {

    if (key.includes('+')) {
        let subkeys = new Array();
        subkeys = key.split('+')
        subkeys = subkeys.map((value) => {
            return value.trim()
        })
        for (let index = 0; index < subkeys.length; index++) {
            const element = subkeys[index];
            // keyToggle(element,"up")  //净化复位
            keyToggle(element, "down")
        }

        subkeys = subkeys.reverse()
        for (let index = 0; index < subkeys.length; index++) {
            const element = subkeys[index];
            keyToggle(element, "up")
        }
    } else {
        // keyToggle(key,"up")  //净化复位
        keyToggle(key, "down")
        keyToggle(key, "up")
    }

    sleep(defaultDelay);
}
exports.keyTap = keyTap
exports.键盘按键 = keyTap


/**
 * 屏幕查找图象定位
 * @param {string|Array} tpPaths 搜索的小图片,建议png格式  相对路径:./image/123.png
 * @param {number} miniSimilarity 可选,指定最低相似度,默认0.85。取值0-1,1为找到完全相同的。
 * @param {number} fromX=0 可选,查找开始的开始横坐标
 * @param {number} fromY=0 可选,查找开始的开始纵坐标
 * @param {number} width=-1 可选,搜索宽度
 * @param {number} height=-1 可选,搜索高度
 * @returns {{x:number,y:number}|false} 返回找到的结果json 格式:{x,y} 相对于左上角原点
 */
var findScreen = (tpPaths, miniSimilarity = 0.85, fromX = 0, fromY = 0, width = -1, height = -1) => {

    if (fromX < 0 || fromY < 0) {
        throw new Error(`错误:找图起始点不能为负,x:${fromX} y:${fromY} `);
    }

    if (fromX != 0 || fromY != 0 || width != -1 || height != -1) {
        showRect(fromX, fromY, width, height);
    }

    tpPaths = Array.isArray(tpPaths) ? tpPaths : [tpPaths]
    console.log(tpPaths);
    
    
    for (let index = 0; index < tpPaths.length; index++) {
        let tpPath = tpPaths[index];
        tpPath = path.resolve(tpPath)
        tpPath = encodeURIComponent(tpPath)
        let url = `${CppUrl}?action=findScreen&imgPath=${tpPath}&fromX=${fromX}&fromY=${fromY}&width=${width}&height=${height}`
        // console.log(url)
        let res = getHtml(url)

        // console.log(res.getBody('utf8'));
        let jsonRes = JSON.parse(res);

        // console.log(tpPath);
        // console.log(jsonRes);

        if (jsonRes.error) {
            console.log(jsonRes.error);
            return false;
        }
        if (jsonRes.value >= miniSimilarity) {
            showRect(jsonRes.x - 25, jsonRes.y - 25, 50, 50, 'green');
            return jsonRes;
        }
    }
    return false;
}
exports.findScreen = findScreen
exports.寻找图像 = findScreen


/**
 * 查找文字,注:此功能受电脑性能影响,低配电脑可能速度较慢。 需要小瓶RPA客户端版本 > V2024.5
 * @param {string} inputTxt 
 * @param {number} fromX=0 可选,查找开始的开始横坐标
 * @param {number} fromY=0 可选,查找开始的开始纵坐标
 * @param {number} width=-1 可选,搜索宽度
 * @param {number} height=-1 可选,搜索高度
 * @returns {{x:number,y:number,text:string}|false}  返回json结果:{x,y,text} x,y坐标相对于屏幕左上角的原点
 */
var findText = (inputTxt, fromX = 0, fromY = 0, width = -1, height = -1) => {
    let jsonDatas = aiOcr('screen', fromX, fromY, width, height);
    // console.log(jsonDatas);
    let result = false;  
    jsonDatas.forEach(element => {
        // console.log(element.text);
        if (element.text.includes(inputTxt)) {
            result = element
        }
    });
    if (result !== false) {
        showRect(result.x - 25, result.y - 25, 50, 50, 'green');
    }
    return result;
}
exports.findText = findText
exports.寻找文字 = findText

/**
 * 等待屏幕指定文字出现
 * @param {string} inputTxt 搜索文字
 * @param {number} fromX 可选,查找开始的开始横坐标
 * @param {number} fromY 可选,查找开始的开始纵坐标
 * @param {number} width 可选,搜索宽度
 * @param {number} height 可选,搜索高度
 * @param {function} intervalFun 回调函数,用于中途判断是否继续等待,返回值为stopWait时,停止等待
 * @param {number} timeOut 超时时间,单位秒
 * @returns 
 */
var waitText = (inputTxt, fromX = 0, fromY = 0, width = -1, height = -1,intervalFun = () => {}, timeOut = 20) => {
    console.log('waiting Text:', inputTxt);
    for (let index = 0; index < timeOut; index++) {
        sleep(1000)
        let position = findText(inputTxt, fromX, fromY, width, height)
        if (position !== false) {
            return position;
        }
        if (typeof intervalFun === 'function' && intervalFun() == 'stopWait') {
            console.log('stopWait from intervalFun');
            return false
        }
    }
    //debug 保存当前屏幕
    console.log('已经保存超时截图到:我的图片');
    screenShot();
    //error
    throw new Error(`等待文字超时 ${inputTxt}`);
}

exports.waitText = waitText
exports.等待文字 = waitText


/**
 * 屏幕查找物体或者窗口轮廓
 * 调试:软件根目录会生成 debug/findContours.png
 * 
 * @param {number} minimumArea 轮廓最小面积  默认过滤掉 10x10 以下的元素
 * @param {number} fromX  查找起点
 * @param {number} fromY 
 * @param {number} width  查找范围
 * @param {number} height 
 * @returns {[]} 所有查找到的轮廓信息,包含闭合区域的起始坐标,中点坐标,面积,id。 格式:[{ x: 250, y: 10, cx: 265.5, cy: 30.5, area: 2401, id: 42 },...]  xy相对于原点
 */
var findContours = (minimumArea = 1000, fromX = 0, fromY = 0, width = -1, height = -1) => {

    if (fromX < 0 || fromY < 0) {
        throw new Error(`错误:轮廓查找起始点不能为负,x:${fromX} y:${fromY} `);
    }

    if (fromX != 0 || fromY != 0 || width != -1 || height != -1) {
        showRect(fromX, fromY, width, height);
    }

    let url = `${CppUrl}?action=findContours&minimumArea=${minimumArea}&fromX=${fromX}&fromY=${fromY}&width=${width}&height=${height}`
    // console.log(url)
    let res = getHtml(url)

    // parse the response string directly (getHtml returns stdout string)
    let jsonRes = JSON.parse(res);

    for (const json of jsonRes) {
        json.x += fromX
        json.y += fromY
    }
    // console.log(jsonRes);
    return jsonRes;
}
exports.findContours = findContours
exports.寻找轮廓 = findContours

/**
 * 当前位置 粘贴(输入)文字  
 * @param {string} txt  复制到电脑剪切板的文本
 */
var paste = (txt) => {
    copyText(txt)
    // sleep(200)
    keyTap('ctrl+v')

    sleep(defaultDelay);
}
exports.paste = paste
exports.粘贴输入 = paste


/**
 * 图片相似度对比  需要小瓶RPA客户端版本 > V2025.3
 * @param {string} path1  图片1路径
 * @param {string} path2  图片2路径
 * @param {'SIFT' | 'ORB' | 'SSIM'} checkType  对比算法  默认 'ORB'
 * @returns {{score:number, time:number}}  score相似度分数 0-1 ; time耗时秒
 */
var imgSimilar = (path1, path2, checkType = 'ORB') => {
    path1 = encodeURIComponent(path1)
    path2 = encodeURIComponent(path2)
    let url = `${CppUrl}?action=imgSimilar&path1=${path1}&path2=${path2}&checkType=${checkType}`
    let res = getHtml(url)
    return JSON.parse(res);
}
exports.imgSimilar = imgSimilar
exports.图片相似度对比 = imgSimilar




/**
 * 模拟复制文字,相当于选择并复制文本内容  v2025.0以上生效
 * @param {string} txt 复制的文本内容
 */
var copyText = (txt) => {
    txt = encodeURIComponent(txt)
    let url = `${CppUrl}?action=copyText&txt=${txt}`
    // console.log(url)
    let res = getHtml(url)
    return res
}
exports.copyText = copyText
exports.复制文字 = copyText

/**
 * 模拟复制操作,支持文件路径和文件夹路径,复制后在目标文件夹ctrl+V 即可粘贴  V2024.7开始生效
 * 复制文件后,在微信发送窗口粘贴,即可发送文件 
 * @param {string} filepath  绝对路径
 */
var copyFile = (filepath) => {
    filepath = path.resolve(filepath)
    if (!fs.existsSync(filepath)) {
        console.log('copyFile警告:文件路径不存在', filepath);
    }
    filepath = filepath.replace(/\\/g, '/')
    filepath = encodeURIComponent(filepath)
    let url = `${CppUrl}?action=copyFile&path=${filepath}`
    // console.log(url)
    let res = getHtml(url)
    return res
}
exports.copyFile = copyFile
exports.复制文件 = copyFile

/**
 * 获取当前电脑的剪切板内容,系统剪切板支持多种格式   版本 V2024.2 开始生效
 * ①纯文本格式:普通复制  如'小瓶RPA'
 * ②图片格式 base64形式:浏览器复制图片    'data:image/png;base64,' 开头
 * ③html格式:浏览器或者钉钉复制富文本综合内容    '<html>'开头
 * @returns 结果文本
 */
var getClipboard = () => {
    let url = `${CppUrl}?action=getClipboard`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.getClipboard = getClipboard
exports.获取剪切板内容 = getClipboard



/**
 * 通知到手机
 * 通过小瓶云发送微信通知 (微信到达率高,并且免费)
 * @param {string} title 消息标题
 * @param {string} content  消息详细内容
 * @param {string} key  获取key详情方法:https://www.pbottle.com/a-12586.html
 */
var wxMessage = (title, content, key) => {

    let url = `https://yun.pbottle.com/manage/yun/?msg=${encodeURIComponent(content)}&name=${encodeURIComponent(title)}&key=${key}`;
    let res = getHtml(url)
    console.log('发送微信消息:', res);

}
exports.wxMessage = wxMessage
exports.微信消息发送 = wxMessage


/**
 * 向指定API网址post一个json,最常用网络接口方式
 * @param {string} url API网络地址 
 * @param {object} msgJson Json对象 
 * @param {object} headersJson 请求头 Json对象 
 * @param {string} method e.g. GET, POST, PUT, DELETE or HEAD
 * @returns {string}
 */
var postJson = (url, msgJson, headersJson = {}, method = 'POST') => {

    const jsonData = JSON.stringify(msgJson);
    const commandArgs = [
        '-X', method,
        '-H', 'Content-Type: application/json',
        "--silent", "--show-error",
        '-d', jsonData,
        url
    ];
    if (Object.keys(headersJson).length !== 0) {
        for (const [key, value] of Object.entries(headersJson)) {
            commandArgs.push('-H', `${key}: ${value}`);
        }
    }
    const result = childProcess.spawnSync(curlCommand, commandArgs, { encoding: 'utf8' });
    if (result.error) {
        throw new Error('postJson 执行 curl 命令时出错:' + result.error.message);
    }
    if (result.status !== 0) {
        throw new Error('postJson curl 命令执行失败: ' + result.stderr);
    }
    return result.stdout;
}
exports.postJson = postJson
exports.提交json = postJson

/**
 * 向指定API网址post一个json文件,适合大型json内容
 * @param {string} url API网络地址 
 * @param {string} msgJsonFile Json文件路径 
 * @param {object} headersJson 请求头Json对象 
 * @param {string} method e.g. GET, POST, PUT, DELETE or HEAD
 * @returns {string}
 */
var postJsonFile = (url, msgJsonFile, headersJson = {}, method = 'POST') => {

    msgJsonFile = path.resolve(msgJsonFile);
    const commandArgs = [
        '-X', method,
        '-H', 'Content-Type: application/json',
        '-d', `@${msgJsonFile}`,
        url
    ];
    if (Object.keys(headersJson).length !== 0) {
        for (const [key, value] of Object.entries(headersJson)) {
            commandArgs.push('-H', `${key}: ${value}`);
        }
    }
    const result = childProcess.spawnSync(curlCommand, commandArgs, { encoding: 'utf8' });
    if (result.error) {
        throw new Error('postJsonFile 执行 curl 命令时出错: ' + result.error.message);
    }
    if (result.status !== 0) {
        throw new Error('postJsonFile curl 命令执行失败:' + result.stderr);
    }
    return result.stdout;
}
exports.postJsonFile = postJsonFile
exports.提交json文件 = postJsonFile

/**
 * 普通请求网址,获取返回的html文本
 * @param {string} url 网络地址 get方法
 * @param {object} headersJson  请求头 Json对象 
 * @param {string} method  请求方法 :GET, POST, PUT, DELETE or HEAD 
 * @returns {string} 返回的文本
 */
function getHtml(url, headersJson = {}, method = 'GET') {
    let commandArgs = ['-X', method, url];
    if (Object.keys(headersJson).length !== 0) {
        for (const [key, value] of Object.entries(headersJson)) {
            commandArgs.push('-H', `${key}: ${value}`);
        }
    }
    const result = childProcess.spawnSync(curlCommand, commandArgs, { encoding: 'utf8' });
    if (result.error) {
        throw new Error('getHtml 执行 curl 命令时出错: ' + result.error.message);
    }
    if (result.status !== 0) {
        throw new Error('getHtml curl 命令执行失败: ' + result.stderr);
    }
    return result.stdout;
}
exports.getHtml = getHtml
exports.请求网址 = getHtml

/**
 * 发送邮件;注意这个方法是个异步方法,请参考示例;
 * @param {string} to  收件人地址
 * @param {string} subject 邮件主题
 * @param {string} content 邮件内容;文本文件,换行用 '\n'
 * @param {string} host 服务器地址(如:smtp.qq.com)
 * @param {number} port 服务器端口 默认是465
 * @param {string} user 认证信息(用户名)一般也是发送邮件地址
 * @param {string} pass 认证信息(密码)
 * @returns 
 */
function sendMail(
    to,
    subject,
    content,
    host = 'smtp.qq.com',
    port = 465,
    user = 'leo191@foxmail.com',
    pass = 'fxfqtsxmwcohbcbc',
) {
    return new Promise((resolve, reject) => {
        const client = tls.connect(port, host, { rejectUnauthorized: false }, () => {
            console.log('✅ 已连接到 SMTP 服务器');
        });
        client.setEncoding('utf8');
        if (user == 'leo191@foxmail.com') {
            content += '\n\n\ 请不要将演示测试邮箱用作实际业务,详细查看:https://rpa.pbottle.com/a-14106.html'
        }
        const commands = [
            `EHLO ${host}`,
            `AUTH LOGIN`,
            Buffer.from(user).toString('base64'),
            Buffer.from(pass).toString('base64'),
            `MAIL FROM:<${user}>`,
            `RCPT TO:<${to}>`,
            `DATA`,
            [
                `From: "小瓶RPA" ${user}`,
                `To: ${to}`,
                `Subject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=`,
                `Content-Type: text/plain; charset=utf-8`,
                ``,
                `${content}`,
                `.`
            ].join('\r\n'),
            `QUIT`
        ];

        let step = 0;
        let responseBuffer = '';

        client.on('data', (data) => {
            responseBuffer += data;

            if (/(\n|\r\n)\d{3}\s/.test(data) || data.endsWith('\n')) {
                const code = parseInt(data.substring(0, 3));
                console.log('📩 SMTP:', data.trim());
                if (code >= 400) {
                    client.end();
                    reject(new Error(`SMTP 错误: ${data.trim()}`));
                    return;
                }
                if (step < commands.length) {
                    const cmd = commands[step++];
                    console.log('➡️ 发送:', cmd.split('\r\n')[0]);
                    client.write(cmd + '\r\n');
                } else {
                    client.end();
                    resolve('✅ 邮件发送成功');
                }
            }
        });

        client.on('error', (err) => {
            reject(err);
        });
    });
}
exports.sendMail = sendMail
exports.发送邮件 = sendMail

/**
 * 从网络下载一个文件到本地路径
 * @param {string} fileUrl 网址
 * @param {string} filename 本地路径文件名
 * @param {object} headersJson  请求头 Json对象 
 */
function downloadFile(fileUrl, filename, headersJson = {}) {

    const dirPath = path.dirname(filename);
    if (!fs.existsSync(dirPath)) {
        fs.mkdirSync(dirPath, { recursive: true });
    }

    filename = path.resolve(filename)
    console.log('下载文件到:', filename)
    const commandArgs = [
        '-o', filename,
        fileUrl
    ];
    if (Object.keys(headersJson).length !== 0) {
        for (const [key, value] of Object.entries(headersJson)) {
            commandArgs.push('-H', `${key}: ${value}`);
        }
    }
    const result = childProcess.spawnSync(curlCommand, commandArgs, { encoding: 'utf8' });
    if (result.error) {
        throw new Error('downloadFile 执行 curl 命令时出错' + result.error.message);
    }
    if (result.status !== 0) {
        throw new Error('downloadFile curl 命令执行失败' + result.stderr);
    }
    return result.stdout;
}
exports.downloadFile = downloadFile
exports.下载文件 = downloadFile

/**
 * 从文本到语音(TextToSpeech)  语音播报
 * 非阻塞
 * @param {string} text 朗读内容
 */
var tts = (text) => {
    text = encodeURIComponent(text)
    let url = `${CppUrl}?action=tts&txt=${text}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay);
}
exports.tts = tts
exports.文字转语音 = tts


/**
 * 用电脑默认浏览器打开网址
 * @param {string} myurl 网址
 */
var openURL = (myurl) => {

    let clearurl = `${CppUrl}?action=setWebReadyPage`
    getHtml(clearurl)

    myurl = encodeURIComponent(myurl)
    let url = `${CppUrl}?action=openURL&url=${myurl}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay + 1000);
}
exports.openURL = openURL
exports.打开网址 = openURL


/**
 * 打开文件(用默认软件)或者 用资源管理器打开展示文件夹,
 * @param {string} filePath 文件夹绝对路径  如:'c:/input/RPAlogo128.png'  Windows磁盘路径分隔符要双 '/'
 */
var openDir = (filePath) => {
    filePath = path.resolve(filePath)
    filePath = encodeURIComponent(filePath)
    let url = `${CppUrl}?action=openDir&path=${filePath}`
    // console.log(url)
    let res = getHtml(url)
    sleep(defaultDelay);
}
exports.openDir = openDir
exports.openfile = openDir
exports.打开目录 = openDir
exports.打开文件 = openDir



/**
 * 获取当前屏幕分辨率和缩放 
 * @returns {{w:number,h:number,ratio:number}} JSON内容格式 {w:1920,h:1080,ratio:1.5} ratio 为桌面缩放比例
 */
var getResolution = () => {
    let url = `${CppUrl}?action=getResolution`
    // console.log(url)
    let res = getHtml(url)
    return JSON.parse(res);
}
exports.getResolution = getResolution
exports.获取屏幕分辨率 = getResolution


/**
 * 文字识别 OCR已经从经典算法升级为AI模型预测,永久免费可脱网使用
 * 
 * @param {string} imagePath 空或者screen 为电脑屏幕;  或者本地图片的绝对路径;
 * @param {number} x 可选 查找起始点
 * @param {number} y 可选 查找起始点
 * @param {number} width  可选 宽度范围
 * @param {number} height 可选 高度范围
 * @returns {{text:string,score:number,x:number,y:number}}  AI OCR识别的json结果 包含准确率的评分和中点位置   格式: [{text:'A',score:'0.319415',x:100,y:200},...]  xy相对于原点
 */
var aiOcr = (imagePath = "screen", x = 0, y = 0, width = -1, height = -1) => {

    if (!imagePath) {
        imagePath = "screen"
    }

    if (x < 0 || y < 0) {
        throw new Error(`错误:OCR 起始点不能为负,x:${x} y:${y} `);
    }

    if (x != 0 || y != 0 || width != -1 || height != -1) {
        showRect(x, y, width, height);
    }

    if (imagePath != 'screen') {
        // use absolute path for local image files
        imagePath = path.resolve(imagePath)
        imagePath = encodeURIComponent(imagePath)
    }

    let url = `${CppUrl}?action=aiOcr&path=${imagePath}&x=${x}&y=${y}&width=${width}&height=${height}&onlyEn=0`
    // console.log(url)
    let res = getHtml(url)

    if (res == '文字识别引擎未启动') {
        console.log('⚠', res, '请在软件设置中开启');
        exit()
    }

    let jsons = JSON.parse(res);
    for (const json of jsons) {
        json.x += x
        json.y += y
    }
    return jsons;
}
exports.aiOcr = aiOcr
exports.文字识别 = aiOcr


/**
 * AI 物体识别 已经从经典算法升级为AI模型预测,企业版可脱网使用  V2024.8 以上版本有效
 * 调试:软件根目录会生成 debug/Ai_ObjectDetect.png 文件
 * 
 * @param {number} minimumScore 相似度阈值
 * @param {number} x 可选 查找范围
 * @param {number} y 可选 查找范围
 * @param {number} width  可选 查找宽度
 * @param {number} height 可选 查找高度
 * @returns {array}  AI 物体识别的 json 结果 包含准确率的评分    格式: [{x:100,y:100,width:150,height:150,score:0.86,class:'分类名'},...]  xy相对于原点
 */
var aiObject = (minimumScore = 0.5, x = 0, y = 0, width = -1, height = -1) => {

    if (x < 0 || y < 0) {
        throw new Error(`错误:OCR 起始点不能为负,x:${x} y:${y} `);
    }

    if (x != 0 || y != 0 || width != -1 || height != -1) {
        showRect(x, y, width, height);
    }

    let url = `${CppUrl}?action=aiObject&minimumScore=${minimumScore}&x=${x}&y=${y}&width=${width}&height=${height}&onlyEn=0`
    // console.log(url)
    let res = getHtml(url)

    if (res == '物体识别引擎未启动') {
        console.log('⚠', res, '请在软件设置中开启');
        exit()
    }

    let jsons = JSON.parse(res);
    for (const json of jsons) {
        json.x += x
        json.y += y
        showRect(json.x, json.y, json.width, json.height, 'green');
    }
    return jsons;
}
exports.aiObject = aiObject
exports.物体识别 = aiObject


/**
 * 压缩文件夹内容成一个zip文件包    v2025.0 以后版本生效
 * @param {string} directory 文件夹路径,输入绝对路径
 * @param {string} zipFilePath zip文件包
 */
function zipDir(directory, zipFilePath = "") {
    if (!zipFilePath) {
        zipFilePath = path.resolve(directory, 'RPA生成的压缩包.zip')
    }
    try {
        zipFilePath = path.resolve(zipFilePath)
        directory = path.resolve(directory)
        let exe = path.resolve(`${basePath}/bin/7za`)
        const os = process.platform;
        if (os === 'linux') {
            exe = '7za'
        }
        childProcess.execSync(`"${exe}" a "${zipFilePath}" "${directory}"`, { stdio: ['ignore', 'ignore', 'pipe'], encoding: 'utf8' })
    } catch (error) {
        if (!error.stderr.includes('Headers Error')) {  //warning
            console.error(`压缩失败`, error);
        }
    }
}
exports.zipDir = zipDir
exports.压缩 = zipDir


/**
 * 解压缩zip文件内容到指定文件夹内    v2025.0 以后版本生效
 * @param {string} zipFilePath zip文件包
 * @param {string} directory 文件夹路径,输入绝对路径  默认解压到zip文件当前目录
 */
function unZip(zipFilePath, directory = "") {
    if (!directory) {
        directory = path.dirname(zipFilePath)
    }
    try {
        let filePath = path.resolve(zipFilePath)
        directory = path.resolve(directory)
        let exe = path.resolve(`${basePath}/bin/7za`)
        const os = process.platform;
        if (os === 'linux') {
            exe = '7za'
        }
        childProcess.execSync(`"${exe}" x "${filePath}" -o"${directory}" -aoa`, { stdio: ['ignore', 'ignore', 'pipe'], encoding: 'utf8' })
    } catch (error) {
        console.error(`解压缩失败`, error);
    }
}
exports.unZip = unZip
exports.解压缩 = unZip

/**
 * 获取buffer存储内容
 * 此buffer可以跨脚本存取,RPA重启时才重置,存取多线程下安全
 * http外部获取方式:http://ip:49888/action=bufferGet&n=0 
 * @param {number} n buffer编号,从0-9共10个  默认:0 第一个buffer
 * @returns  {string} 返回字符串
 */
var bufferGet = (n = 0) => {
    let url = `${CppUrl}?action=bufferGet&n=${n}`
    let res = getHtml(url)
    return res;
}
exports.bufferGet = bufferGet


/**
 * 设置buffer存储内容
 * 此buffer可以跨脚本存取,RPA重启时才重置,存取多线程下安全
 * http外部设置方式(POST方法):http://ip:49888/action=bufferSet&n=0 ,content设置到Post的body中
 * @param {string} content 存储的内容,通常为一个json,也可以字符串
 * @param {number} n buffer编号,从0-9共10个  默认:0 第一个buffer
 * @returns {string} ok 表示成功
 */
var bufferSet = (content, n = 0) => {
    let url = `${CppUrl}?action=bufferSet&n=${n}`
    let res = postJson(url, content);
    return res;

}
exports.bufferSet = bufferSet


/**
 * 设置接力执行的脚本
 * 当前脚本结束后(无论正常结束还是错误退出),立刻启动的自动脚本。
 * http外部设置方式(GET方法):http://ip:49888/action=pbottleRPA_delay&path=MyPATH
 * @param {string} scriptPath 接力脚本的路径 如:'D:/test.mjs'    如果路径为空,默认清除当前已经设置的接力任务。
 * @returns {string} ok 表示成功
 */
var delaySet = (scriptPath = '') => {
    scriptPath = path.resolve(scriptPath)
    scriptPath = encodeURIComponent(scriptPath)
    let url = `${CppUrl}?action=pbottleRPA_delay&path=${scriptPath}`
    let res = getHtml(url)
    return res
}
exports.delaySet = delaySet


/**
 * 获取当前的设备唯一号
 * @returns {string} 返回字符串
 */
function deviceID() {
    let url = `${CppUrl}?action=pbottleRPA_deviceID`
    let res = getHtml(url)
    return res
}
exports.deviceID = deviceID



/**
 * 获取
 * @returns {string} 返回字符串
 */
function clusterCenter() {
    let url = `${CppUrl}?action=pbottleRPA_clusterCenter`
    let res = getHtml(url)
    return res
}
exports.clusterCenter = clusterCenter



/**
 *  小瓶RPA 云端模块,AI在线大模型
 *  注意:
 *  ①此模块不是必须模块 ,云端模块不影响本地模块的独立运行
 *  ②此模块功能需要登录并激活云端模块。碍于成本因素,部分功能需要充值计费才能使用
 */
exports.cloud = {}

/**
 * @typedef {Object} Answerinfo  AI回答结果
 * @property {string} content - 回答结果
 * @property {number} usage - 消耗token数量
 */
/**
 * @typedef {Object} AiOptions  AI输入选项
 * @property {string} response_format 云端模型输出格式,默认:"text",可选 "json_object" JSON格式
 * @property {number} temperature  模型温度,默认:0.75,取值范围 [0-2).
 * @property {boolean} enable_search   false|true  联网搜素开关,默认关闭,开启增加token消耗。开启后,只会根据问题自动判断是否联网,可以在问题中添加联网搜素关键词,如:"联网搜素:xxxx"
 */
/**
 * 小瓶RPA整合的云端大语言答案生成模型
 * @param {string} question 提问问题,如:'今天是xx日,你能给我写首诗吗?'
 * @param {number} modelLevel 模型等级,不同参数大小不同定价,默认 0 为标准模型。0为低价模型;1为性价比模型;2为旗舰高智能模型;
 * @param {AiOptions} options AI输入选项
 * @returns {Answerinfo} JSON内容格式 {content:'结果',tokens:消耗token的数量}
 */
function cloud_GPT(question, modelLevel = 0, options = {
    response_format: 'text',
    temperature: 0.75,
    enable_search: false,
}) {
    let deviceToken = deviceID()
    if (question.length < 3) {
        throw new Error('❌ 错误:问题过短,请输入至少2个字符')
    }
    let rs = postJson('https://rpa.pbottle.com/API/', { question, deviceToken, modelLevel, options })
    // console.log(rs);
    let json = JSON.parse(rs)
    if (json.error) {
        throw new Error('❌ 错误:' + json.error)
    }
    return json
}
exports.cloud_GPT = cloud_GPT
exports.cloud.GPT = cloud_GPT


/**
 * 小瓶RPA整合的云端图像分析大模型
 * @param {string} question 提问问题,如:'分析这个图片的内容'
 * @param {string} imagePath 上传图片的路径,如:'c:/test.jpg'
 * @param {number} modelLevel 模型等级,不同参数大小不同定价,默认 0 为标准模型。
 * @returns {Answerinfo} JSON内容格式 {content:'结果',tokens:消耗token的数量}
 */
function cloud_GPTV(question, imagePath, modelLevel = 0) {
    let deviceToken = deviceID()
    imagePath = path.resolve(imagePath)

    if (!fs.existsSync(imagePath)) {
        throw new Error('❌输入分析图片不存在:cloud_GPTV')
    }

    let tempJsonFile = homePath + '/cloud_GPTV.json'

    let image_base64 = fs.readFileSync(imagePath).toString('base64')
    fs.writeFileSync(tempJsonFile, JSON.stringify({ question, deviceToken, modelLevel, image_base64 }))

    let rs = postJsonFile('https://rpa.pbottle.com/API/gptv', tempJsonFile);
    let json = JSON.parse(rs)
    if (json.error) {
        console.log('❌ 错误 cloud_GPTV', json.error, rs)
        throw new Error(json.error)
    }
    return json
}
exports.cloud_GPTV = cloud_GPTV
exports.cloud.GPTV = cloud_GPTV


/**
 * 小瓶RPA整合的云端图像分析大模型,直接操作屏幕
 * @param {string} action  '点击'|'双击'|'右键'
 * @param {string} question 提问问题,如:'分析这个图片的内容'
 * @returns
 */
function cloud_GPTA(action = '点击', question = "桌面微信图标") {
    let deviceToken = deviceID()

    let tempScreenShoot = homePath + '/cloud_GPT_do.png'
    let tempJsonFile = homePath + '/cloud_GPTV.json'

    screenShot(tempScreenShoot)

    let image_base64 = 'data:image/png;base64,' + fs.readFileSync(tempScreenShoot).toString('base64')

    fs.writeFileSync(tempJsonFile, JSON.stringify({ question, deviceToken, image_base64 }))

    let rs = postJsonFile('https://rpa.pbottle.com/API/gpta', tempJsonFile);
    let json = JSON.parse(rs)
    if (json.error) {
        console.log('❌ 错误 cloud_GPTA', json.error, rs)
        throw new Error(json.error)
    }
    console.log(json);
    let boxs = json.content.split('\n')
    for (let index = 0; index < boxs.length; index++) {
        const box = boxs[index];
        if (!box) {
            continue
        }
        let box4 = JSON.parse(box)
        let resolution = getResolution()
        box4[0] = box4[0] / 1000 * resolution.w
        box4[1] = box4[1] / 1000 * resolution.h
        box4[2] = box4[2] / 1000 * resolution.w
        box4[3] = box4[3] / 1000 * resolution.h

        showRect(box4[0], box4[1], box4[2] - box4[0], box4[3] - box4[1], 'green')


        let x = Math.round((box4[0] + box4[2]) / 2)
        let y = Math.round((box4[1] + box4[3]) / 2)
        console.log(question + '的位置', x, y);
        moveMouseSmooth(x, y)

        if (action == '点击') {
            mouseClick('left')
        } else if (action == '双击') {
            mouseDoubleClick()
        } else if (action == '右键') {
            mouseClick('right')
        }

    }


}
exports.cloud_GPTA = cloud_GPTA
exports.cloud.GPTA = cloud_GPTA


/**
 *  小瓶RPA 浏览器增强命令
 *  注意:
 *  ①此模块不是必须模块 
 *  ②此模块功能需要安装小瓶RPA浏览器增强插件:https://rpa.pbottle.com/a-13942.html
 */
exports.browserCMD = {}

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 警告框
 * @param {string} msg 显示文本内容
 * @returns {string} 正常返回 'ok'
 */
var browserCMD_alert = function (msg) {
    let action = 'alert';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_alert = browserCMD_alert;
exports.browserCMD.alert = browserCMD_alert


/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 关闭浏览器标签页。打开新标签页用 pbottleRPA.openURL()
 * @param {string} 关闭类型  'current':默认关闭当前标签页; 'other':关闭其他标签页
 * @returns {string} 正常返回 'ok'
 */
var browserCMD_closeTab = function (type = 'current') {
    let action = 'closeTab';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_closeTab = browserCMD_closeTab
exports.browserCMD.closeTab = browserCMD_closeTab


/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * fetch请求网址,从当前页面发起ajax请求并返回响应结果  https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
 * 默认 20 秒超时
 * @param {string} fetch_url 网址
 * @param {object} options 请求参数
 * @returns {string} 响应结果
 */
var browserCMD_fetch = function (fetch_url, options = {}) {
    let action = 'fetch';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_fetch = browserCMD_fetch
exports.browserCMD.fetch = browserCMD_fetch

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 等待页面加载完成,返回页面网址
 * 默认 20 秒超时
 * @param {string} readyURL  页面加载完成后的网址
 * @param {number} timeout 超时时间,单位秒
 * @returns {string}  返回当前浏览器的url网址 或者错误退出
 */
var browserCMD_waitPageReady = function (readyURL,timeout = 20) {

    let url = `${CppUrl}?action=getWebReadyPage`
    for (let index = 0; index < timeout; index++) {
        let res = getHtml(url)
        // console.log("结果:",res);
        if (res == readyURL) {
            return res
        }else{
            sleep(1000);
            console.log(`等待页面加载完成...`);
        }
    }
    throw new Error('waitPageReady 等待页面加载超时')
}
exports.browserCMD_waitPageReady = browserCMD_waitPageReady
exports.browserCMD.waitPageReady = browserCMD_waitPageReady

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * @param {string} urlStr 当前网页转向新网址,默认为空获取当前网址   【小瓶RPA浏览器增强插件V2023.8以上生效】
 * @returns {string}  返回当前浏览器的url网址 或者 ok
 */
var browserCMD_url = function (urlStr = undefined) {
    let action = 'url';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_url = browserCMD_url;
exports.browserCMD.url = browserCMD_url


/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 元素数量   参考 jQuery 选择器 
 * @param {string} selector   元素选择器
 * @returns {number}  返回选择元素的数量,最优的选择结果是1
 */
var browserCMD_count = function (selector) {
    let action = 'count';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    let resStr = res
    if (isNumeric(resStr)) {
        return parseInt(resStr)
    } else {
        return 0
    }
}
exports.browserCMD_count = browserCMD_count;
exports.browserCMD.count = browserCMD_count


/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 模拟点击   参考 jQuery click() 方法,改为浏览器 native 的 click() 并自动获取焦点
 * @param {string} selector   元素选择器。如果选择多个元素,只触发第一个元素的click事件
 * @param {object} options 点击选项  可选  如:{ bubbles: false, ctrlKey: true} https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/MouseEvent
 * @returns {string}
 */
var browserCMD_click = function (selector) {

    let action = 'click';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_click = browserCMD_click;
exports.browserCMD.click = browserCMD_click;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 模拟双击   参考 jQuery dblclick() 方法,改为浏览器 native 的 click() 并自动获取焦点
 * @param {string} selector   元素选择器。如果选择多个元素,只触发第一个元素的click事件
 * @param {object} options 点击选项  可选  如:{ bubbles: false,  ctrlKey: true} https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/MouseEvent
 * @returns {string}
 */
var browserCMD_dblclick = function (key) {

    let action = 'dblclick';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_dblclick = browserCMD_dblclick;
exports.browserCMD.dblclick = browserCMD_dblclick;


/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 显示元素   参考 jQuery show() 方法 
 * @param {string} selector   元素选择器
 * @returns {string}
 */
var browserCMD_show = function (selector) {

    let action = 'show';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_show = browserCMD_show;
exports.browserCMD.show = browserCMD_show;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 隐藏元素   参考 jQuery hide() 方法 
 * @param {string} selector   元素选择器
 * @returns {string}
 */
var browserCMD_hide = function (selector) {

    let action = 'hide';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_hide = browserCMD_hide;
exports.browserCMD.hide = browserCMD_hide;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展   2024.0 以上版本生效
 * 获取元素定位,相对浏览器文档左上角   参考 jQuery offset() 方法 
 * @param {string} selector   元素选择器
 * @returns {{left:number,top:number}}  返回 json:{"left":100,"top":100} 位置位元素的左上角顶点坐标
 */
var browserCMD_offset = function (selector) {

    let action = 'offset';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return JSON.parse(res)
}
exports.browserCMD_offset = browserCMD_offset;
exports.browserCMD.offset = browserCMD_offset;


/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 移除元素   参考 jQuery remove() 方法 
 * @param {string} selector   元素选择器
 * @returns {string}
 */
var browserCMD_remove = function (selector) {

    let action = 'remove';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_remove = browserCMD_remove;
exports.browserCMD.remove = browserCMD_remove;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或者设置文本   参考 jQuery text() 方法
 * @param {string} selector  元素选择器
 * @param {string} content 可选
 * @returns {string} 选择多个元素时会返回一个数组
 */
var browserCMD_text = function (selector, content = undefined) {

    let action = 'text';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res

}
exports.browserCMD_text = browserCMD_text;
exports.browserCMD.text = browserCMD_text;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或者设置html   参考 jQuery html() 方法
 * @param {string} selector  元素选择器
 * @param {string} content  可选
 * @returns {string} 选择多个元素时会返回一个数组
 */
var browserCMD_html = function (selector, content = undefined) {

    let action = 'html';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res

}
exports.browserCMD_html = browserCMD_html;
exports.browserCMD.html = browserCMD_html;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或设置值 input select等   参考 jQuery val() 方法
 * @param {string} selector  元素选择器
 * @param {string} content  可选,值
 * @returns {string} 选择多个元素时会返回一个数组
 */
var browserCMD_val = function (selector, content = undefined) {

    let action = 'val';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res

}
exports.browserCMD_val = browserCMD_val;
exports.browserCMD.val = browserCMD_val;

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或设置当前站点的 cookie
 * @param {string} cName  cookie 名称 
 * @param {string} cValue cookie 值  留空为获取cookie的值
 * @param {number} expDays cookie 过期时间,单位:天, 留空为会话cookie
 * @returns {string} 返回 cookie的值
 */
var browserCMD_cookie = function (cName, cValue = undefined, expDays = undefined) {

    let action = 'cookie';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res
}
exports.browserCMD_cookie = browserCMD_cookie;
exports.browserCMD.cookie = browserCMD_cookie

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或设置css样式   参考 jQuery css() 方法
 * @param {string} selector  元素选择器
 * @param {string} propertyname 名
 * @param {string} value 值
 * @returns 
 */
var browserCMD_css = function (selector, propertyname, value = undefined) {

    let action = 'css';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res

}
exports.browserCMD_css = browserCMD_css;
exports.browserCMD.css = browserCMD_css

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或设置attr属性   参考 jQuery attr() 方法
 * @param {string} selector 元素选择器
 * @param {string} propertyname 属性名
 * @param {string} value 值
 * @returns {string}
 */
var browserCMD_attr = function (selector, propertyname, value = undefined) {

    let action = 'attr';

    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res

}
exports.browserCMD_attr = browserCMD_attr;
exports.browserCMD.attr = browserCMD_attr

/**
 * 浏览器增强命令  需要安装小瓶RPA的浏览器拓展
 * 获取或设置prop属性   参考 jQuery prop() 方法
 * @param {string} selector 元素选择器
 * @param {string} propertyname 属性名
 * @param {string} value 值
 * @returns {string}
 */
var browserCMD_prop = function (selector, propertyname, value = undefined) {

    let action = 'prop';
    let [...args] = arguments;
    let url = `${CppUrl}?action=webInject&jscode=` + encodeURIComponent(JSON.stringify({ action, args }))
    let res = getHtml(url)
    return res

}
exports.browserCMD_prop = browserCMD_prop;
exports.browserCMD.prop = browserCMD_prop


/**
 * 等待屏幕上的图片出现
 * @param {string|Array} tpPath 图片模板路径 相对路径:./image/123.png  | 数组等待多个图片
 * @param {Function} intervalFun 检测间隔的操作,function格式
 * @param {number} timeOut 可选,等待超时时间 单位秒 默认30秒
 * @param {number} miniSimilarity  可选,指定最低相似度,默认0.85。取值0-1,1为找到完全相同的。
 * @returns {position|boolean} 结果的位置信息,json格式:{x,y}  相对于屏幕左上角原点
 */
function waitImage(tpPath, intervalFun = () => { }, timeOut = 30, miniSimilarity = 0.85) {
    console.log('waitImage', tpPath);
    for (let index = 0; index < timeOut; index++) {
        sleep(1000)
        let position = findScreen(tpPath, miniSimilarity)
        if (position !== false) {
            return position;
        }
        if (typeof intervalFun === 'function' && intervalFun() == 'stopWait') {
            console.log('stopWait from intervalFun');
            return false
        }
    }
    //debug 保存当前屏幕
    console.log('已经保存超时截图到:我的图片');
    screenShot();
    //error
    throw new Error(`waitImage 等待图片超时 ${tpPath}`)
}
exports.waitImage = waitImage;
exports.等待图像出现 = waitImage;

/**
 * 等待屏幕上的图片消失
 * @param {string} tpPath 图片模板路径  相对路径:./image/123.png
 * @param {function} intervalFun 检测间隔的操作,function格式
 * @param {number} timeOut 可选,等待超时时间 单位秒 默认30秒
 * @param {number} miniSimilarity  可选,指定最低相似度,默认0.85。取值0-1,1为找到完全相同的。
 * @returns  {string|boolean}
 */
function waitImageDisappear(tpPath, intervalFun = () => { }, timeOut = 30, miniSimilarity = 0.85) {
    console.log('waitImageDisappear', tpPath);
    for (let index = 0; index < timeOut; index++) {
        sleep(1000)
        let position = findScreen(tpPath, miniSimilarity)
        if (position === false) {
            return 'ok';
        }
        if (typeof intervalFun === 'function' && intervalFun() == 'stopWait') {
            console.log('stopWait from intervalFun');
            return false
        }
    }
    //debug 保存当前屏幕
    console.log('已经保存超时截图到:我的图片');
    screenShot();
    //error
    throw new Error(`waitImageDisappear 等待图片消失超时 ${tpPath}`)
}
exports.waitImageDisappear = waitImageDisappear;
exports.等待图像消失 = waitImageDisappear;

/**
 * 等待文件下载成功或者生成
 * @param {string} dirPath 监控文件夹目录  如:'c:/User/pbottle/download'
 * @param {string} keyWords 过滤关键词  如:'.zip'
 * @param {function} intervalFun 检测间隔的操作,function格式
 * @param {number} timeOut 等待超时时间 单位秒
 * @returns  {string[]}
 */
function waitFile(dirPath, keyWords = '', intervalFun = () => { }, timeOut = 30) {
    console.log('waitFile', dirPath, keyWords);
    for (let index = 0; index < timeOut; index++) {
        sleep(1000)
        let rs = searchFile(dirPath, keyWords)
        if (hasData(rs)) {
            return rs;
        }
        if (typeof intervalFun === 'function' && intervalFun() == 'stopWait') {
            console.log('stopWait from intervalFun');
            return false
        }
    }
    //error
    throw new Error(`waitFile 等待文件超时: ${dirPath}`)
}
exports.waitFile = waitFile;
exports.等待文件 = waitFile;


/**
 * 等待文件消失或者被删除
 * @param {string} dirPath 监控文件夹目录  如:'c:/User/pbottle/download'
 * @param {string} keyWords 过滤关键词  如:'.crdownload'
 * @param {function} intervalFun 检测间隔的操作,function格式
 * @param {number} timeOut 等待超时时间 单位秒
 * @returns  {string[]}
 */
function waitFileDisappear(dirPath, keyWords = '', intervalFun = () => { }, timeOut = 30) {
    console.log('waitFileDisappear', dirPath, keyWords);
    for (let index = 0; index < timeOut; index++) {
        sleep(1000)
        let rs = searchFile(dirPath, keyWords)
        if (!hasData(rs)) {
            return true;
        }
        if (typeof intervalFun === 'function' && intervalFun() == 'stopWait') {
            console.log('stopWait from intervalFun');
            return false
        }
    }
    //error
    let frame = new Error().stack.split("\n")[2]; // change to 3 for grandparent func
    throw new Error(`waitFileDisappear 等待文件消失错误: ${dirPath} ${frame}`)
}
exports.waitFileDisappear = waitFileDisappear;
exports.等待文件消失 = waitFileDisappear;



/**
 * 等待输入 V2026.0.0 新增
 * @param {string} inputPrompt 输入提示词
 * @param {number} timeOut 可选,等待超时时间 单位秒 默认600秒
 * @returns {string}  输入内容  默认返回空字符串
 */
function waitInput(inputPrompt = '输入提示词', timeOut = 600) {
    console.log('waitInput 等待用户输入:', inputPrompt);
    inputPrompt = encodeURIComponent(inputPrompt)
    let url = `${CppUrl}?action=waitInput&inputPrompt=${inputPrompt}`
    let res = getHtml(url)
    for (let index = 0; index < timeOut; index++) {
        sleep(1000)
        let rs = getHtml(`${CppUrl}?action=waitInputResult`)
        if (hasData(rs)) {
            showMsg('用户输入了:', rs)
            return rs;
        } else {
            continue;
        }
    }
}
exports.waitInput = waitInput;
exports.等待输入 = waitInput;

/**
 *  小瓶RPA 硬件键鼠模拟接口
 *  注意:
 *  ①此模块不是必须模块 
 *  ②此模块功能需要添加电脑硬件外设,购买装配请咨询小瓶RPA客服
 */
exports.hid = {}
/**
 * 模拟按键触发事件 (硬件级)
 * @param {string} key  按键名称参考:https://www.pbottle.com/a-13862.html
 * @param {string} upDown  默认按下down,up松开按键
 * @returns 
 */
let hid_keyToggle = (key, upDown) => {
    let upDown_n = 0;
    if (upDown == 'up') {
        upDown_n = 2;
    }
    let key_n = keycode(key)
    if (key_n === undefined) {
        console.log(`⚠ 按键 ${key} 不存在!~`);
        return
    }
    let url = `${CppUrl}?action=keyToggleHardWare&key_n=${key_n}&upDown_n=${upDown_n}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}
exports.hid.keyToggle = hid_keyToggle

/**
 * 按一下键盘(硬件级)   支持组合按键 加号连接 如:  keyTap('ctrl + alt + del')
 * @param {string} key  按键名称参考:https://www.pbottle.com/a-13862.html
 */
let hid_keyTap = (key) => {
    if (key.includes('+')) {
        let subkeys = new Array();
        subkeys = key.split('+')
        subkeys = subkeys.map((value) => {
            return value.trim()
        })
        for (let index = 0; index < subkeys.length; index++) {
            const element = subkeys[index];
            hid_keyToggle(element, "down")
        }

        subkeys = subkeys.reverse()
        for (let index = 0; index < subkeys.length; index++) {
            const element = subkeys[index];
            hid_keyToggle(element, "up")
        }

    } else {
        hid_keyToggle(key, "down")
        hid_keyToggle(key, "up")
    }
    sleep(defaultDelay);
}
exports.hid.keyTap = hid_keyTap


/**
 * 基础鼠标命令  全部为零释放
 * @param {number} button 按键  1,2,4 代表鼠标的 左键,右键,中键。
 * @param {number} x 按键时候移动的位置,绝对位置  x=100:向右移动 100像素,负数向左
 * @param {number} y 按键时候移动的位置,拖拽相对位置  y=100:向下移动 100像素,负数向上
 * @param {number} mouseWheel 滚动齿轮数  正数向下,负数向下
 * @param {number} time 按下到释放时间
 * @returns 
 */
let hid_mouseCMD = (button = 1, x = 0, y = 0, mouseWheel = 0, time = 10) => {
    let url = `${CppUrl}?action=mouseDataHardWare&bt=${button}&x=${x}&y=${y}&wheel=${mouseWheel}&time=${time}`
    // console.log(url)
    let res = getHtml(url)
    return res;
}

/**
 * 移动鼠标到指定位置  起点为屏幕左上角  屏幕绝对位置(硬件分辨率)
 * @param {number} x   横坐标
 * @param {number} y   纵坐标
 * @returns 
 */
let hid_moveMouse = (x, y) => {
    hid_mouseCMD(0, x, y, 0, 10)
}
exports.hid.moveMouse = hid_moveMouse


/**
 * 当前位置点击鼠标 默认左键  
 * @param {string} 鼠标的按键选择 left right middle 可选  ,默认左键
 * @param {number} 点按时间 单位毫秒 可选
 * @returns 
 */
let hid_mouseClick = (button = 'left', time = 10) => {
    let bt = 1
    switch (button) {
        case 'right':
            bt = 2
            break;
        case 'middle':
            bt = 4
            break
        default:
            bt = 1
            break;
    }
    hid_mouseCMD(bt, 0, 0, 0, time)
    hid_mouseCMD(0, 0, 0, 0, 0)
    sleep(defaultDelay);
}
exports.hid.mouseClick = hid_mouseClick


/**
 * 移动鼠标到指定位置并点击
 * @param {number} x 横坐标
 * @param {number} y 纵坐标
 */
let hid_moveAndClick = (x, y) => {
    hid_moveMouse(x, y)
    hid_mouseClick()
}
exports.hid.moveAndClick = hid_moveAndClick

/**
 * 双击鼠标  左键
 * @returns 
 */
let hid_mouseDoubleClick = () => {
    hid_mouseCMD(1, 0, 0, 0, 10)
    hid_mouseCMD(0, 0, 0, 0, 0)
    hid_mouseCMD(1, 0, 0, 0, 10)
    hid_mouseCMD(0, 0, 0, 0, 0)
    sleep(defaultDelay);
}
exports.hid.mouseDoubleClick = hid_mouseDoubleClick

/**
 * 鼠标左键拖到一段位置
 * @param {number} x  位置
 * @param {number} y  位置
 * @returns 
 */
let hid_mouseLeftDragTo = (x, y) => {
    hid_mouseCMD(1, 0, 0, 0, 10)
    hid_mouseCMD(1, x, y, 0, 10)
    hid_mouseCMD(0, 0, 0, 0, 0)
    sleep(defaultDelay);
}
exports.hid.mouseLeftDragTo = hid_mouseLeftDragTo

/**
 * 鼠标左键拖到一段位置
 * @param {number} x  位置
 * @param {number} y  位置
 * @returns 
 */
let hid_mouseRightDragTo = (x, y) => {
    // use hid_mouseCMD (hardware mouse command) instead of undefined mouseCMD
    hid_mouseCMD(2, 0, 0, 0, 10)
    hid_mouseCMD(2, x, y, 0, 10)
    hid_mouseCMD(0, 0, 0, 0, 0)
    sleep(defaultDelay);
}
exports.hid.mouseRightDragTo = hid_mouseRightDragTo


/**
 * 鼠标滚轮
 * @param {number} data 滚动的量  默认为-1   向下滚动一个齿轮;  正数向上滚动;
 * @returns 
 */
let hid_mouseWheel = (data = -1) => {
    hid_mouseCMD(0, 0, 0, data, 0)
    hid_mouseCMD(0, 0, 0, 0, 0)
    sleep(defaultDelay);
}
exports.hid.mouseWheel = hid_mouseWheel


/**
 * 公共工具类,一般和模拟操作没有直接关系的方法。  用法:pbottleRPA.utils.function(parameters) or pbottleRPA.function(parameters)
 * 持续添加常用工具,为流程提供快捷方法。
 */
exports.utils = {}
exports.工具箱 = {}

/**
 * 常用工具
 * 判断是否为数字化变量(包含数字化的字符串)

 * @param {*} value 任意类型变量
 * @returns {boolean}
 */
function isNumeric(value) {
    return !isNaN(parseFloat(value)) && isFinite(value);
}
exports.isNumeric = isNumeric;
exports.是否数字 = isNumeric;
exports.utils.isNumeric = isNumeric;
exports.工具箱.是否数字 = isNumeric;

/**
 * 常用工具
 * 判断变量中是否有数据,直接if()可用。
 * 非零数字 或 非空字符串、数组、对象 返回 true,其他都返回 false
 * @param {*} value 任意类型变量
 * @returns {boolean}
 */
function hasData(value) {
    // console.log(value);
    if (value === null || value === undefined) {
        return false;
    }
    if (typeof value === 'string' && value.trim().length === 0) {
        return false;
    }
    if (Array.isArray(value) && value.length === 0) {
        return false;
    }
    if (typeof value === 'number' && (value === 0 || isNaN(value))) {
        return false;
    }
    if (typeof value === 'bigint' && value === 0n) {
        return false;
    }
    if (typeof value === 'boolean') {
        return value;
    }
    if (typeof value === 'symbol' || typeof value === 'function') {
        return false;
    }
    if (typeof value === 'object' && Object.keys(value).length === 0) {
        return false;
    }
    return true;
}
exports.hasData = hasData;
exports.是否有内容 = hasData;
exports.utils.hasData = hasData;
exports.工具箱.是否有内容 = hasData;

/**
 * 常用工具
 * 格式化的时间  getTime('Y-m-d H:i:s') 输出类似 "2023-09-17 14:30:45" 的日期时间字符串
 * @param {string} format 格式参考 https://www.runoob.com/php/php-date.html  仅支持 Y|y|m|d|H|i|s|n|j
 * @param {number} timestamp 时间戳秒
 * @returns {string}
 */
function getTime(format = 'Y-m-d H:i:s', timestamp = null) {

    // 如果没有提供 timestamp,使用当前时间  
    const date = timestamp ? new Date(timestamp * 1000) : new Date();

    // 映射 PHP 的日期格式到 JavaScript 的日期方法  
    const formatMap = {
        'Y': date.getFullYear(),         // 4位数的年份  
        'y': (date.getFullYear() % 100).toString().padStart(2, '0').slice(-2), // 2位数的年份 
        'm': ('0' + (date.getMonth() + 1)).slice(-2), // 月份,01-12  
        'd': ('0' + date.getDate()).slice(-2),        // 日期,01-31  
        'H': ('0' + date.getHours()).slice(-2),       // 24小时制的小时,00-23  
        'i': ('0' + date.getMinutes()).slice(-2),     // 分钟,00-59  
        's': ('0' + date.getSeconds()).slice(-2),     // 秒,00-59  
        'n': date.getMonth() + 1,           // 月份,1-12,没有前导零  
        'j': date.getDate(),                // 日期,1-31,没有前导零
    };
    // 替换格式字符串中的占位符  
    return format.replace(/Y|y|m|d|H|i|s|n|j/g, (matched) => formatMap[matched]);
}
exports.getTime = getTime;
exports.获取格式化时间 = getTime;
exports.utils.getTime = getTime;
exports.工具箱.获取格式化时间 = getTime;


/**
 * 常用工具
 * 根据关键字定位具体文件
 * @param {string} directory  目录绝对路径
 * @param {string} words  文件名包含的关键字,过滤词,默认忽略大小写
 * @param {boolean} recursive  是否递归深入目录子目录查找 ,默认false
 * @returns {string[]}  文件路径 || [] 未找到
 */
function searchFile(directory, words = '', recursive = false) {
    let rs = []  //全局结果
    // 读取目录内容
    directory = path.resolve(directory)
    let files = fs.readdirSync(directory)
    // console.log('files',files);
    // 遍历每个文件
    words = words.toLowerCase()
    files.forEach((file) => {
        let filePath = path.resolve(directory, file);
        if (recursive) {  //判断子目录
            let stats = fs.statSync(filePath);
            if (stats.isDirectory()) {
                rsTemp = searchFile(filePath, words, recursive)
                rs.push(...rsTemp)
            }
        }
        // console.log(filePath);
        if (filePath.toLowerCase().includes(words)) {
            rs.push(filePath)
        }
    });
    return rs;
}
exports.searchFile = searchFile;
exports.搜索文件 = searchFile;
exports.utils.searchFile = searchFile;
exports.工具箱.搜索文件 = searchFile;



/**
 * 常用工具
 * 生成唯一符串 注意:默认只是毫秒级的
 * @param {string} prefix 前缀
 * @param {boolean} moreEntropy  是否开启更精细的随机,如果还不能满足请使用uuid
 * @returns {string}
 */
function uniqid(prefix = '', moreEntropy = false) {
    let timestamp = Date.now().toString(36); // 将时间戳转换为36进制  
    let randomStr = '';
    if (moreEntropy) {
        // 如果需要更多的熵,则添加一些随机字符  
        randomStr = Math.random().toString(36).substring(2);
    }
    return prefix + timestamp + randomStr;
}
exports.uniqid = uniqid;
exports.唯一数 = uniqid;
exports.utils.uniqid = uniqid;
exports.工具箱.唯一数 = uniqid;



/**
 * 常用工具
 * 根据起始关键词,截取一部分字符串
 * @param {string} str 检索目标
 * @param {string} from 开始关键词 不包含本身  空表示从头部开始
 * @param {string} to 结束关键词  不包含本身   空表示到结尾结束
 * @returns  {string}
 */
function substringFromTo(str, from = '', to = '') {
    let fromIndex = str.indexOf(from) + from.length
    let toIndex = str.lastIndexOf(to)
    if (fromIndex == -1 || toIndex == -1) {
        console.log('⚠substringFromTo 没有关键词:', from, to);
        return ''
    }
    if (!from) {
        fromIndex = 0
    }
    if (!to) {
        toIndex = str.length
    }
    let rs = str.substring(fromIndex, toIndex);
    return rs
}
exports.substringFromTo = substringFromTo
exports.截取文本 = substringFromTo
exports.utils.substringFromTo = substringFromTo
exports.工具箱.截取文本 = substringFromTo




//检测入口文件
if (process.argv[1] === __filename) {
    console.log('当前文件不能执行', "请直接执行中文名的脚本文件");
    showMsg('当前文件不能执行', "请直接执行中文名的脚本文件");
    process.exit(1);
}

//检测 win10  以下系统 curl 命令是否存在
const isWindows = process.platform === 'win32';
let command;
if (isWindows) {
    command = 'where curl';
} else {
    command = 'which curl';
}

try {
    childProcess.execSync(command, { encoding: 'utf8' });
} catch (error) {
    console.log('⚠️ 系统 curl 命令不存在,使用集成 curl');
    curlCommand = basePath + '/bin/curl.exe';
    // process.exit(1);
}




================================================
FILE: python示例/GPT图像解析示例.py
================================================
"""
小瓶RPA演示demo,具体api请查看*流程开发文档*
官网:https://rpa.pbottle.com/
流程开发文档:https://rpa.pbottle.com/docs/

功能说明:此脚本演示了RPA中的GPT图像解析功能,可以向云端AI提问关于图片内容的问题
通过这个示例,您可以学习如何结合AI能力分析和理解图片内容
"""

import time
import pbottleRPA  # 引入小瓶RPA的核心库,获得对RPA功能的访问权限

# 开始RPA操作

ask = "描述图片中有什么?"  # 定义要向AI提出的问题
print(ask, "./input/RPAlogo128.png")  # 在控制台输出问题和要分析的图片路径

start = time.time()  # 记录开始时间,用于计算处理耗时

pbottleRPA.log("云端 AI 生成答案:")  # 将提示信息输出到日志文件中
# 调用云端GPT图像分析API,传入问题和图片路径,并输出结果内容
print(pbottleRPA.cloud.GPTV(ask, "./input/RPAlogo128.png")["content"])
print(
    "图片解析耗时:(毫秒)", (time.time() - start) * 1000
)  # 计算并输出图片解析耗时(毫秒)


================================================
FILE: python示例/GPT问题答案AI生成演示.py
================================================
"""

小瓶RPA python版本(Beta)
https://gitee.com/pbottle/pbottle-rpa
示例

"""

import pbottleRPA  #引入小瓶RPA模块

asks = [
    '鲁迅为什么要打周树人?',
    '给我随便作一首诗吧',
    '你是谁?',
]

for index,ask in enumerate(asks):
    
    print(f'问题 {index+1}:',ask);
    pbottleRPA.tts(ask)

    rs = pbottleRPA.cloud.GPT(ask)
    pbottleRPA.log('云端 AI 生成答案:',rs['content'])
    pbottleRPA.log('------------')
    pbottleRPA.log()


# end

================================================
FILE: python示例/WEB增强-数据批量爬取演示.py
================================================
"""

小瓶RPA python版本(Beta)
https://gitee.com/pbottle/pbottle-rpa
示例

"""

import pbottleRPA  #引入小瓶RPA模块
import time
import json


print("=== WEB增强插件-浏览器数据库批量爬取演示 ===")
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(current_time)

print("=== ※※※※※※※※※ ===");
print("=== 需要安装 小瓶RPA 浏览器插件 ===");
print("=== ※※※※※※※※※ ===");

pbottleRPA.showMsg('提示:','必须先安装浏览器增强插件')

#打开被获取数据的网页
pbottleRPA.sleep(5*1000)
pbottleRPA.openURL('https://rpa.pbottle.com/')
pbottleRPA.sleep(5*1000)
pbottleRPA.keyTap('page down')
pbottleRPA.keyTap('page down')
pbottleRPA.keyTap('page down')


#开始获取网页上的数据
rs = pbottleRPA.browserCMD.text('a.list-group-item')
if rs == '20s超时':
    pbottleRPA.showMsg('出现错误:','必须先安装浏览器增强插件和联网')
    pbottleRPA.exit()

datas =  json.loads(rs)

print('爬取数据数量:',len(datas))
pbottleRPA.tts('爬取数据'+ str(len(datas)) +'条,请查看日志')
pbottleRPA.sleep(1000*4)

print('数据列表:')
for element in datas:
    element = element.strip().replace('\r', '').replace('\n', '')
    print(element);


pbottleRPA.tts('演示结束')

================================================
FILE: python示例/WEB增强-浏览器元素操作演示.py
================================================
"""

小瓶RPA python版本(Beta)
https://gitee.com/pbottle/pbottle-rpa
示例

"""

import pbottleRPA  #引入小瓶RPA模块
import time


print("=== WEB增强插件-浏览器元素操作演示 ===")
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(current_time)

print("=== ※※※※※※※※※ ===")
print("=== 需要安装 小瓶RPA 浏览器插件 ===")
print("=== ※※※※※※※※※ ===")


pbottleRPA.tts('必须安装小瓶RPA浏览器增强插件,手动点击确定继续')
pbottleRPA.showMsg('提示:','必须先安装浏览器增强插件')
pbottleRPA.openURL('https://www.baidu.com')


ret = pbottleRPA.browserCMD.alert('来自小瓶RPA的问候,手动点击确定开始,20秒超时')
print('返回操作结果',ret)
if (ret != 'ok'):
    print('没有检测到小瓶RPA浏览器插件',ret)
    pbottleRPA.exit(1)


#延迟1秒
pbottleRPA.sleep(1000*1)



ret = pbottleRPA.browserCMD.text('span.title-content-title')
print('返回操作结果【一次多个】',ret)


ret = pbottleRPA.browserCMD.cookie('BAIDUID')
print('返回操作结果 cookieGet',ret)
ret = pbottleRPA.browserCMD.cookie('pbottleID',"good",3)
print('返回操作结果 cookieSet',ret)


pbottleRPA.tts('变换背景色')
ret = pbottleRPA.browserCMD.css('body',"background-color",'blue')
print('返回操作结果 cssSet',ret)
ret = pbottleRPA.browserCMD.css('body',"background-color")
print('返回操作结果【颜色值】',ret)
ret = pbottleRPA.browserCMD.css('body',"background-color",'white')
print('返回操作结果 cssSet',ret)


ret = pbottleRPA.browserCMD.text('title')
print('返回操作结果 textGet',ret)
pbottleRPA.tts('获取标题 ')
pbottleRPA.sleep(1000*3)


pbottleRPA.tts('设置页面标题 ')
ret = pbottleRPA.browserCMD.text('title','[小瓶RPA]-'+ret)
print('返回操作结果 textSet',ret)
ret = pbottleRPA.browserCMD.text('title')
print('当前页面标题:',ret)
pbottleRPA.sleep(1000*3)


pbottleRPA.tts('输入搜索词 点击搜索按钮 ')
ret = pbottleRPA.browserCMD.val('#kw','小瓶RPA')
print('返回点击操作结果 valSet',ret)


ret = pbottleRPA.browserCMD.click('#su')
print('返回点击操作结果 click',ret)


pbottleRPA.sleep(1000*3)


pbottleRPA.tts('开始去广告')
ret = pbottleRPA.browserCMD.remove('#content_left div:first')
print('返回点击操作结果 remove',ret)
pbottleRPA.sleep(3000)



pbottleRPA.tts('打开网站')
pbottleRPA.browserCMD.click('div#content_left a:first')
pbottleRPA.sleep(1500)


pbottleRPA.tts('读取 logo 路径,显示到日志')
ret = pbottleRPA.browserCMD.attr('img:first','src')
print('网站logo图片地址',ret)
pbottleRPA.sleep(1500)


pbottleRPA.tts('演示完成准备退出')
print("准备结束脚本")
ret = pbottleRPA.browserCMD.alert('演示结束')
#脚本强制退出
pbottleRPA.exit()
print("已经退出了,无效")

================================================
FILE: python示例/WEB增强-账号密码登录演示.py
================================================
"""

小瓶RPA python版本(Beta)
https://gitee.com/pbottle/pbottle-rpa
示例

"""

import pbottleRPA  #引入小瓶RPA模块
import time


print("=== WEB增强插件-账号密码登录演示 ===")
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(current_time)

print("=== ※※※※※※※※※ ===");
print("=== 需要安装 小瓶RPA 浏览器插件 ===");
print("=== ※※※※※※※※※ ===");


pbottleRPA.tts('必须安装小瓶RPA浏览器增强插件,手动点击确定继续')
pbottleRPA.showMsg('提示:','必须先安装浏览器增强插件')
pbottleRPA.openURL('https://yun.pbottle.com/?from=rpademo')


ret = pbottleRPA.browserCMD.alert('来自小瓶RPA的问候,手动点击确定开始,20秒超时')
print('返回操作结果',ret);
if (ret != 'ok'):
    print('没有检测到小瓶RPA浏览器插件',ret);
    pbottleRPA.exit(1)

#点击登录按钮
pbottleRPA.browserCMD.click("a[role='button']:contains(登录或注册)")
pbottleRPA.sleep(2000)

pbottleRPA.browserCMD.click("a[role='button']:contains(用帐号密码登录)")
pbottleRPA.sleep(1000)


#输入账号密码
pbottleRPA.browserCMD.click("input[name='uname']")
pbottleRPA.browserCMD.val("input[name='uname']",'test')

pbottleRPA.browserCMD.click("input[name='pwd']")
pbottleRPA.browserCMD.val("input[name='pwd']",'123456')
pbottleRPA.sleep(1000)


#登录按钮
pbottleRPA.browserCMD.click("button:contains(登录帐号)")
pbottleRPA.sleep(3000)

pbottleRPA.keyTap('enter')
pbottleRPA.tts('演示结束')

================================================
FILE: python示例/[企业版]接力执行脚本.py
================================================
"""

小瓶RPA python版本(Beta)
https://gitee.com/pbottle/pbottle-rpa
示例

"""

import pbottleRPA  #引入小瓶RPA模块
import time


pbottleRPA.delaySet(__file__)  #自己接力自己

pbottleRPA.log('等待3秒')
pbottleRPA.wait(3)
pbottleRPA.log('完成')


================================================
FILE: python示例/[第三方] 读写Excel演示脚本.py
================================================
"""
小瓶RPA演示demo,具体api请查看*流程开发文档*
官网:https://rpa.pbottle.com/
流程开发文档:https://rpa.pbottle.com/docs/
"""

import sys
import os
import platform
import json
import time

# 添加父目录到路径以导入pbottleRPA
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pbottleRPA as rpa

# 尝试导入openpyxl库
try:
    import openpyxl
    from openpyxl.styles import Font
except ImportError:
    rpa.showMsg("请先安装第三方模块", "运行: pip install openpyxl")
    rpa.tts("请先安装第三方模块" + "运行: pip install openpyxl")
    rpa.exit("请先安装第三方模块" + "运行: pip install openpyxl")


def excel_append(filename, line=None):
    """
    Excel 文件追加一行数据

    Args:
        filename (str): 文件绝对路径
        line (list): 行数据
    """
    if line is None:
        line = []
    print("excel追加行", filename, line)

    # 读取excel
    workbook = openpyxl.load_workbook(filename)
    sheet = workbook.active

    # excel 重新追加一行记录到已有的excel文件末尾
    sheet.append(line)

    # 保存
    workbook.save(filename)


print("=== Excel 读写测试 ===")
print(rpa.getTime())
rpa.tts("Excel 读写测试")
rpa.wait(3)
rpa.tts("将当前电脑配置信息生成EXCEL文件")
rpa.wait(5)

# 生成excel文档
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "pbottleRPA"

# 添加表头
sheet.append(["项", "值"])

# 设置第一行粗体
for cell in sheet[1]:
    cell.font = Font(bold=True)

# 设置列宽
sheet.column_dimensions["A"].width = 30
sheet.column_dimensions["B"].width = 60

# 添加数据行
sheet.append(
    [
        "时间",
        rpa.getTime(),
    ]
)
sheet.append(
    [
        "显示器分辨率",
        json.dumps(rpa.getResolution()),
    ]
)
sheet.append(
    [
        "CPU",
        platform.processor() or "Unknown",
    ]
)

# 保存文件
excel_path = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Excel测试表格.xlsx"
)
workbook.save(excel_path)
rpa.tts("已经生成EXCEL测试表格...请查看")
rpa.openDir(os.path.dirname(excel_path))

# 追加一条数据
excel_append(excel_path, ["项名", "重新追加值"])
rpa.wait(5)

# 读取excel
workbook2 = openpyxl.load_workbook(excel_path)
sheet2 = workbook2.active

# 获取所有数据
values = []
for row in sheet2.iter_rows(values_only=True):
    values.append(row)
print(values)
rpa.tts("已经读取EXCEL测试表格到日志")


if __name__ == "__main__":
    pass


================================================
FILE: python示例/[第三方] 读写word演示脚本.py
================================================
"""
小瓶 RPA 演示 demo,具体 api 请查看*流程开发文档*
官网:https://rpa.pbottle.com/
流程开发文档:https://rpa.pbottle.com/docs/
"""

import sys
import os
import time

# 添加父目录到路径以导入 pbottleRPA
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pbottleRPA as rpa

# 尝试导入 python-docx 和 mammoth 库
try:
    from docx import Document
    from docx.shared import Pt
    from docx.enum.text import WD_ALIGN_PARAGRAPH
    import mammoth
except ImportError:
    rpa.showMsg("请先安装第三方模块", "运行:pip install python-docx mammoth")
    rpa.tts("请先安装第三方模块" + "运行:pip install python-docx mammoth")
    rpa.exit("请先安装第三方模块" + "运行:pip install python-docx mammoth")


print("=== Word 后台读写测试 ===")
print(rpa.getTime())
rpa.tts("Word 后台读写测试")
rpa.wait(3)
rpa.tts("将后台生成 Word 文件")
rpa.wait(5)

# 生成 Word 文档
doc = Document()

# 添加标题段落
heading = doc.add_heading("标题文字", level=1)
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = heading.runs[0]
run.bold = True
run.font.size = Pt(20)

# 添加普通段落
p = doc.add_paragraph()
run1 = p.add_run("小瓶 RPA 官网:")
run1.font.size = Pt(12)

# 添加链接样式的文本
hyperlink_run = p.add_run("https://rpa.pbottle.com")
hyperlink_run.font.size = Pt(12)
hyperlink_run.font.underline = True

# 添加更多示例内容
doc.add_paragraph()
doc.add_paragraph("这是一个 Python 版本的 Word 读写演示。")
doc.add_paragraph("小瓶 RPA 支持多种自动化操作,包括:")
doc.add_paragraph("• 鼠标键盘操作", style="List Bullet")
doc.add_paragraph("• 图像识别", style="List Bullet")
doc.add_paragraph("• AI 能力集成", style="List Bullet")
doc.add_paragraph("• 文件操作", style="List Bullet")

# 保存文件
word_path = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Word测试文档.docx"
)
doc.save(word_path)
rpa.openDir(os.path.dirname(word_path))
rpa.tts("已经生成 Word测试文档...请查看")
rpa.wait(3)

# 读取 Word 文档
rpa.tts("将后台读取 Word 文件 显示到日志")
rpa.wait(3)

# 使用 mammoth 读取文本内容
with open(word_path, "rb") as f:
    result = mammoth.extract_raw_text(f)
    print("读取 Word 文档内容:", result.value)

rpa.tts("已经读取 Word测试文档到日志")
rpa.wait(2)


if __name__ == "__main__":
    pass


================================================
FILE: python示例/pbottleRPA.py
================================================
"""
小瓶RPA python版本(Beta)
https://gitee.com/pbottle/pbottle-rpa
官网:https://rpa.pbottle.com/

Nodejs 移植兼容版 beta
注:目前已完成 NodeJS 版本 API 同步

js -> python 对照表:

console.log ->  print  日志
json ->  {}   json 字典
`` ->  f""   字符串模板
encodeURIComponent -> urlencode

"""

import time
import json
import sys
import io
import zipfile
import os
import inspect
import urllib.request
import urllib.parse
import subprocess
import base64
import tempfile
import smtplib
import ssl
import random
from email.header import Header
from email.mime.text import MIMEText
from email.utils import formataddr

# ========== 全局配置 ==========
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", line_buffering=True)
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", line_buffering=True)
pyPath = os.path.dirname(os.path.abspath(__file__))
basePath = os.environ.get("RPAbaseDir", "")
homePath = os.environ.get("RPAhomeDir", "")
CppUrl = "http://127.0.0.1:49888/"
defaultDelay = 1000

print("基座服务地址:(Python)", CppUrl, "Python版本已同步NodeJS API")


# ========== 自定义异常 ==========
class RPAError(Exception):
    """RPA 操作异常基类"""

    pass


class TimeoutError(RPAError):
    """等待超时异常"""

    pass


# ========== 工具函数 ==========
def urlencode(input_str):
    """JS兼容的URL编码,等价于 encodeURIComponent"""
    return urllib.parse.quote(input_str, safe="")


def isNumeric(value):
    """
    判断是否为数字(包含数字字符串)
    @param value: 任意类型变量
    @return: bool
    """
    try:
        float(value)
        return True
    except (ValueError, TypeError):
        return False


def hasData(value):
    """
    判断变量是否包含有效数据(非零数字、非空字符串/列表/字典/对象等)
    @param value: 任意类型变量
    @return: bool
    """
    if value is None:
        return False
    if isinstance(value, str) and value.strip() == "":
        return False
    if isinstance(value, (list, tuple, dict)) and len(value) == 0:
        return False
    if isinstance(value, (int, float)) and (value == 0 or value != value):  # NaN check
        return False
    if isinstance(value, bool):
        return value
    return True


def getTime(format_str="Y-m-d H:i:s", timestamp=None):
    """
    格式化时间,支持 Y/y/m/d/H/i/s/n/j
    @param format_str: 格式字符串,如 'Y-m-d H:i:s'
    @param timestamp: 可选,Unix时间戳(秒)
    @return: 格式化后的时间字符串
    """
    if timestamp:
        date = time.localtime(timestamp)
    else:
        date = time.localtime()
    mapping = {
        "Y": date.tm_year,
        "y": str(date.tm_year)[-2:],
        "m": f"{date.tm_mon:02d}",
        "d": f"{date.tm_mday:02d}",
        "H": f"{date.tm_hour:02d}",
        "i": f"{date.tm_min:02d}",
        "s": f"{date.tm_sec:02d}",
        "n": date.tm_mon,
        "j": date.tm_mday,
    }
    result = format_str
    for k, v in mapping.items():
        result = result.replace(k, str(v))
    return result


def searchFile(directory, words="", recursive=False):
    """
    根据关键字搜索文件(递归可选)
    @param directory: 目录绝对路径
    @param words: 文件名包含的关键字(忽略大小写)
    @param recursive: 是否递归子目录
    @return: 匹配的文件路径列表
    """
    directory = os.path.abspath(directory)
    result = []
    words = words.lower()
    try:
        for root, dirs, files in os.walk(directory):
            if not recursive and root != directory:
                break
            for f in files:
                full_path = os.path.join(root, f)
                if words in full_path.lower():
                    result.append(full_path)
    except Exception:
        pass
    return result


def uniqid(prefix="", moreEntropy=False):
    """
    生成唯一ID(毫秒级)
    @param prefix: 前缀字符串
    @param moreEntropy: 是否增加随机熵
    @return: 唯一ID字符串
    """
    timestamp = format(int(time.time() * 1000), "x")
    rand = ""
    if moreEntropy:
        rand = format(random.randint(0, 0xFFFFFFFF), "x")
    return prefix + timestamp + rand


def substringFromTo(s, from_str="", to_str=""):
    """
    根据起始关键词截取字符串(不包含关键词本身)
    @param s: 原字符串
    @param from_str: 开始关键词,空表示从头部开始
    @param to_str: 结束关键词,空表示到结尾结束
    @return: 截取后的子串
    """
    start = s.find(from_str) + len(from_str) if from_str else 0
    end = s.rfind(to_str) if to_str else len(s)
    if (from_str and start == -1 + len(from_str)) or (to_str and end == -1):
        print(f"⚠substringFromTo 没有关键词: {from_str} -> {to_str}")
        return ""
    return s[start:end]


# ========== 基础操作 ==========
def setDefaultDelay(millisecond):
    """
    设置RPA模拟操作的全局延时(鼠标、键盘、粘贴、打开网页等)
    @param millisecond: 毫秒数,默认1000
    """
    global defaultDelay
    defaultDelay = millisecond


def sleep(milliseconds):
    """
    脚本暂停等待(毫秒),使用 Python 自带延时机制,一次等待上限 2 分钟
    @param milliseconds: 毫秒数
    """
    if milliseconds < 1:
        return
    if milliseconds >= 120000:
        print("警告:一次等待上限时长两分钟内")
    seconds = milliseconds / 1000.0
    time.sleep(seconds)


def wait(seconds=1):
    """
    脚本暂停等待(秒),支持小数,超过100秒会自动分段等待
    @param seconds: 秒数,默认1
    """
    if seconds <= 0 or not isNumeric(seconds):
        print("pbottleRPA.wait:seconds input error")
        return
    if seconds > 100:
        quotient = int(seconds // 100)
        for _ in range(quotient):
            sleep(100 * 1000)
            print("提示:已等待100s...")
        seconds = seconds % 100
    sleep(seconds * 1000)


def beep():
    """发出系统警告声音"""
    urllib.request.urlopen(f"{CppUrl}?action=beep")


def showMsg(title, content):
    """
    系统原生消息提示(右下角弹窗)
    @param title: 标题
    @param content: 内容
    """
    title = urlencode(title)
    content = urlencode(content)
    urllib.request.urlopen(f"{CppUrl}?action=showMsg&title={title}&content={content}")


def showRect(fromX=0, fromY=0, width=500, height=500, color="red", msec=500):
    """
    在屏幕上显示彩色方框(用于调试定位)
    @param fromX: 起始X坐标
    @param fromY: 起始Y坐标
    @param width: 宽度
    @param height: 高度
    @param color: 颜色 red/green/blue/yellow
    @param msec: 显示毫秒数
    """
    fromX = int(round(fromX))
    fromY = int(round(fromY))
    width = int(round(width))
    height = int(round(height))
    color = urlencode(color)
    urllib.request.urlopen(
        f"{CppUrl}?action=showRect&fromX={fromX}&fromY={fromY}&width={width}&height={height}&color={color}&msec={msec}"
    )


def exit_script(*args):
    """
    强制退出当前脚本
    @param *args: 退出时输出的信息
    """
    if args:
        print(*args)
    beep()
    sys.exit(1)


# 避免与内置 exit 冲突,同时保留原名
def exit(*args):
    """
    强制退出当前脚本
    @param *args: 退出时输出的信息
    """
    exit_script(*args)


def kill(processName, force=False):
    """
    关闭指定进程(Windows taskkill)
    @param processName: 进程名称,如 'WINWORD.EXE'
    @param force: 是否强制结束
    """
    force_flag = "/F" if force else ""
    try:
        subprocess.run(
            f"taskkill {force_flag} /IM {processName}",
            shell=True,
            check=True,
            capture_output=True,
        )
        print(f"关闭进程成功:{processName}")
    except subprocess.CalledProcessError:
        print(f"关闭进程({processName})失败,可能软件未运行")


def moveMouseSmooth(x, y, interval=0):
    """
    平滑移动鼠标到指定位置(屏幕左上角为原点)
    @param x: 横坐标
    @param y: 纵坐标
    @param interval: 像素间隔时间(毫秒),越大移动越慢,默认0
    """
    x = int(round(x))
    y = int(round(y))
    urllib.request.urlopen(f"{CppUrl}?action=moveMouse&x={x}&y={y}&interval={interval}")
    sleep(defaultDelay)


# 别名
moveMouse = moveMouseSmooth


def moveAndClick(x, y):
    """
    移动鼠标到指定位置并点击左键
    @param x: 横坐标
    @param y: 纵坐标
    """
    moveMouseSmooth(x, y)
    mouseClick()


def mouseClick(leftRight="left", time_ms=30):
    """
    当前位置点击鼠标
    @param leftRight: 'left' 或 'right'
    @param time_ms: 点按时间(毫秒)
    """
    action = "mouseLeftClick" if leftRight != "right" else "mouseRightClick"
    urllib.request.urlopen(f"{CppUrl}?action={action}&time={time_ms}")
    sleep(defaultDelay)


def mouseDoubleClick():
    """双击鼠标左键"""
    urllib.request.urlopen(f"{CppUrl}?action=mouseDoubleClick")
    sleep(defaultDelay)


def mouseWheel(data=-720):
    """
    鼠标滚轮
    @param data: 滚动量,负数向下,正数向上,默认-720
    """
    urllib.request.urlopen(f"{CppUrl}?action=mouseWheel&data={data}")
    sleep(defaultDelay)


def mouseLeftDragTo(x, y):
    """
    鼠标左键拖拽到指定位置
    @param x: 目标X坐标
    @param y: 目标Y坐标
    """
    x = int(round(x))
    y = int(round(y))
    urllib.request.urlopen(f"{CppUrl}?action=mouseLeftDragTo&x={x}&y={y}")
    sleep(defaultDelay)


def mouseRightDragTo(x, y):
    """
    鼠标右键拖拽到指定位置
    @param x: 目标X坐标
    @param y: 目标Y坐标
    """
    x = int(round(x))
    y = int(round(y))
    urllib.request.urlopen(f"{CppUrl}?action=mouseRightDragTo&x={x}&y={y}")
    sleep(defaultDelay)


def getScreenColor(x, y):
    """
    获取屏幕某点颜色
    @param x: 横坐标
    @param y: 纵坐标
    @return: 颜色值,如 '#000000'
    """
    resp = urllib.request.urlopen(f"{CppUrl}?action=getScreenColor&x={x}&y={y}")
    return json.loads(resp.read().decode())["rs"]


def screenShot(savePath="", x=0, y=0, w=-1, h=-1):
    """
    屏幕截图
    @param savePath: 保存路径(应以.png结尾),默认保存到“我的图片”
    @param x: 截图起始X
    @param y: 截图起始Y
    @param w: 宽度,-1表示全屏
    @param h: 高度,-1表示全屏
    @return: 返回结果字符串
    """
    if savePath:
        savePath = os.path.abspath(savePath)
        savePath = urlencode(savePath)
    x, y, w, h = int(x), int(y), int(w), int(h)
    if x != 0 or y != 0 or w != -1 or h != -1:
        showRect(x, y, w, h)
    resp = urllib.request.urlopen(
        f"{CppUrl}?action=screenShot&savePath={savePath}&x={x}&y={y}&w={w}&h={h}"
    )
    return resp.read().decode()


def keycode(name):
    """
    按键名称转虚拟键码(与 JS 版本完全对齐)
    @param name: 按键名称(参考 https://www.pbottle.com/a-13862.html)
    @return: 键码整数
    """
    name = name.strip().lower()
    mapping = {
        "backspace": 8,
        "tab": 9,
        "enter": 13,
        "shift": 16,
        "ctrl": 17,
        "alt": 18,
        "pause/break": 19,
        "caps lock": 20,
        "esc": 27,
        "space": 32,
        "page up": 33,
        "page down": 34,
        "end": 35,
        "home": 36,
        "left": 37,
        "up": 38,
        "right": 39,
        "down": 40,
        "insert": 45,
        "delete": 46,
        "command": 91,
        "left command": 91,
        "right command": 93,
        "numpad *": 106,
        "numpad +": 107,
        "numpad -": 109,
        "numpad .": 110,
        "numpad /": 111,
        "num lock": 144,
        "scroll lock": 145,
        "my computer": 182,
        "my calculator": 183,
        "windows": 91,
        "⇧": 16,
        "⌥": 18,
        "⌃": 17,
        "⌘": 91,
        "ctl": 17,
        "control": 17,
        "option": 18,
        "pause": 19,
        "break": 19,
        "caps": 20,
        "return": 13,
        "escape": 27,
        "spc": 32,
        "spacebar": 32,
        "pgup": 33,
        "pgdn": 34,
        "ins": 45,
        "del": 46,
        "cmd": 91,
        "f1": 112,
        "f2": 113,
        "f3": 114,
        "f4": 115,
        "f5": 116,
        "f6": 117,
        "f7": 118,
        "f8": 119,
        "f9": 120,
        "f10": 121,
        "f11": 122,
        "f12": 123,
        ";": 186,
        "=": 187,
        ",": 188,
        "-": 189,
        ".": 190,
        "/": 191,
        "`": 192,
        "[": 219,
        "\\": 220,
        "]": 221,
        "'": 222,
        "0": 48,
        "1": 49,
        "2": 50,
        "3": 51,
        "4": 52,
        "5": 53,
        "6": 54,
        "7": 55,
        "8": 56,
        "9": 57,
        "a": 65,
        "b": 66,
        "c": 67,
        "d": 68,
        "e": 69,
        "f": 70,
        "g": 71,
        "h": 72,
        "i": 73,
        "j": 74,
        "k": 75,
        "l": 76,
        "m": 77,
        "n": 78,
        "o": 79,
        "p": 80,
        "q": 81,
        "r": 82,
        "s": 83,
        "t": 84,
        "u": 85,
        "v": 86,
        "w": 87,
        "x": 88,
        "y": 89,
        "z": 90,
    }
    return mapping.get(name, 0)


def keyToggle(key, upDown="down"):
    """
    模拟键盘按键基础事件(按下或松开)
    @param key: 按键名称
    @param upDown: 'down' 按下 / 'up' 松开
    """
    key_n = keycode(key)
    if key_n == 0:
        print(f"⚠ 按键 {key} 不存在!~")
        return
    upDown_n = 0 if upDown != "up" else 2
    urllib.request.urlopen(
        f"{CppUrl}?action=keyToggle&key_n={key_n}&upDown_n={upDown_n}"
    )


def keyTap(key):
    """
    模拟键盘按键(按下并松开),支持组合键,如 'ctrl+a'
    @param key: 按键名称或组合键(加号连接)
    """
    if "+" in key:
        parts = [p.strip() for p in key.split("+")]
        for p in parts:
            keyToggle(p, "down")
        for p in reversed(parts):
            keyToggle(p, "up")
    else:
        keyToggle(key, "down")
        keyToggle(key, "up")
    sleep(defaultDelay)


def mouseKeyToggle(key="left", upDown="down"):
    """
    模拟鼠标按键基础事件
    @param key: 'left' / 'right' / 'middle'
    @param upDown: 'down' 按下 / 'up' 松开
    """
    key_map = {"left": 0, "right": 1, "middle": 2}
    key_n = key_map.get(key, 0)
    upDown_n = 0 if upDown != "up" else 2
    urllib.request.urlopen(
        f"{CppUrl}?action=mouseKeyToggle&key_n={key_n}&upDown_n={upDown_n}"
    )


def findScreen(tpPaths, miniSimilarity=0.85, fromX=0, fromY=0, width=-1, height=-1):
    """
    屏幕查找图像定位
    @param tpPaths: 小图片路径(建议png),或图片路径列表
    @param miniSimilarity: 最低相似度,0-1,默认0.85
    @param fromX: 查找起始X
    @param fromY: 查找起始Y
    @param width: 搜索宽度,-1表示全屏
    @param height: 搜索高度,-1表示全屏
    @return: 找到返回 {'x':int, 'y':int, 'value':float},否则返回 False
    """
    if fromX < 0 or fromY < 0:
        raise ValueError(f"错误:找图起始点不能为负,x:{fromX} y:{fromY}")
    if fromX != 0 or fromY != 0 or width != -1 or height != -1:
        showRect(fromX, fromY, width, height)
    if not isinstance(tpPaths, list):
        tpPaths = [tpPaths]

    for tpPath in tpPaths:
        tpPath = os.path.abspath(tpPath)
        tpPath = urlencode(tpPath)
        resp = urllib.request.urlopen(
            f"{CppUrl}?action=findScreen&imgPath={tpPath}&fromX={fromX}&fromY={fromY}&width={width}&height={height}"
        )
        data = json.loads(resp.read().decode())
        if "error" not in data and data.get("value", 0) >= miniSimilarity:
            showRect(data["x"] - 25, data["y"] - 25, 50, 50, "green")
            return {"x": data["x"], "y": data["y"], "value": data["value"]}
    return False


def findText(inputTxt, fromX=0, fromY=0, width=-1, height=-1):
    """
    查找屏幕上的文字(基于OCR)
    @param inputTxt: 要查找的文字(部分匹配)
    @param fromX: 查找起始X
    @param fromY: 查找起始Y
    @param width: 搜索宽度
    @param height: 搜索高度
    @return: 找到返回 {'text':str, 'x':int, 'y':int, 'score':float},否则返回 False
    """
    ocr_res = aiOcr("screen", fromX, fromY, width, height)
    for item in ocr_res:
        if inputTxt in item["text"]:
            showRect(item["x"] - 25, item["y"] - 25, 50, 50, "green")
            return item
    return False


def waitText(
    inputTxt, fromX=0, fromY=0, width=-1, height=-1, intervalFun=None, timeOut=20
):
    """
    等待屏幕指定文字出现
    @param inputTxt: 搜索文字
    @param fromX,fromY,width,height: 搜索范围
    @param intervalFun: 回调函数,返回 'stopWait' 时停止等待
    @param timeOut: 超时秒数
    @return: 找到返回位置字典,超时抛出 TimeoutError
    """
    print("waiting Text:", inputTxt)
    for _ in range(timeOut):
        sleep(1000)
        pos = findText(inputTxt, fromX, fromY, width, height)
        if pos:
            return pos
        if intervalFun and intervalFun() == "stopWait":
            print("stopWait from intervalFun")
            return False
    print("已经保存超时截图到:我的图片")
    screenShot()
    frame = inspect.currentframe().f_back
    raise TimeoutError(f"等待文字超时 {inputTxt} 位置(行):{frame.f_lineno}")


def findContours(minimumArea=1000, fromX=0, fromY=0, width=-1, height=-1):
    """
    查找屏幕上的轮廓(物体/窗口边缘)
    @param minimumArea: 最小面积,默认1000(约31x31像素)
    @param fromX: 查找起始X
    @param fromY: 查找起始Y
    @param width: 搜索宽度
    @param height: 搜索高度
    @return: 轮廓列表,每个元素包含 x,y,cx,cy,area,id
    """
    if fromX < 0 or fromY < 0:
        raise ValueError(f"错误:轮廓查找起始点不能为负,x:{fromX} y:{fromY}")
    if fromX != 0 or fromY != 0 or width != -1 or height != -1:
        showRect(fromX, fromY, width, height)
    resp = urllib.request.urlopen(
        f"{CppUrl}?action=findContours&minimumArea={minimumArea}&fromX={fromX}&fromY={fromY}&width={width}&height={height}"
    )
    contours = json.loads(resp.read().decode())
    for c in contours:
        c["x"] += fromX
        c["y"] += fromY
    return contours


def imgSimilar(path1, path2, checkType="ORB"):
    """
    图片相似度对比
    @param path1: 图片1路径
    @param path2: 图片2路径
    @param checkType: 算法 'SIFT'/'ORB'/'SSIM',默认 'ORB'
    @return: {'score':float, 'time':float}
    """
    path1 = urlencode(os.path.abspath(path1))
    path2 = urlencode(os.path.abspath(path2))
    resp = urllib.request.urlopen(
        f"{CppUrl}?action=imgSimilar&path1={path1}&path2={path2}&checkType={checkType}"
    )
    return json.loads(resp.read().decode())


def paste(txt):
    """
    在当前焦点位置粘贴输入文本(模拟 Ctrl+V)
    @param txt: 要输入的文本
    """
    copyText(txt)
    keyTap("ctrl+v")
    sleep(defaultDelay)


def copyText(txt):
    """
    复制文本到剪贴板
    @param txt: 文本内容
    """
    txt = urlencode(txt)
    urllib.request.urlopen(f"{CppUrl}?action=copyText&txt={txt}")


def copyFile(filepath):
    """
    复制文件/文件夹到剪贴板,之后可在目标位置粘贴(如微信发送文件)
    @param filepath: 绝对路径
    """
    filepath = os.path.abspath(filepath)
    if not os.path.exists(filepath):
        print(f"copyFile警告:文件路径不存在 {filepath}")
    filepath = filepath.replace("\\", "/")
    filepath = urlencode(filepath)
    urllib.request.urlopen(f"{CppUrl}?action=copyFile&path={filepath}")


def getClipboard():
    """
    获取剪贴板内容(支持文本、图片base64、HTML)
    @return: 剪贴板内容字符串
    """
    resp = urllib.request.urlopen(f"{CppUrl}?action=getClipboard")
    return resp.read().decode()


def wxMessage(title, content, key):
    """
    通过小瓶云发送微信通知(免费)
    @param title: 消息标题
    @param content: 消息内容
    @param key: 获取key详情 https://www.pbottle.com/a-12586.html
    """
    url = f"https://yun.pbottle.com/manage/yun/?msg={urlencode(content)}&name={urlencode(title)}&key={key}"
    resp = urllib.request.urlopen(url)
    print("发送微信消息:", resp.read().decode())


def postJson(url, msgJson, headersJson=None, method="POST"):
    """
    向API发送JSON数据
    @param url: API地址
    @param msgJson: 要发送的字典对象
    @param headersJson: 额外请求头字典
    @param method: HTTP方法,默认POST
    @return: 响应文本
    """
    if headersJson is None:
        headersJson = {}
    data = json.dumps(msgJson).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json", **headersJson},
        method=method,
    )
    with urllib.request.urlopen(req) as f:
        return f.read().decode()


def postJsonFile(url, msgJsonFile, headersJson=None, method="POST"):
    """
    从文件读取JSON并发送到API(适合大JSON)
    @param url: API地址
    @param msgJsonFile: JSON文件路径
    @param headersJson: 额外请求头字典
    @param method: HTTP方法
    @return: 响应文本
    """
    if headersJson is None:
        headersJson = {}
    msgJsonFile = os.path.abspath(msgJsonFile)
    with open(msgJsonFile, "rb") as f:
        data = f.read()
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json", **headersJson},
        method=method,
    )
    with urllib.request.urlopen(req) as f:
        return f.read().decode()


def getHtml(url, headersJson=None, method="GET"):
    """
    普通HTTP请求,返回响应文本
    @param url: 网址
    @param headersJson: 请求头字典
    @param method: HTTP方法
    @return: 响应文本
    """
    if headersJson is None:
        headersJson = {}
    req = urllib.request.Request(url, headers=headersJson, method=method)
    with urllib.request.urlopen(req) as f:
        return f.read().decode()


def downloadFile(fileUrl, filename, headersJson=None):
    """
    下载文件到本地
    @param fileUrl: 文件URL
    @param filename: 本地保存路径
    @param headersJson: 请求头字典
    """
    if headersJson is None:
        headersJson = {}
    filename = os.path.abspath(filename)
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    print("下载文件到:", filename)
    req = urllib.request.Request(fileUrl, headers=headersJson)
    with urllib.request.urlopen(req) as resp, open(filename, "wb") as out:
        out.write(resp.read())


def sendMail(
    to,
    subject,
    content,
    host="smtp.qq.com",
    port=465,
    user="leo191@foxmail.com",
    passwd="fxfqtsxmwcohbcbc",
):
    """
    发送邮件(同步阻塞)
    @param to: 收件人地址
    @param subject: 主题
    @param content: 正文(纯文本)
    @param host: SMTP服务器
    @param port: 端口
    @param user: 用户名
    @param passwd: 密码
    @return: 成功提示字符串
    """
    if user == "leo191@foxmail.com":
        content += "\n\n 请不要将演示测试邮箱用作实际业务,详细查看:https://rpa.pbottle.com/a-14106.html"
    msg = MIMEText(content, "plain", "utf-8")
    msg["From"] = formataddr(("小瓶RPA", user))
    msg["To"] = formataddr(("", to)) if "@" in str(to) else to
    msg["Subject"] = Header(subject, "utf-8")

    context = ssl.create_default_context()
    with smtplib.SMTP_SSL(host, port, context=context) as server:
        server.login(user, passwd)
        server.sendmail(user, [to], msg.as_string())
    return "✅ 邮件发送成功"


def tts(text):
    """
    文字转语音播报(非阻塞)
    @param text: 要朗读的文本
    """
    text = urlencode(text)
    urllib.request.urlopen(f"{CppUrl}?action=tts&txt={text}")
    sleep(defaultDelay)


def openURL(myurl):
    """
    用默认浏览器打开网址
    @param myurl: 网址
    """
    urllib.request.urlopen(f"{CppUrl}?action=setWebReadyPage")
    myurl = urlencode(myurl)
    urllib.request.urlopen(f"{CppUrl}?action=openURL&url={myurl}")
    sleep(defaultDelay + 1000)


def openDir(filePath):
    """
    用资源管理器打开文件夹或默认程序打开文件
    @param filePath: 绝对路径
    """
    filePath = os.path.abspath(filePath)
    filePath = urlencode(filePath)
    urllib.request.urlopen(f"{CppUrl}?action=openDir&path={filePath}")
    sleep(defaultDelay)


# 别名
openfile = openDir


def getResolution():
    """
    获取屏幕分辨率及缩放比例
    @return: {'w':int, 'h':int, 'ratio':float}
    """
    resp = urllib.request.urlopen(f"{CppUrl}?action=getResolution")
    return json.loads(resp.read().decode())


def aiOcr(imagePath="screen", x=0, y=0, width=-1, height=-1):
    """
    AI文字识别(OCR),支持屏幕或图片文件
    @param imagePath: 'screen' 或图片绝对路径
    @param x: 起始X
    @param y: 起始Y
    @param width: 宽度
    @param height: 高度
    @return: 识别结果列表,每项含 text,score,x,y
    """
    if not imagePath:
        imagePath = "screen"
    if x < 0 or y < 0:
        raise ValueError(f"错误:OCR 起始点不能为负,x:{x} y:{y}")
    if x != 0 or y != 0 or width != -1 or height != -1:
        showRect(x, y, width, height)
    if imagePath != "screen":
        imagePath = os.path.abspath(imagePath)
        imagePath = urlencode(imagePath)
    resp = urllib.request.urlopen(
        f"{CppUrl}?action=aiOcr&path={imagePath}&x={x}&y={y}&width={width}&height={height}&onlyEn=0"
    )
    res = resp.read().decode()
    if res == "文字识别引擎未启动":
        print("⚠ 文字识别引擎未启动,请在软件设置中开启")
        exit_script()
    items = json.loads(res)
    for it in items:
        it["x"] += x
        it["y"] += y
    return items


def aiObject(minimumScore=0.5, x=0, y=0, width=-1, height=-1):
    """
    AI物体识别(检测常见物体)
    @param minimumScore: 置信度阈值,默认0.5
    @param x: 起始X
    @param y: 起始Y
    @param width: 宽度
    @param height: 高度
    @return: 检测结果列表,每项含 x,y,width,height,score,class
    """
    if x < 0 or y < 0:
        raise ValueError(f"错误:物体识别起始点不能为负,x:{x} y:{y}")
    if x != 0 or y != 0 or width != -1 or height != -1:
        showRect(x, y, width, height)
    resp = urllib.request.urlopen(
        f"{CppUrl}?action=aiObject&minimumScore={minimumScore}&x={x}&y={y}&width={width}&height={height}&onlyEn=0"
    )
    res = resp.read().decode()
    if res == "物体识别引擎未启动":
        print("⚠ 物体识别引擎未启动,请在软件设置中开启")
        exit_script()
    items = json.loads(res)
    for it in items:
        it["x"] += x
        it["y"] += y
        showRect(it["x"], it["y"], it["width"], it["height"], "green")
    return items


def zipDir(directory, zipFilePath=""):
    """
    压缩文件夹为ZIP文件(使用Python标准库zipfile)
    @param directory: 要压缩的文件夹路径
    @param zipFilePath: 输出ZIP路径,默认在目录下生成
    """
    if not zipFilePath:
        zipFilePath = os.path.join(directory, "RPA生成的压缩包.zip")
    directory = os.path.abspath(directory)
    zipFilePath = os.path.abspath(zipFilePath)
    with zipfile.ZipFile(zipFilePath, "w", zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, files in os.walk(directory):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, directory)
                zipf.write(file_path, arcname)


def unZip(zipFilePath, directory=""):
    """
    解压ZIP文件到指定目录
    @param zipFilePath: ZIP文件路径
    @param directory: 解压目标目录,默认与ZIP同级
    """
    if not directory:
        directory = os.path.dirname(zipFilePath)
    zipFilePath = os.path.abspath(zipFilePath)
    directory = os.path.abspath(directory)
    os.makedirs(directory, exist_ok=True)
    with zipfile.ZipFile(zipFilePath, "r") as zipf:
        zipf.extractall(directory)


def bufferGet(n=0):
    """
    获取跨脚本共享缓冲区内容(0-9共10个)
    @param n: 缓冲区编号
    @return: 存储的字符串
    """
    resp = urllib.request.urlopen(f"{CppUrl}?action=bufferGet&n={n}")
    return resp.read().decode()


def bufferSet(content, n=0):
    """
    设置跨脚本共享缓冲区内容
    @param content: 要存储的内容(字符串)
    @param n: 缓冲区编号
    @return: 'ok' 表示成功
    """
    return postJson(f"{CppUrl}?action=bufferSet&n={n}", content)


def delaySet(scriptPath=""):
    """
    设置当前脚本结束后自动接力的脚本
    @param scriptPath: 接力脚本绝对路径,为空则清除接力任务
    @return: 'ok'
    """
    if scriptPath:
        scriptPath = os.path.abspath(scriptPath)
    scriptPath = urlencode(scriptPath)
    resp = urllib.request.urlopen(f"{CppUrl}?action=pbottleRPA_delay&path={scriptPath}")
    return resp.read().decode()


def deviceID():
    """
    获取当前设备的唯一ID(用于云端接口)
    @return: 设备ID字符串
    """
    resp = urllib.request.urlopen(f"{CppUrl}?action=pbottleRPA_deviceID")
    return resp.read().decode()


def clusterCenter():
    """
    获取集群中心信息(企业版功能)
    @return: 字符串
    """
    resp = urllib.request.urlopen(f"{CppUrl}?action=pbottleRPA_clusterCenter")
    return resp.read().decode()


# ========== Cloud 模块 ==========
class cloud:
    @staticmethod
    def GPT(question, modelLevel=0, options=None):
        """
        云端大语言模型问答
        @param question: 问题字符串
        @param modelLevel: 0=低价模型,1=性价比,2=旗舰
        @param options: 可选字典,如 {'response_format':'json_object', 'temperature':0.75, 'enable_search':False}
        @return: {'content':str, 'tokens':int}
        """
        if options is None:
            options = {
                "response_format": "text",
                "temperature": 0.75,
                "enable_search": False,
            }
        if len(question) < 3:
            raise ValueError("问题过短,请输入至少2个字符")
        data = {
            "question": question,
            "deviceToken": deviceID(),
            "modelLevel": modelLevel,
            "options": options,
        }
        resp = postJson("https://rpa.pbottle.com/API/", data)
        result = json.loads(resp)
        if result.get("error"):
            raise RPAError(f"错误: {result['error']}")
        return result

    @staticmethod
    def GPTV(question, imagePath, modelLevel=0):
        """
        云端图像分析大模型(图片+问题)
        @param question: 关于图片的问题
        @param imagePath: 图片本地路径
        @param modelLevel: 模型等级
        @return: {'content':str, 'tokens':int}
        """
        imagePath = os.path.abspath(imagePath)
        if not os.path.exists(imagePath):
            raise FileNotFoundError("输入分析图片不存在:cloud_GPTV")
        with open(imagePath, "rb") as f:
            img_b64 = base64.b64encode(f.read()).decode()
        temp_json = os.path.join(tempfile.gettempdir(), "cloud_GPTV.json")
        with open(temp_json, "w") as f:
            json.dump(
                {
                    "question": question,
                    "deviceToken": deviceID(),
                    "modelLevel": modelLevel,
                    "image_base64": img_b64,
                },
                f,
            )
        resp = postJsonFile("https://rpa.pbottle.com/API/gptv", temp_json)
        result = json.loads(resp)
        if result.get("error"):
            raise RPAError(f"错误 cloud_GPTV: {result['error']}")
        return result

    @staticmethod
    def GPTA(action="点击", question="桌面微信图标"):
        """
        云端屏幕分析并自动执行动作(点击/双击/右键)
        @param action: '点击' / '双击' / '右键'
        @param question: 要操作的目标描述,如“桌面微信图标”
        """
        device_token = deviceID()
        tmp_img = os.path.join(
            homePath if homePath else tempfile.gettempdir(), "cloud_GPT_do.png"
        )
        tmp_json = os.path.join(
            homePath if homePath else tempfile.gettempdir(), "cloud_GPTV.json"
        )
        screenShot(tmp_img)
        with open(tmp_img, "rb") as f:
            img_b64 = "data:image/png;base64," + base64.b64encode(f.read()).decode()
        with open(tmp_json, "w") as f:
            json.dump(
                {
                    "question": question,
                    "deviceToken": device_token,
                    "image_base64": img_b64,
                },
                f,
            )
        resp = postJsonFile("https://rpa.pbottle.com/API/gpta", tmp_json)
        result = json.loads(resp)
        if result.get("error"):
            raise RPAError(f"错误 cloud_GPTA: {result['error']}")
        print(result)
        boxes = result["content"].split("\n")
        resolution = getResolution()
        for box_str in boxes:
            if not box_str.strip():
                continue
            box = json.loads(box_str)
            x1 = box[0] / 1000.0 * resolution["w"]
            y1 = box[1] / 1000.0 * resolution["h"]
            x2 = box[2] / 1000.0 * resolution["w"]
            y2 = box[3] / 1000.0 * resolution["h"]
            showRect(x1, y1, x2 - x1, y2 - y1, "green")
            cx = int(round((x1 + x2) / 2))
            cy = int(round((y1 + y2) / 2))
            print(f"{question} 的位置: ({cx}, {cy})")
            moveMouseSmooth(cx, cy)
            if action == "点击":
                mouseClick("left")
            elif action == "双击":
                mouseDoubleClick()
            elif action == "右键":
                mouseClick("right")


# ========== 浏览器增强模块 ==========
class browserCMD:
    @staticmethod
    def alert(msg):
        """浏览器弹出警告框"""
        code = json.dumps({"action": "alert", "args": [msg]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def closeTab(type="current"):
        """
        关闭浏览器标签页
        @param type: 'current' 关闭当前页, 'other' 关闭其他页
        """
        code = json.dumps({"action": "closeTab", "args": [type]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def fetch(fetch_url, options=None):
        """
        从当前页面发起fetch请求
        @param fetch_url: 目标URL
        @param options: 请求选项字典
        """
        if options is None:
            options = {}
        code = json.dumps({"action": "fetch", "args": [fetch_url, options]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def waitPageReady(readyURL, timeout=20):
        """
        等待浏览器页面加载到指定URL
        @param readyURL: 期望的URL(完全匹配)
        @param timeout: 超时秒数
        @return: 当前URL
        """
        url = f"{CppUrl}?action=getWebReadyPage"
        for _ in range(timeout):
            res = getHtml(url)
            if res == readyURL:
                return res
            sleep(1000)
            print("等待页面加载完成...")
        raise TimeoutError("waitPageReady 等待页面加载超时")

    @staticmethod
    def url(urlStr=None):
        """
        获取或设置当前页面URL
        @param urlStr: 设置新URL时传入,不传则获取当前URL
        @return: 当前URL字符串
        """
        args = [urlStr] if urlStr is not None else []
        code = json.dumps({"action": "url", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def count(selector):
        """
        统计符合选择器的元素数量
        @param selector: CSS选择器
        @return: 元素个数
        """
        code = json.dumps({"action": "count", "args": [selector]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return int(resp) if isNumeric(resp) else 0

    @staticmethod
    def dblclick(selector, options=None):
        """
        双击匹配的第一个元素
        @param selector: CSS选择器
        @param options: 鼠标事件选项
        """
        if options is None:
            options = {}
        code = json.dumps({"action": "dblclick", "args": [selector, options]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def offset(selector):
        """
        获取元素相对于文档左上角的位置
        @param selector: CSS选择器
        @return: {'left':int, 'top':int}
        """
        code = json.dumps({"action": "offset", "args": [selector]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return json.loads(resp)

    @staticmethod
    def click(selector, options=None):
        """
        点击匹配的第一个元素
        @param selector: CSS选择器
        @param options: 鼠标事件选项
        """
        if options is None:
            options = {}
        code = json.dumps({"action": "click", "args": [selector, options]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def show(selector):
        """显示匹配的元素(修改display样式)"""
        code = json.dumps({"action": "show", "args": [selector]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def hide(selector):
        """隐藏匹配的元素"""
        code = json.dumps({"action": "hide", "args": [selector]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def remove(selector):
        """从DOM中移除匹配的元素"""
        code = json.dumps({"action": "remove", "args": [selector]})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def text(selector, content=None):
        """
        获取或设置元素的纯文本内容
        @param selector: CSS选择器
        @param content: 设置文本时传入,不传则获取文本
        @return: 文本内容(多个元素返回数组)
        """
        args = [selector] if content is None else [selector, content]
        code = json.dumps({"action": "text", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def html(selector, content=None):
        """
        获取或设置元素的HTML内容
        @param selector: CSS选择器
        @param content: 设置HTML时传入
        """
        args = [selector] if content is None else [selector, content]
        code = json.dumps({"action": "html", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def val(selector, content=None):
        """
        获取或设置表单元素的值
        @param selector: CSS选择器
        @param content: 设置值时传入
        """
        args = [selector] if content is None else [selector, content]
        code = json.dumps({"action": "val", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def cookie(cName, cValue=None, expDays=None):
        """
        获取或设置cookie
        @param cName: cookie名称
        @param cValue: 设置时传入值,不传则获取
        @param expDays: 过期天数,不传则为会话cookie
        """
        args = [cName]
        if cValue is not None:
            args.append(cValue)
        if expDays is not None:
            args.append(expDays)
        code = json.dumps({"action": "cookie", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def css(selector, propertyname, value=None):
        """
        获取或设置CSS样式
        @param selector: CSS选择器
        @param propertyname: 样式属性名
        @param value: 设置时传入值
        """
        args = (
            [selector, propertyname]
            if value is None
            else [selector, propertyname, value]
        )
        code = json.dumps({"action": "css", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def attr(selector, propertyname, value=None):
        """
        获取或设置元素属性
        @param selector: CSS选择器
        @param propertyname: 属性名
        @param value: 设置时传入值
        """
        args = (
            [selector, propertyname]
            if value is None
            else [selector, propertyname, value]
        )
        code = json.dumps({"action": "attr", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp

    @staticmethod
    def prop(selector, propertyname, value=None):
        """
        获取或设置DOM属性(如checked, disabled)
        @param selector: CSS选择器
        @param propertyname: 属性名
        @param value: 设置时传入值
        """
        args = (
            [selector, propertyname]
            if value is None
            else [selector, propertyname, value]
        )
        code = json.dumps({"action": "prop", "args": args})
        resp = getHtml(f"{CppUrl}?action=webInject&jscode={urlencode(code)}")
        return resp


# ========== 硬件级 hid 模块 ==========
class hid:
    @staticmethod
    def keyToggle(key, upDown="down"):
        """硬件级按键按下/松开"""
        key_n = keycode(key)
        if key_n == 0:
            print(f"⚠ 按键 {key} 不存在!~")
            return
        upDown_n = 0 if upDown != "up" else 2
        urllib.request.urlopen(
            f"{CppUrl}?action=keyToggleHardWare&key_n={key_n}&upDown_n={upDown_n}"
        )

    @staticmethod
    def keyTap(key):
        """硬件级按键(按下并松开),支持组合键"""
        if "+" in key:
            parts = [p.strip() for p in key.split("+")]
            for p in parts:
                hid.keyToggle(p, "down")
            for p in reversed(parts):
                hid.keyToggle(p, "up")
        else:
            hid.keyToggle(key, "down")
            hid.keyToggle(key, "up")
        sleep(defaultDelay)

    @staticmethod
    def _mouseCMD(button=1, x=0, y=0, wheel=0, time_ms=10):
        """硬件级鼠标底层命令"""
        urllib.request.urlopen(
            f"{CppUrl}?action=mouseDataHardWare&bt={button}&x={x}&y={y}&wheel={wheel}&time={time_ms}"
        )

    @staticmethod
    def moveMouse(x, y):
        """硬件级移动鼠标到绝对坐标"""
        hid._mouseCMD(0, int(round(x)), int(round(y)), 0, 10)

    @staticmethod
    def mouseClick(button="left", time_ms=10):
        """硬件级鼠标点击"""
        bt = {"left": 1, "right": 2, "middle": 4}.get(button, 1)
        hid._mouseCMD(bt, 0, 0, 0, time_ms)
        hid._mouseCMD(0, 0, 0, 0, 0)
        sleep(defaultDelay)

    @staticmethod
    def moveAndClick(x, y):
        """硬件级移动并点击"""
        hid.moveMouse(x, y)
        hid.mouseClick()

    @staticmethod
    def mouseDoubleClick():
        """硬件级双击左键"""
        hid.mouseClick("left", 10)
        hid.mouseClick("left", 10)
        sleep(defaultDelay)

    @staticmethod
    def mouseLeftDragTo(x, y):
        """硬件级左键拖拽"""
        hid._mouseCMD(1, 0, 0, 0, 10)
        hid._mouseCMD(1, int(round(x)), int(round(y)), 0, 10)
        hid._mouseCMD(0, 0, 0, 0, 0)
        sleep(defaultDelay)

    @staticmethod
    def mouseRightDragTo(x, y):
        """硬件级右键拖拽"""
        hid._mouseCMD(2, 0, 0, 0, 10)
        hid._mouseCMD(2, int(round(x)), int(round(y)), 0, 10)
        hid._mouseCMD(0, 0, 0, 0, 0)
        sleep(defaultDelay)

    @staticmethod
    def mouseWheel(data=-1):
        """硬件级滚轮,-1向下滚动一格"""
        hid._mouseCMD(0, 0, 0, data, 0)
        hid._mouseCMD(0, 0, 0, 0, 0)
        sleep(defaultDelay)


# ========== 等待函数 ==========
def waitImage(tpPath, intervalFun=None, timeOut=30, miniSimilarity=0.85):
    """
    等待屏幕上的图片出现(每1秒检测一次)
    @param tpPath: 图片模板路径 相对路径:./image/123.png  | 列表等待多个图片
    @param intervalFun: 每次检测间隔调用的函数,若返回 'stopWait' 则提前结束
    @param timeOut: 超时秒数
    @param miniSimilarity: 最低相似度
    @return: 找到返回位置字典,超时抛出 TimeoutError
    """
    print("waitImage", tpPath)
    for _ in range(timeOut):
        sleep(1000)
        pos = findScreen(tpPath, miniSimilarity)
        if pos:
            return pos
        if intervalFun and intervalFun() == "stopWait":
            print("stopWait from intervalFun")
            return False
    print("已经保存超时截图到:我的图片")
    screenShot()
    frame = inspect.currentframe().f_back
    raise TimeoutError(f"等待图片超时 {tpPath} 位置(行):{frame.f_lineno}")


def waitImageDisappear(tpPath, intervalFun=None, timeOut=30, miniSimilarity=0.85):
    """
    等待屏幕上的图片消失
    @param tpPath: 图片模板路径
    @param intervalFun: 检测间隔回调
    @param timeOut: 超时秒数
    @param miniSimilarity: 相似度阈值
    @return: 'ok' 表示消失,超时抛出 TimeoutError
    """
    print("waitImageDisappear", tpPath)
    for _ in range(timeOut):
        sleep(1000)
        pos = findScreen(tpPath, miniSimilarity)
        if not pos:
            return "ok"
        if intervalFun and intervalFun() == "stopWait":
            print("stopWait from intervalFun")
            return False
    print("已经保存超时截图到:我的图片")
    screenShot()
    frame = inspect.currentframe().f_back
    raise TimeoutError(f"等待图片消失超时 {tpPath} 位置(行):{frame.f_lineno}")


def waitFile(dirPath, keyWords="", intervalFun=None, timeOut=30):
    """
    等待指定目录下出现包含关键词的文件
    @param dirPath: 监控目录
    @param keyWords: 文件名包含的关键词
    @param intervalFun: 检测间隔回调
    @param timeOut: 超时秒数
Download .txt
gitextract_s9rnej04/

├── .gitignore
├── GPT图像解析示例.js
├── GPT问题答案AI生成演示.js
├── LICENSE
├── README.md
├── WEB增强-数据批量爬取演示.js
├── WEB增强-浏览器元素操作演示.js
├── WEB增强-账号密码登录演示.js
├── [企业版]HID硬件级键盘鼠标演示.js
├── [企业版]外部控制能力.js
├── [企业版]屏幕物体查找Ai演示.js
├── [企业版]接力执行脚本.js
├── [企业版]集群控制中心日志回传.js
├── [企业版]集群控制中心流程升级 .js
├── [企业版]集群控制中心示例.js
├── [第三方 模块卸载].bat
├── [第三方 模块安装].bat
├── [第三方] 读写Excel演示脚本.mjs
├── [第三方] 读写word演示脚本.mjs
├── docs/
│   ├── -图片测试.md
│   ├── .vitepress/
│   │   └── config.mjs
│   ├── AI生成流程脚本.md
│   ├── APIAI图像.md
│   ├── APIAI大模型.md
│   ├── API办公文档.md
│   ├── API压缩解压.md
│   ├── API声音.md
│   ├── API外部控制.md
│   ├── API屏幕.md
│   ├── API浏览器增强.md
│   ├── API用户输入.md
│   ├── API系统相关.md
│   ├── API统一规范.md
│   ├── API网络.md
│   ├── API通用工具.md
│   ├── API键盘操作.md
│   ├── API键鼠硬模拟.md
│   ├── API鼠标操作.md
│   ├── Demo示例.md
│   ├── HTTP静态服务.md
│   ├── SaaS系统自动化任务.md
│   ├── index.md
│   ├── package.json
│   ├── public/
│   │   └── index.html
│   ├── win7操作系统.md
│   ├── ‌Q&A.md
│   ├── 专用自动化独立软件.md
│   ├── 业务管理系统.md
│   ├── 中文调用.md
│   ├── 信创操作系统.md
│   ├── 其他功能模块.md
│   ├── 定时启动.md
│   ├── 开机启动.md
│   ├── 手机应用的自动化.md
│   ├── 无尽模式.md
│   ├── 更多三方功能和拓展.md
│   ├── 桌面快捷方式.md
│   ├── 流程录制.md
│   ├── 流程运行日志.md
│   ├── 流程配置项.md
│   ├── 热键和快捷方式.md
│   ├── 用 js 脚本开发自动化流程.md
│   ├── 用 python 脚本开发自动化流程.md
│   ├── 硬件键鼠模拟.md
│   ├── 老旧低配电脑.md
│   ├── 视频教程.md
│   ├── 集群控制中心.md
│   └── 验证码自动化.md
├── package.json
├── pbottleRPA.js
├── python示例/
│   ├── GPT图像解析示例.py
│   ├── GPT问题答案AI生成演示.py
│   ├── WEB增强-数据批量爬取演示.py
│   ├── WEB增强-浏览器元素操作演示.py
│   ├── WEB增强-账号密码登录演示.py
│   ├── [企业版]接力执行脚本.py
│   ├── [第三方] 读写Excel演示脚本.py
│   ├── [第三方] 读写word演示脚本.py
│   ├── pbottleRPA.py
│   ├── 上传(发送)文件演示.py
│   ├── 下载文件示例演示.py
│   ├── 剪切板演示脚本.py
│   ├── 压缩和解压缩示例.py
│   ├── 发送Email电子邮件.py
│   ├── 发送运维消息手机通知.py
│   ├── 图片相似度检测.py
│   ├── 基础(循环、判断、等待)演示.py
│   ├── 屏幕物体轮廓查找演示.py
│   ├── 常用工具 Utils 演示.py
│   ├── 异步子流程模板.py
│   ├── 微信朋友圈自动点赞.py
│   ├── 快速开始演示(3行代码).py
│   ├── 截屏操作演示脚本.py
│   ├── 文件基础操作演示.py
│   ├── 文字提取查找OCR演示.py
│   ├── 用户手动输入变量示例.py
│   ├── 运维消息手机通知.py
│   ├── 键盘基本操作演示脚本.py
│   └── 鼠标基础操作演示.py
├── word测试文档.docx
├── 上传(发送)文件演示.js
├── 下载文件示例演示.js
├── 企业版 Demo示例说明.txt
├── 剪切板演示脚本.js
├── 压缩和解压缩示例.js
├── 发送Email电子邮件.js
├── 发送运维消息手机通知.js
├── 图片相似度检测.js
├── 基础(循环、判断、等待)演示.js
├── 屏幕物体轮廓查找演示.js
├── 常用工具 Utils 演示.js
├── 异步子流程模板.js
├── 微信朋友圈自动点赞.js
├── 快速开始演示(3行代码).js
├── 截屏操作演示脚本.js
├── 文件基础操作演示.js
├── 文字提取查找OCR演示.js
├── 用户手动输入变量示例.js
├── 键盘基础操作演示脚本.js
└── 鼠标基础操作演示.js
Download .txt
SYMBOL INDEX (134 symbols across 9 files)

FILE: [第三方] 读写Excel演示脚本.mjs
  function excelAppend (line 78) | async function excelAppend(filename,line=[]) {

FILE: pbottleRPA.js
  function keycode (line 391) | function keycode(name) {
  function getHtml (line 937) | function getHtml(url, headersJson = {}, method = 'GET') {
  function sendMail (line 967) | function sendMail(
  function downloadFile (line 1043) | function downloadFile(fileUrl, filename, headersJson = {}) {
  function zipDir (line 1238) | function zipDir(directory, zipFilePath = "") {
  function unZip (line 1266) | function unZip(zipFilePath, directory = "") {
  function deviceID (line 1339) | function deviceID() {
  function clusterCenter (line 1352) | function clusterCenter() {
  function cloud_GPT (line 1387) | function cloud_GPT(question, modelLevel = 0, options = {
  function cloud_GPTV (line 1415) | function cloud_GPTV(question, imagePath, modelLevel = 0) {
  function cloud_GPTA (line 1446) | function cloud_GPTA(action = '点击', question = "桌面微信图标") {
  function waitImage (line 1884) | function waitImage(tpPath, intervalFun = () => { }, timeOut = 30, miniSi...
  function waitImageDisappear (line 1914) | function waitImageDisappear(tpPath, intervalFun = () => { }, timeOut = 3...
  function waitFile (line 1944) | function waitFile(dirPath, keyWords = '', intervalFun = () => { }, timeO...
  function waitFileDisappear (line 1972) | function waitFileDisappear(dirPath, keyWords = '', intervalFun = () => {...
  function waitInput (line 2000) | function waitInput(inputPrompt = '输入提示词', timeOut = 600) {
  function isNumeric (line 2215) | function isNumeric(value) {
  function hasData (line 2230) | function hasData(value) {
  function getTime (line 2270) | function getTime(format = 'Y-m-d H:i:s', timestamp = null) {
  function searchFile (line 2304) | function searchFile(directory, words = '', recursive = false) {
  function uniqid (line 2342) | function uniqid(prefix = '', moreEntropy = false) {
  function substringFromTo (line 2366) | function substringFromTo(str, from = '', to = '') {

FILE: python示例/[第三方] 读写Excel演示脚本.py
  function excel_append (line 27) | def excel_append(filename, line=None):

FILE: python示例/pbottleRPA.py
  class RPAError (line 50) | class RPAError(Exception):
  class TimeoutError (line 56) | class TimeoutError(RPAError):
  function urlencode (line 63) | def urlencode(input_str):
  function isNumeric (line 68) | def isNumeric(value):
  function hasData (line 81) | def hasData(value):
  function getTime (line 100) | def getTime(format_str="Y-m-d H:i:s", timestamp=None):
  function searchFile (line 128) | def searchFile(directory, words="", recursive=False):
  function uniqid (line 152) | def uniqid(prefix="", moreEntropy=False):
  function substringFromTo (line 166) | def substringFromTo(s, from_str="", to_str=""):
  function setDefaultDelay (line 183) | def setDefaultDelay(millisecond):
  function sleep (line 192) | def sleep(milliseconds):
  function wait (line 205) | def wait(seconds=1):
  function beep (line 222) | def beep():
  function showMsg (line 227) | def showMsg(title, content):
  function showRect (line 238) | def showRect(fromX=0, fromY=0, width=500, height=500, color="red", msec=...
  function exit_script (line 258) | def exit_script(*args):
  function exit (line 270) | def exit(*args):
  function kill (line 278) | def kill(processName, force=False):
  function moveMouseSmooth (line 297) | def moveMouseSmooth(x, y, interval=0):
  function moveAndClick (line 314) | def moveAndClick(x, y):
  function mouseClick (line 324) | def mouseClick(leftRight="left", time_ms=30):
  function mouseDoubleClick (line 335) | def mouseDoubleClick():
  function mouseWheel (line 341) | def mouseWheel(data=-720):
  function mouseLeftDragTo (line 350) | def mouseLeftDragTo(x, y):
  function mouseRightDragTo (line 362) | def mouseRightDragTo(x, y):
  function getScreenColor (line 374) | def getScreenColor(x, y):
  function screenShot (line 385) | def screenShot(savePath="", x=0, y=0, w=-1, h=-1):
  function keycode (line 407) | def keycode(name):
  function keyToggle (line 530) | def keyToggle(key, upDown="down"):
  function keyTap (line 546) | def keyTap(key):
  function mouseKeyToggle (line 563) | def mouseKeyToggle(key="left", upDown="down"):
  function findScreen (line 577) | def findScreen(tpPaths, miniSimilarity=0.85, fromX=0, fromY=0, width=-1,...
  function findText (line 608) | def findText(inputTxt, fromX=0, fromY=0, width=-1, height=-1):
  function waitText (line 626) | def waitText(
  function findContours (line 652) | def findContours(minimumArea=1000, fromX=0, fromY=0, width=-1, height=-1):
  function imgSimilar (line 676) | def imgSimilar(path1, path2, checkType="ORB"):
  function paste (line 692) | def paste(txt):
  function copyText (line 702) | def copyText(txt):
  function copyFile (line 711) | def copyFile(filepath):
  function getClipboard (line 724) | def getClipboard():
  function wxMessage (line 733) | def wxMessage(title, content, key):
  function postJson (line 745) | def postJson(url, msgJson, headersJson=None, method="POST"):
  function postJsonFile (line 767) | def postJsonFile(url, msgJsonFile, headersJson=None, method="POST"):
  function getHtml (line 791) | def getHtml(url, headersJson=None, method="GET"):
  function downloadFile (line 806) | def downloadFile(fileUrl, filename, headersJson=None):
  function sendMail (line 823) | def sendMail(
  function tts (line 857) | def tts(text):
  function openURL (line 867) | def openURL(myurl):
  function openDir (line 878) | def openDir(filePath):
  function getResolution (line 893) | def getResolution():
  function aiOcr (line 902) | def aiOcr(imagePath="screen", x=0, y=0, width=-1, height=-1):
  function aiObject (line 935) | def aiObject(minimumScore=0.5, x=0, y=0, width=-1, height=-1):
  function zipDir (line 964) | def zipDir(directory, zipFilePath=""):
  function unZip (line 982) | def unZip(zipFilePath, directory=""):
  function bufferGet (line 997) | def bufferGet(n=0):
  function bufferSet (line 1007) | def bufferSet(content, n=0):
  function delaySet (line 1017) | def delaySet(scriptPath=""):
  function deviceID (line 1030) | def deviceID():
  function clusterCenter (line 1039) | def clusterCenter():
  class cloud (line 1049) | class cloud:
    method GPT (line 1051) | def GPT(question, modelLevel=0, options=None):
    method GPTV (line 1080) | def GPTV(question, imagePath, modelLevel=0):
    method GPTA (line 1111) | def GPTA(action="点击", question="桌面微信图标"):
  class browserCMD (line 1165) | class browserCMD:
    method alert (line 1167) | def alert(msg):
    method closeTab (line 1174) | def closeTab(type="current"):
    method fetch (line 1184) | def fetch(fetch_url, options=None):
    method waitPageReady (line 1197) | def waitPageReady(readyURL, timeout=20):
    method url (line 1214) | def url(urlStr=None):
    method count (line 1226) | def count(selector):
    method dblclick (line 1237) | def dblclick(selector, options=None):
    method offset (line 1250) | def offset(selector):
    method click (line 1261) | def click(selector, options=None):
    method show (line 1274) | def show(selector):
    method hide (line 1281) | def hide(selector):
    method remove (line 1288) | def remove(selector):
    method text (line 1295) | def text(selector, content=None):
    method html (line 1308) | def html(selector, content=None):
    method val (line 1320) | def val(selector, content=None):
    method cookie (line 1332) | def cookie(cName, cValue=None, expDays=None):
    method css (line 1349) | def css(selector, propertyname, value=None):
    method attr (line 1366) | def attr(selector, propertyname, value=None):
    method prop (line 1383) | def prop(selector, propertyname, value=None):
  class hid (line 1401) | class hid:
    method keyToggle (line 1403) | def keyToggle(key, upDown="down"):
    method keyTap (line 1415) | def keyTap(key):
    method _mouseCMD (line 1429) | def _mouseCMD(button=1, x=0, y=0, wheel=0, time_ms=10):
    method moveMouse (line 1436) | def moveMouse(x, y):
    method mouseClick (line 1441) | def mouseClick(button="left", time_ms=10):
    method moveAndClick (line 1449) | def moveAndClick(x, y):
    method mouseDoubleClick (line 1455) | def mouseDoubleClick():
    method mouseLeftDragTo (line 1462) | def mouseLeftDragTo(x, y):
    method mouseRightDragTo (line 1470) | def mouseRightDragTo(x, y):
    method mouseWheel (line 1478) | def mouseWheel(data=-1):
  function waitImage (line 1486) | def waitImage(tpPath, intervalFun=None, timeOut=30, miniSimilarity=0.85):
  function waitImageDisappear (line 1510) | def waitImageDisappear(tpPath, intervalFun=None, timeOut=30, miniSimilar...
  function waitFile (line 1534) | def waitFile(dirPath, keyWords="", intervalFun=None, timeOut=30):
  function waitFileDisappear (line 1556) | def waitFileDisappear(dirPath, keyWords="", intervalFun=None, timeOut=30):
  function waitInput (line 1578) | def waitInput(inputPrompt="输入提示词", timeOut=600):
  class utils (line 1598) | class utils:

FILE: python示例/常用工具 Utils 演示.py
  function main (line 18) | def main():

FILE: python示例/异步子流程模板.py
  function 同步子流程 (line 20) | def 同步子流程(脚本路径):
  function 异步子流程 (line 46) | def 异步子流程(脚本路径, *args):
  function 动态导入子模块 (line 68) | def 动态导入子模块(模块路径):
  function 子流程2 (line 85) | def 子流程2(params):
  function main (line 94) | def main():

FILE: python示例/微信朋友圈自动点赞.py
  function loop (line 53) | def loop():

FILE: 异步子流程模板.js
  function main (line 16) | async function main() {
  function 子流程2 (line 53) | async function 子流程2(params) {  // 定义一个内联的异步函数作为子流程,接收参数,子流程推荐使用中文命名

FILE: 微信朋友圈自动点赞.js
  function 重复 (line 49) | function 重复() {
Condensed preview — 120 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (364K chars).
[
  {
    "path": ".gitignore",
    "chars": 229,
    "preview": "/**/node_modules\n/**/__pycache__\nnode_modules\nExcel测试表格.xlsx\n/**/Excel测试表格.xlsx\n单元测试test.js\ntest.js\ntest.mjs\n配置项.json\npa"
  },
  {
    "path": "GPT图像解析示例.js",
    "chars": 703,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "GPT问题答案AI生成演示.js",
    "chars": 1208,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "MIT License\r\n\r\nCopyright (c) 2023 LEO\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\no"
  },
  {
    "path": "README.md",
    "chars": 4068,
    "preview": "#  小瓶RPA\r\n\r\n专业用户的专业RPA+AI软件。\r\n\r\n### 介绍\r\n小瓶RPA,长难业务自动化流程专精。 轻量级简单全能的RPA软件,显著降本增效 & 工作100%准确 & 非侵入式集成。同时支持浏览器web应用和客户端应用的操"
  },
  {
    "path": "WEB增强-数据批量爬取演示.js",
    "chars": 2325,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "WEB增强-浏览器元素操作演示.js",
    "chars": 4442,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "WEB增强-账号密码登录演示.js",
    "chars": 1932,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "[企业版]HID硬件级键盘鼠标演示.js",
    "chars": 3393,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "[企业版]外部控制能力.js",
    "chars": 1683,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nconst pb"
  },
  {
    "path": "[企业版]屏幕物体查找Ai演示.js",
    "chars": 1173,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "[企业版]接力执行脚本.js",
    "chars": 476,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nconst pb"
  },
  {
    "path": "[企业版]集群控制中心日志回传.js",
    "chars": 978,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nconst pb"
  },
  {
    "path": "[企业版]集群控制中心流程升级 .js",
    "chars": 1010,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nconst pb"
  },
  {
    "path": "[企业版]集群控制中心示例.js",
    "chars": 650,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nconst pb"
  },
  {
    "path": "[第三方 模块卸载].bat",
    "chars": 131,
    "preview": "chcp 65001\n@echo off\ncd .\n\necho 正在卸载模块中\ncall npm uninstall  exceljs mammoth docx\necho.\n\necho.\necho 卸载完成!~    按任意键退出\n\npau"
  },
  {
    "path": "[第三方 模块安装].bat",
    "chars": 212,
    "preview": "chcp 65001\n@echo off\ncd .\n\necho 正在切换国内安装源 \ncall npm config set registry https://registry.npmmirror.com/\necho.\n\necho 正在安装"
  },
  {
    "path": "[第三方] 读写Excel演示脚本.mjs",
    "chars": 2117,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nimport p"
  },
  {
    "path": "[第三方] 读写word演示脚本.mjs",
    "chars": 2170,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n */\n\nimport p"
  },
  {
    "path": "docs/-图片测试.md",
    "chars": 141,
    "preview": "![image.png](https://raw.gitcode.com/qq_17154415/pbottleRPAdoc/attachment/uploads/b83ca6b3-5bd9-47c8-9fe2-50602f7bd9bb/i"
  },
  {
    "path": "docs/.vitepress/config.mjs",
    "chars": 5827,
    "preview": "import { defineConfig } from 'vitepress'\n\n\n// https://vitepress.dev/reference/site-config\nexport default defineConfig({\n"
  },
  {
    "path": "docs/AI生成流程脚本.md",
    "chars": 587,
    "preview": "# AI 对话大模型 生成小瓶RPA流程脚本\n\n得益于小瓶RPA脚本层开源开放政策,小瓶RPA能够轻松用主流AI编程助手快速生成高质量流程脚本。\n\n此功能目前只做参考和测试用途。\n\n## 小瓶RPA助手智能体\n\n\n直接访问小瓶RPA助手智能"
  },
  {
    "path": "docs/APIAI图像.md",
    "chars": 1285,
    "preview": "# 本地 AI 图像\n\n首先在软件设置中开启本地AI识别功能模块,然后重启软件生效。\n\n![本地AI识别](public/Snipaste_2025-11-21_01-17-47.png)\n\n## aiOcr 文字识别\n\n@param {s"
  },
  {
    "path": "docs/APIAI大模型.md",
    "chars": 842,
    "preview": "# AI 大模型(云模块)\n\n小瓶RPA 云端模块,AI在线大模型\n\n![本地AI识别](public/Snipaste_2025-11-21_01-21-00.png)\n\n1. 此模块不是必须模块 ,云端模块不影响本地模块的独立运行\n2."
  },
  {
    "path": "docs/API办公文档.md",
    "chars": 209,
    "preview": "# office 办公文档\n\n## 常规模拟操作\n\n办公文档的操作一般依赖常用的办公软件:微软office、金山wps 等,\n小瓶 RPA 可以通过模拟操作鼠标键盘操作这些软件。\n\n## 后台读写快捷模式\n\n1. Excel 文档:参考示例"
  },
  {
    "path": "docs/API压缩解压.md",
    "chars": 296,
    "preview": "# 压缩解压缩\n\n新版压缩解压接口支持 4GB 以上超大文件\n\n## 压缩 zipDir\n\n压缩文件夹内容成一个zip文件包 v2025.0 以后版本生效\n\n@param {string} directory 文件夹路径,输入绝对路径\n\n@"
  },
  {
    "path": "docs/API声音.md",
    "chars": 96,
    "preview": "# 声音\n\n## beep 蜂鸣声\n\n发出系统默认提示音\n\n\n## tts 文字转语音\n\n从文本到语音(TextToSpeech) 语音播报\n非阻塞\n\n@param {*} text 朗读内容"
  },
  {
    "path": "docs/API外部控制.md",
    "chars": 1786,
    "preview": "# 外部控制\n\n除了实现软件操作自动化外,外部控制功能够实现小瓶RPA自身操作的自动化。\n\n外部控制提供一系列控制小瓶RPA本身的 **http 接口** ,可以由 第三方系统 或者 RPA集群控制器 发出。\n\n外部控制功能只对企业版开放 "
  },
  {
    "path": "docs/API屏幕.md",
    "chars": 1560,
    "preview": "# 屏幕画面\n\n\n## getResolution 获取屏幕分辨率\n\n获取当前屏幕分辨率, ratio 为桌面缩放比例\n\n@returns JSON 内容格式  `{ w:1920,h:1080,ratio:1.5 }`\n\n## scree"
  },
  {
    "path": "docs/API浏览器增强.md",
    "chars": 4615,
    "preview": "# 浏览器增强 - web应用\n\n**⚠ 浏览器插件只是一种web浏览器页面操作的快捷操作方式,不是必须。按照小瓶RPA桌面应用的操作规则一样可以操作web浏览器应用。**\n\n需要先安装小瓶RPA浏览器插件,安装方法查看:\n\nhttps:/"
  },
  {
    "path": "docs/API用户输入.md",
    "chars": 343,
    "preview": "# 用户输入\n\n小瓶RPA流程执行中,流程可以暂停等待用户可以手动输入数值或者选项。\n\n输入后流程继续执行。\n\n⚠ V2026.0 以上版本基座支持用户输入。\n\n\n\n## 等待输入 waitInput\n\n\n * 等待输入 V2026.0.0"
  },
  {
    "path": "docs/API系统相关.md",
    "chars": 1886,
    "preview": "# 系统相关\n\n## wait 等待\n\n  脚本暂停等待操作响应 (秒)\n  注意:一次等待超过100s, 会有日志提示\n  @param {number} seconds  秒,  缺省值为 1 秒。支持小数。\n\n## setDefaul"
  },
  {
    "path": "docs/API统一规范.md",
    "chars": 578,
    "preview": "# API统一规范定义\n\n## 屏幕坐标\n\n以屏幕左上角为原点(坐标为 (0, 0)),水平向右为 x 轴正方向,垂直向下为 y 轴正方向。在这种坐标系中,屏幕上任意一点都可以通过一个 (x, y) 的坐标值来表示,其中 x 表示该点距离原"
  },
  {
    "path": "docs/API网络.md",
    "chars": 1465,
    "preview": "# 网络\n\n流程同步网络方法\n\n## openURL 打开网址\n\n用电脑默认浏览器打开网址\n\n@param {string} myurl 网址\n\n示例:快速开始脚本\n\n## getHtml 请求网址 (同步方法)\n\n  普通请求网址,获取返"
  },
  {
    "path": "docs/API通用工具.md",
    "chars": 911,
    "preview": "# pbottleRPA.utils 工具箱\n\n提供基础常用便利工具\n\n调用方式:\npbottleRPA.utils.xxx()\n\ndemo示例:\n常用工具 Utils 演示.js\n\n## 获取格式化时间 getTime\n\nutils.ge"
  },
  {
    "path": "docs/API键盘操作.md",
    "chars": 489,
    "preview": "# 键盘模拟操作\n\n## keyToggle 键盘基础触发\n\n模拟按键触发事件\n\n@param {*} key 按键名称参考:https://www.pbottle.com/a-13862.html\n\n@param {*} upDown 默"
  },
  {
    "path": "docs/API键鼠硬模拟.md",
    "chars": 1450,
    "preview": "# 键鼠硬模拟 API\n\n小瓶RPA硬件增强不是必选项,需要购买额外的硬件,建议只有需要系统级模拟操作时候才启用。\n\n开启硬件键盘鼠标模拟,不影响默认的软件键盘鼠标模拟。\n\n功能起始版本:V2024.3\n\n本功能只对企业版开放 ,详细查看小"
  },
  {
    "path": "docs/API鼠标操作.md",
    "chars": 579,
    "preview": "# 鼠标操作模拟\n\n\n## moveMouse 鼠标移动\n\n移动鼠标到指定位置并点击 起点为屏幕左上角\n\n@param {number} x 横坐标\n\n@param {number} y 纵坐标\n\n@param {number} inter"
  },
  {
    "path": "docs/Demo示例.md",
    "chars": 311,
    "preview": "# Demo示例\n\n小瓶RPA提供多个基础功能的demo示例,方面大家参考。具体API的使用可以参考demo,所有api接口都有demo示例。\n\nDemo示例为 JavaScript 和 python 脚本语言,符合完整 Nodejs NP"
  },
  {
    "path": "docs/HTTP静态服务.md",
    "chars": 271,
    "preview": "# 集成HTTP静态服务\n\n小瓶RPA 集成了HTTP静态服务,可以快速集成静态页面,实现自动化测试、下载文件、H5页面展示等功能。\n\n⚠ 本功能V2026.1以上版本有效。\n\n## 静态文件本地目录\n\n小瓶RPA主菜单 设置-》打开数据目"
  },
  {
    "path": "docs/SaaS系统自动化任务.md",
    "chars": 221,
    "preview": "# SaaS系统自动化任务\n\n小瓶RPA可以为现有SaaS系统服务,升级提供自动化任务服务中心。\n\n## 优势\n\n- SaaS 系统无缝平台升级改造\n- SaaS 品牌用户端保持\n- 本地批量集中执行,低成本\n- 扩充现有 SaaS 现有自"
  },
  {
    "path": "docs/index.md",
    "chars": 1266,
    "preview": "---\nnext:\n  text: 'Demo示例'\n  link: 'Demo示例.html'\n---\n\n\n# 开始使用小瓶RPA\n\n**小瓶RPA,专业用户的专业RPA+AI软件。**\n\n长难业务自动化流程专精,轻量级简单全能的RPA软"
  },
  {
    "path": "docs/package.json",
    "chars": 255,
    "preview": "{\n  \"devDependencies\": {\n    \"rimraf\": \"^6.1.2\",\n    \"vitepress\": \"^1.6.4\"\n  },\n  \"scripts\": {\n    \"docs:dev\": \"vitepres"
  },
  {
    "path": "docs/public/index.html",
    "chars": 8,
    "preview": "文档静态资源目录"
  },
  {
    "path": "docs/win7操作系统.md",
    "chars": 641,
    "preview": "# win7 操作系统上使用小瓶RPA\n\n国内政企单位仍有大量 win7 电脑设备\n\n小瓶 RPA 仍然花费开发资源保持对 win7 系统的全功能模块兼容。\n\n\n\n## win7 小瓶RPA平台基座\n\nWin7 系统存在大量盗版精简系统,尽"
  },
  {
    "path": "docs/‌Q&A.md",
    "chars": 1963,
    "preview": "# Q & A 常见问答\n\n- 收集和探讨关于小瓶RPA的常见问题和官方答案。\n- 更多问题可以联系在线客服。\n\n## 小瓶RPA 可以实现哪些流程的自动化,可以破解某个系统或软件吗?\n\n小瓶RPA不具有破解和越权操作能力,RPA可以代替并"
  },
  {
    "path": "docs/专用自动化独立软件.md",
    "chars": 526,
    "preview": "# 专用自动化独立软件(上位机软件定制开发)\n\n独立自动化产品定制,硬件软件结合的独立可执行程序。\n\n商业化友好,高性能 C/C++,AI能力集成等特点。\n\n浏览业务官网:https://soft.pbottle.com/\n\n\n## 硬件设"
  },
  {
    "path": "docs/业务管理系统.md",
    "chars": 450,
    "preview": "# 业务管理系统(小瓶ERP业务管理系统)\n\n市场瞬息万变,业务系统必须实时响应,小瓶ERP业务管理系统,业务敏捷开发高于一切的ERP系统,能够快速低成本实现贴合业务的量身定制。\n\n摒弃传统 ERP 系统冗长的开发周期与高昂的定制成本,依托"
  },
  {
    "path": "docs/中文调用.md",
    "chars": 249,
    "preview": "# 中文调用\n\n- 增加中文调用方式,更直观,更易读,更低入门\n- 免去注释,快速开发流程\n- 中文+英文 双重调用方式\n\n## API 中英文对照示意图\n\n![中文API脚本示意图](./public/chinese-coding.png"
  },
  {
    "path": "docs/信创操作系统.md",
    "chars": 557,
    "preview": "# 信创操作系统上使用小瓶RPA\n\n小瓶RPA从 V2024.8 版本支持信创操作系统,已经完成信创操作系统的全功能模块原生支持。\n\n\n![小瓶RPA信创版本](https://www.pbottle.com/static/upload/2"
  },
  {
    "path": "docs/其他功能模块.md",
    "chars": 2279,
    "preview": "# 自由引入其他功能模块\n\nJavaScript 是互联网上最流行的脚本语言之一,所有网页前端和客户端中都有它的身影,由于是一种类C语言,有着广泛的用户基础并且 JavaScript 很容易学习。\n\nNode.js使用 `npm` 作为其默"
  },
  {
    "path": "docs/定时启动.md",
    "chars": 763,
    "preview": "# 定时启动计划任务\n\n## 小瓶RPA自带定时任务规则\n\n计划任务设置规则\n\n1. 小时字段和分钟字段用空格隔开\n2. 多个值用逗号隔开\n3. 小时的取值范围 0-23,分钟取值范围0-59 \n4. 支持  -   表示范围,支持  * "
  },
  {
    "path": "docs/开机启动.md",
    "chars": 218,
    "preview": "# 开机启动\n\n软件和流程均支持零操作,开机即可自动启动。\n\n## 开机启动小瓶RPA基座\n\n设置-》基础设置-》开机自动启动\n\n![小瓶RPA设置开机启动](public/imageautostart.png)\n\n###  信创系统\n\n信"
  },
  {
    "path": "docs/手机应用的自动化.md",
    "chars": 474,
    "preview": "# 手机应用的自动化\n\n手机应用可以采用 Android 模拟器方案 或者 真机投屏交互方案。得益于小瓶RPA采用纯图像识别的驱动方式,完全兼容各种手机应用模拟器 和 手机厂商的镜像投屏\n **可以使用除了web增强插件外的任意api接口能"
  },
  {
    "path": "docs/无尽模式.md",
    "chars": 260,
    "preview": "# 流程执行的无尽模式\n\n依赖  delaySet()  ,   小瓶RPA可以稳定高效地连续执行流程任务.\n\nA 任务是一个时间长短不定的流程,如果想让A任务不停的运行,这就不能用定时器准确启动了。我们可以 A 任务中开头设置接力任务A自"
  },
  {
    "path": "docs/更多三方功能和拓展.md",
    "chars": 835,
    "preview": "\n--小瓶RPA兼容所有 nodejs生态\n\nnodejs 文档:https://nodejs.org/docs/latest-v16.x/api/\n\nnpm 第三方功能库:https://www.npmjs.com/\n\n **任何传统编程"
  },
  {
    "path": "docs/桌面快捷方式.md",
    "chars": 629,
    "preview": "# 桌面快捷方式\n\n每个流程应用都可以在桌面创建一个快捷方式。\n注:本功能依赖外部控制方式,企业版可用\n\n\n## windows 桌面启动\n\n新建文本文件,改名为: 一键启动.bat\n\n发送到桌面快捷方式\n\n```bat\nchcp 6500"
  },
  {
    "path": "docs/流程录制.md",
    "chars": 411,
    "preview": "# 小瓶RPA流程录制\n\n对于**简单鼠标操作**流程,小瓶RPA可以直接录制成js脚本,然后直接开始执行。\n\n流程录制功能模块条件:\n1. windows版本的小瓶RPA\n2. 小瓶RPA V2024 以上版本\n\n\n![小瓶RPA流程录制"
  },
  {
    "path": "docs/流程运行日志.md",
    "chars": 447,
    "preview": "# 流程运行日志\n\n小瓶RPA既有当前日志 和 永久硬盘日志\n\n**新版支持:每个流程独立生成一个日志文件,形成一个清晰的运行记录。**\n\n\n## 实时运行日志\n\n小瓶RPA的流程日志系统会直接承接脚本引擎的标准输出,并会自动附加当前的详细"
  },
  {
    "path": "docs/流程配置项.md",
    "chars": 313,
    "preview": "# 流程配置项\n\n\n- 为每个流程设立一个配置项\n- 日常用户只修改 excel 或者 json 即可,不需要直接修改流程脚本代码\n\n\n## 配置项为 excel 格式\n\n建立一个excel文件,如:配置项.xlsx\n\n参考示例: Exce"
  },
  {
    "path": "docs/热键和快捷方式.md",
    "chars": 454,
    "preview": "# 热键和快捷方式\n\n方便用户快速启动已有的任务流程,手动和自动充分结合,增效更多应用场景。\n\n比如自动登录,自动数据处理等场景,让小瓶RPA真正成为你的办公小助手。\n\n## 全局流程任务热键\n\n#### Ctrl + Shift + Q\n"
  },
  {
    "path": "docs/用 js 脚本开发自动化流程.md",
    "chars": 1300,
    "preview": "# 用 js 脚本开发自动化流程 \n\nJavaScript 是互联网上最流行的脚本语言之一,所有网页中都有它的身影,由于是一种类C语言,有着广泛的用户基础并且 JavaScript 很容易学习。\n\n小瓶RPA自动流程脚本的开发只用到了最基础"
  },
  {
    "path": "docs/用 python 脚本开发自动化流程.md",
    "chars": 691,
    "preview": "# 用 python 脚本开发自动化流程(beta)\n\nPython 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。\n\nPython 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比"
  },
  {
    "path": "docs/硬件键鼠模拟.md",
    "chars": 480,
    "preview": "# 硬件键盘鼠标自动化模拟\n\n⚠ 只有商业用户获取支持\n\n多数 RPA 采用**软件模拟**方式操作系统的键盘和鼠标。\n部分网银软件、U盾软件、财务软件等,为了安全禁止其他软件对其操作。\n小瓶 RPA **硬件键盘鼠标自动化模拟** 可以解"
  },
  {
    "path": "docs/老旧低配电脑.md",
    "chars": 308,
    "preview": "# 低配置老旧电脑运行自动化\n\n\n小瓶RPA基座平台采用 c++ 高性能开发方案,已经最大限度的减低电脑配。\n\n## 平台基座系统资源占用优化\n\n设置里取消本地 AI 引擎的启动加载。\n\n取消全部AI引擎后,小瓶RPA主内存可以降低到 40"
  },
  {
    "path": "docs/视频教程.md",
    "chars": 114,
    "preview": "# 小瓶RPA系列视频教程\n\n已经陆续推出了免费的 《小瓶RPA系列视频教程》\n\n请网络搜索 :小瓶RPA系列视频教程\n\n## B站入口\n\nhttps://www.bilibili.com/video/BV1Th4y1C7np/"
  },
  {
    "path": "docs/集群控制中心.md",
    "chars": 1678,
    "preview": "# 集群控制中心\n\n⚠ 集群控制只有商业版本可以获取支持\n\n\n**小瓶RPA 集群管理系统可以让终端用户只通过浏览器批量管理自己的任务结果,无需接触具体任务流程。**\n\n小瓶RPA(机器人流程自动化)集群控制中心是整个 RPA 批量任务的核"
  },
  {
    "path": "docs/验证码自动化.md",
    "chars": 565,
    "preview": "# 验证码自动化\n\n**温馨提示:小瓶RPA本身以模拟操作和数据处理为主,它不具有任何破解功能**\n\n优先选择记住登录状态、U盾、定时刷新等方法保持用户权限的会话。\n\n## 简单图形验证码\n   \n小瓶RPA文字OCR模块可以解决简单字母数"
  },
  {
    "path": "package.json",
    "chars": 309,
    "preview": "{\n  \"name\": \"pbottle-rpa-demo\",\n  \"version\": \"2025.4.0\",\n  \"description\": \"小瓶RPA基础演示流程,使用教程:https://rpa.pbottle.com/a-14"
  },
  {
    "path": "pbottleRPA.js",
    "chars": 64473,
    "preview": "/**\n *  小瓶 RPA 标准库API  NodeJS版本\n *  官网:https://rpa.pbottle.com/\n *  作者:leo@pbottle.com\n *  \n *  欢迎各路高手将本代码转换成 python、lua"
  },
  {
    "path": "python示例/GPT图像解析示例.py",
    "chars": 611,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的GPT图像"
  },
  {
    "path": "python示例/GPT问题答案AI生成演示.py",
    "chars": 407,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\n\nasks = [\n    '鲁迅"
  },
  {
    "path": "python示例/WEB增强-数据批量爬取演示.py",
    "chars": 1027,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\nimpor"
  },
  {
    "path": "python示例/WEB增强-浏览器元素操作演示.py",
    "chars": 2245,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\n\npri"
  },
  {
    "path": "python示例/WEB增强-账号密码登录演示.py",
    "chars": 1205,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\n\npri"
  },
  {
    "path": "python示例/[企业版]接力执行脚本.py",
    "chars": 220,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\n\npbo"
  },
  {
    "path": "python示例/[第三方] 读写Excel演示脚本.py",
    "chars": 2158,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\"\"\"\n\nimport sys\nimport"
  },
  {
    "path": "python示例/[第三方] 读写word演示脚本.py",
    "chars": 2009,
    "preview": "\"\"\"\n小瓶 RPA 演示 demo,具体 api 请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\"\"\"\n\nimport sys\ni"
  },
  {
    "path": "python示例/pbottleRPA.py",
    "chars": 44917,
    "preview": "\"\"\"\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n官网:https://rpa.pbottle.com/\n\nNodejs 移植兼容版 beta\n注:目前已完成 No"
  },
  {
    "path": "python示例/上传(发送)文件演示.py",
    "chars": 1413,
    "preview": "\"\"\" \n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的文件上传"
  },
  {
    "path": "python示例/下载文件示例演示.py",
    "chars": 945,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的文件下载功"
  },
  {
    "path": "python示例/剪切板演示脚本.py",
    "chars": 645,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\nprin"
  },
  {
    "path": "python示例/压缩和解压缩示例.py",
    "chars": 997,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的压缩和解压"
  },
  {
    "path": "python示例/发送Email电子邮件.py",
    "chars": 499,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的电子邮件E"
  },
  {
    "path": "python示例/发送运维消息手机通知.py",
    "chars": 1351,
    "preview": "\"\"\"小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的手机消息通知"
  },
  {
    "path": "python示例/图片相似度检测.py",
    "chars": 823,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的图片相似度"
  },
  {
    "path": "python示例/基础(循环、判断、等待)演示.py",
    "chars": 679,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport random \n\n\n"
  },
  {
    "path": "python示例/屏幕物体轮廓查找演示.py",
    "chars": 978,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的屏幕物体轮"
  },
  {
    "path": "python示例/常用工具 Utils 演示.py",
    "chars": 3349,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的常用工具函"
  },
  {
    "path": "python示例/异步子流程模板.py",
    "chars": 3732,
    "preview": "\"\"\"\n小瓶RPA异步子流程模板\n\n功能说明:此脚本演示了RPA中如何使用子流程(同步/异步)\n通过这个示例,您可以学习如何在主流程中调用同步和异步子流程,并处理子流程的返回结果。\n\"\"\"\n\n# 引入小瓶RPA的核心库\nimport pbo"
  },
  {
    "path": "python示例/微信朋友圈自动点赞.py",
    "chars": 1772,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  # 引入小瓶RPA模块\nimport time\n\n\npr"
  },
  {
    "path": "python示例/快速开始演示(3行代码).py",
    "chars": 228,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\n\npbottleRPA.openU"
  },
  {
    "path": "python示例/截屏操作演示脚本.py",
    "chars": 906,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\nprin"
  },
  {
    "path": "python示例/文件基础操作演示.py",
    "chars": 847,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport os\nimport "
  },
  {
    "path": "python示例/文字提取查找OCR演示.py",
    "chars": 1643,
    "preview": "\"\"\" \n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:此脚本演示了RPA中的OCR文"
  },
  {
    "path": "python示例/用户手动输入变量示例.py",
    "chars": 434,
    "preview": "\"\"\"\n小瓶RPA演示demo,具体api请查看*流程开发文档*\n官网:https://rpa.pbottle.com/\n流程开发文档:https://rpa.pbottle.com/docs/\n\n功能说明:这是一个最基础的小瓶RPA Ja"
  },
  {
    "path": "python示例/运维消息手机通知.py",
    "chars": 1045,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\n\npri"
  },
  {
    "path": "python示例/键盘基本操作演示脚本.py",
    "chars": 1390,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\n\nimport pbottleRPA  # 引入小瓶RPA模块\nimport time\n\npri"
  },
  {
    "path": "python示例/鼠标基础操作演示.py",
    "chars": 1788,
    "preview": "\"\"\"\n\n小瓶RPA python版本(Beta)\nhttps://gitee.com/pbottle/pbottle-rpa\n示例\n\n\"\"\"\nimport pbottleRPA  #引入小瓶RPA模块\nimport time\n\nprint"
  },
  {
    "path": "上传(发送)文件演示.js",
    "chars": 1363,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "下载文件示例演示.js",
    "chars": 1093,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "企业版 Demo示例说明.txt",
    "chars": 286,
    "preview": "企业版demo示例演示运行步骤:\n\n① 访问 https://gitee.com/pbottle/pbottle-rpa(或者:https://github.com/leoxiaoping/pbottleRPA)\n\n② 打开以 [企业版] "
  },
  {
    "path": "剪切板演示脚本.js",
    "chars": 1432,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "压缩和解压缩示例.js",
    "chars": 1138,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "发送Email电子邮件.js",
    "chars": 508,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "发送运维消息手机通知.js",
    "chars": 1611,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "图片相似度检测.js",
    "chars": 912,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "基础(循环、判断、等待)演示.js",
    "chars": 1457,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "屏幕物体轮廓查找演示.js",
    "chars": 1201,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "常用工具 Utils 演示.js",
    "chars": 3477,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "异步子流程模板.js",
    "chars": 1436,
    "preview": "/**\n * 小瓶RPA异步子流程模板\n * \n * 功能说明:此脚本演示了RPA中如何使用异步子流程\n * 通过这个示例,您可以学习如何在主流程中调用同步和异步子流程,并处理子流程的返回结果\n */\n\n// 引入小瓶RPA的核心库,获得对"
  },
  {
    "path": "微信朋友圈自动点赞.js",
    "chars": 1963,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "快速开始演示(3行代码).js",
    "chars": 425,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:这"
  },
  {
    "path": "截屏操作演示脚本.js",
    "chars": 1869,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "文件基础操作演示.js",
    "chars": 1830,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "文字提取查找OCR演示.js",
    "chars": 1775,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "用户手动输入变量示例.js",
    "chars": 497,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:这"
  },
  {
    "path": "键盘基础操作演示脚本.js",
    "chars": 3188,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  },
  {
    "path": "鼠标基础操作演示.js",
    "chars": 4100,
    "preview": "/**\n * 小瓶RPA演示demo,具体api请查看*流程开发文档*\n * 官网:https://rpa.pbottle.com/\n * 流程开发文档:https://rpa.pbottle.com/docs/\n * \n * 功能说明:此"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the leoxiaoping/pbottleRPA GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 120 files (244.6 KB), approximately 97.8k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!